diff --git a/.deadcode-out b/.deadcode-out index a44599b6f1..70a74128eb 100644 --- a/.deadcode-out +++ b/.deadcode-out @@ -22,11 +22,6 @@ code.gitea.io/gitea/models/actions ScheduleList.LoadTriggerUser ScheduleList.LoadRepos -code.gitea.io/gitea/models/asymkey - ErrGPGKeyAccessDenied.Error - ErrGPGKeyAccessDenied.Unwrap - HasDeployKey - code.gitea.io/gitea/models/auth GetSourceByName WebAuthnCredentials @@ -54,9 +49,7 @@ code.gitea.io/gitea/models/git code.gitea.io/gitea/models/issues IsErrUnknownDependencyType - ErrNewIssueInsert.Error IsErrIssueWasClosed - ChangeMilestoneStatus code.gitea.io/gitea/models/organization GetTeamNamesByID @@ -81,16 +74,12 @@ code.gitea.io/gitea/models/repo WatchRepoMode code.gitea.io/gitea/models/user - ErrUserInactive.Error - ErrUserInactive.Unwrap IsErrExternalLoginUserAlreadyExist IsErrExternalLoginUserNotExist NewFederatedUser IsErrUserSettingIsNotExist GetUserAllSettings DeleteUserSetting - GetUserEmailsByNames - GetUserNamesByIDs code.gitea.io/gitea/modules/activitypub NewContext diff --git a/.forgejo/workflows-composite/build-backend/action.yaml b/.forgejo/workflows-composite/build-backend/action.yaml index ada372b834..68a99ffaf9 100644 --- a/.forgejo/workflows-composite/build-backend/action.yaml +++ b/.forgejo/workflows-composite/build-backend/action.yaml @@ -3,7 +3,7 @@ runs: steps: - run: | su forgejo -c 'make deps-backend' - - uses: actions/cache@v4 + - uses: https://data.forgejo.org/actions/cache@v4 id: cache-backend with: path: ${{github.workspace}}/gitea diff --git a/.forgejo/workflows-composite/setup-cache-go/action.yaml b/.forgejo/workflows-composite/setup-cache-go/action.yaml index 1b1d37bb6b..cc21485d14 100644 --- a/.forgejo/workflows-composite/setup-cache-go/action.yaml +++ b/.forgejo/workflows-composite/setup-cache-go/action.yaml @@ -49,7 +49,7 @@ runs: - name: "Restore Go dependencies from cache or mark for later caching" id: cache-deps - uses: actions/cache@v4 + uses: https://data.forgejo.org/actions/cache@v4 with: key: setup-cache-go-deps-${{ runner.os }}-${{ inputs.username }}-${{ steps.go-version.outputs.go_version }}-${{ hashFiles('go.sum', 'go.mod') }} restore-keys: | diff --git a/.forgejo/workflows/build-release-integration.yml b/.forgejo/workflows/build-release-integration.yml index 6410915644..1af6d567dd 100644 --- a/.forgejo/workflows/build-release-integration.yml +++ b/.forgejo/workflows/build-release-integration.yml @@ -25,7 +25,7 @@ jobs: if: vars.ROLE == 'forgejo-coding' runs-on: lxc-bookworm steps: - - uses: actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v4 - id: forgejo uses: https://data.forgejo.org/actions/setup-forgejo@v2.0.4 diff --git a/.forgejo/workflows/build-release.yml b/.forgejo/workflows/build-release.yml index 9d88cb43dd..0d7f94c5a6 100644 --- a/.forgejo/workflows/build-release.yml +++ b/.forgejo/workflows/build-release.yml @@ -33,7 +33,7 @@ jobs: # root is used for testing, allow it if: vars.ROLE == 'forgejo-integration' || github.repository_owner == 'root' steps: - - uses: actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v4 with: fetch-depth: 0 diff --git a/.forgejo/workflows/cascade-setup-end-to-end.yml b/.forgejo/workflows/cascade-setup-end-to-end.yml index 710cd27ba4..bcc7821f4f 100644 --- a/.forgejo/workflows/cascade-setup-end-to-end.yml +++ b/.forgejo/workflows/cascade-setup-end-to-end.yml @@ -37,11 +37,11 @@ jobs: container: image: data.forgejo.org/oci/node:20-bookworm steps: - - uses: actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v4 with: fetch-depth: '0' show-progress: 'false' - - uses: https://code.forgejo.org/actions/cascading-pr@v2.2.0 + - uses: https://data.forgejo.org/actions/cascading-pr@v2.2.0 with: origin-url: ${{ env.GITHUB_SERVER_URL }} origin-repo: ${{ github.repository }} diff --git a/.forgejo/workflows/publish-release.yml b/.forgejo/workflows/publish-release.yml index 0e75912a3a..b44d670fc4 100644 --- a/.forgejo/workflows/publish-release.yml +++ b/.forgejo/workflows/publish-release.yml @@ -39,7 +39,7 @@ jobs: runs-on: lxc-bookworm if: vars.DOER != '' && vars.FORGEJO != '' && vars.TO_OWNER != '' && vars.FROM_OWNER != '' && secrets.TOKEN != '' steps: - - uses: actions/checkout@v4 + - uses: https://data.forgejo.org/actions/checkout@v4 - name: copy & sign uses: https://data.forgejo.org/forgejo/forgejo-build-publish/publish@v5.3.1 diff --git a/.forgejo/workflows/renovate.yml b/.forgejo/workflows/renovate.yml index 632fb82c19..f72aaa5803 100644 --- a/.forgejo/workflows/renovate.yml +++ b/.forgejo/workflows/renovate.yml @@ -25,7 +25,7 @@ jobs: runs-on: docker container: - image: data.forgejo.org/renovate/renovate:39.106.0 + image: data.forgejo.org/renovate/renovate:39.136.1 steps: - name: Load renovate repo cache diff --git a/.forgejo/workflows/testing.yml b/.forgejo/workflows/testing.yml index eb3163d3ae..784bc45736 100644 --- a/.forgejo/workflows/testing.yml +++ b/.forgejo/workflows/testing.yml @@ -46,7 +46,7 @@ jobs: apt-get update -qq apt-get -q install -qq -y zstd - name: "Cache frontend build for playwright testing" - uses: actions/cache/save@v4 + uses: https://data.forgejo.org/actions/cache/save@v4 with: path: ${{github.workspace}}/public/assets key: frontend-build-${{ github.sha }} @@ -104,7 +104,7 @@ jobs: fetch-depth: 20 - uses: ./.forgejo/workflows-composite/setup-env - name: "Restore frontend build" - uses: actions/cache/restore@v4 + uses: https://data.forgejo.org/actions/cache/restore@v4 id: cache-frontend with: path: ${{github.workspace}}/public/assets diff --git a/CODEOWNERS b/CODEOWNERS index 6ca34a69df..ff2a4b9fdd 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -33,8 +33,9 @@ models/.* @gusted # for code that lives in here. routers/.* @gusted -# Let new strings be checked by the translation team. -options/locale/locale_en-US.ini @0ko +# Let locale changes be checked by the translation team. +options/locale/.* @0ko +options/locale_next/.* @0ko # Personal interest .*/webhook.* @oliverpool diff --git a/Dockerfile b/Dockerfile index 3f7f3e7d1f..2df13c2326 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ -FROM --platform=$BUILDPLATFORM code.forgejo.org/oci/xx AS xx +FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx -FROM --platform=$BUILDPLATFORM code.forgejo.org/oci/golang:1.23-alpine3.21 as build-env +FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.23-alpine3.21 as build-env ARG GOPROXY ENV GOPROXY=${GOPROXY:-direct} @@ -51,7 +51,7 @@ RUN chmod 755 /tmp/local/usr/bin/entrypoint \ /go/src/code.gitea.io/gitea/environment-to-ini RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete -FROM code.forgejo.org/oci/alpine:3.21 +FROM data.forgejo.org/oci/alpine:3.21 ARG RELEASE_VERSION LABEL maintainer="contact@forgejo.org" \ org.opencontainers.image.authors="Forgejo" \ diff --git a/Dockerfile.rootless b/Dockerfile.rootless index 129bd6f1ba..8aec4d828f 100644 --- a/Dockerfile.rootless +++ b/Dockerfile.rootless @@ -1,6 +1,6 @@ -FROM --platform=$BUILDPLATFORM code.forgejo.org/oci/xx AS xx +FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/xx AS xx -FROM --platform=$BUILDPLATFORM code.forgejo.org/oci/golang:1.23-alpine3.21 as build-env +FROM --platform=$BUILDPLATFORM data.forgejo.org/oci/golang:1.23-alpine3.21 as build-env ARG GOPROXY ENV GOPROXY=${GOPROXY:-direct} @@ -49,7 +49,7 @@ RUN chmod 755 /tmp/local/usr/local/bin/docker-entrypoint.sh \ /go/src/code.gitea.io/gitea/environment-to-ini RUN chmod 644 /go/src/code.gitea.io/gitea/contrib/autocompletion/bash_autocomplete -FROM code.forgejo.org/oci/alpine:3.21 +FROM data.forgejo.org/oci/alpine:3.21 LABEL maintainer="contact@forgejo.org" \ org.opencontainers.image.authors="Forgejo" \ org.opencontainers.image.url="https://forgejo.org" \ diff --git a/Makefile b/Makefile index 5865262d1a..151e752545 100644 --- a/Makefile +++ b/Makefile @@ -37,7 +37,7 @@ endif XGO_VERSION := go-1.21.x AIR_PACKAGE ?= github.com/air-verse/air@v1 # renovate: datasource=go -EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.1.1 # renovate: datasource=go +EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.2.0 # renovate: datasource=go GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@v0.7.0 # renovate: datasource=go GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@v1.62.2 # renovate: datasource=go GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.11 # renovate: datasource=go @@ -49,7 +49,7 @@ GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasour DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.29.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.17.1 # renovate: datasource=go -RENOVATE_NPM_PACKAGE ?= renovate@39.115.4 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate +RENOVATE_NPM_PACKAGE ?= renovate@39.136.1 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate # https://github.com/disposable-email-domains/disposable-email-domains/commits/main/ DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ... diff --git a/cmd/web.go b/cmd/web.go index 3fc64f7748..787411939c 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -195,7 +195,7 @@ func serveInstalled(ctx *cli.Context) error { publicFilesSet.Remove(".well-known") publicFilesSet.Remove("assets") publicFilesSet.Remove("robots.txt") - for _, fn := range publicFilesSet.Values() { + for fn := range publicFilesSet.Seq() { log.Error("Found legacy public asset %q in CustomPath. Please move it to %s/public/assets/%s", fn, setting.CustomPath, fn) } if _, err := os.Stat(filepath.Join(setting.CustomPath, "robots.txt")); err == nil { diff --git a/models/asymkey/error.go b/models/asymkey/error.go index 03bc82302f..e38ba121c6 100644 --- a/models/asymkey/error.go +++ b/models/asymkey/error.go @@ -192,28 +192,6 @@ func (err ErrGPGKeyIDAlreadyUsed) Unwrap() error { return util.ErrAlreadyExist } -// ErrGPGKeyAccessDenied represents a "GPGKeyAccessDenied" kind of Error. -type ErrGPGKeyAccessDenied struct { - UserID int64 - KeyID int64 -} - -// IsErrGPGKeyAccessDenied checks if an error is a ErrGPGKeyAccessDenied. -func IsErrGPGKeyAccessDenied(err error) bool { - _, ok := err.(ErrGPGKeyAccessDenied) - return ok -} - -// Error pretty-prints an error of type ErrGPGKeyAccessDenied. -func (err ErrGPGKeyAccessDenied) Error() string { - return fmt.Sprintf("user does not have access to the key [user_id: %d, key_id: %d]", - err.UserID, err.KeyID) -} - -func (err ErrGPGKeyAccessDenied) Unwrap() error { - return util.ErrPermissionDenied -} - // ErrKeyAccessDenied represents a "KeyAccessDenied" kind of error. type ErrKeyAccessDenied struct { UserID int64 diff --git a/models/asymkey/ssh_key_deploy.go b/models/asymkey/ssh_key_deploy.go index 923c5020ed..429635330d 100644 --- a/models/asymkey/ssh_key_deploy.go +++ b/models/asymkey/ssh_key_deploy.go @@ -105,14 +105,6 @@ func addDeployKey(ctx context.Context, keyID, repoID int64, name, fingerprint st return key, db.Insert(ctx, key) } -// HasDeployKey returns true if public key is a deploy key of given repository. -func HasDeployKey(ctx context.Context, keyID, repoID int64) bool { - has, _ := db.GetEngine(ctx). - Where("key_id = ? AND repo_id = ?", keyID, repoID). - Get(new(DeployKey)) - return has -} - // AddDeployKey add new deploy key to database and authorized_keys file. func AddDeployKey(ctx context.Context, repoID int64, name, content string, readOnly bool) (*DeployKey, error) { fingerprint, err := CalcFingerprint(content) diff --git a/models/issues/issue.go b/models/issues/issue.go index 1e969790d7..6bd91797e7 100644 --- a/models/issues/issue.go +++ b/models/issues/issue.go @@ -63,21 +63,6 @@ func (err ErrIssueIsClosed) Error() string { return fmt.Sprintf("issue is closed [id: %d, repo_id: %d, index: %d]", err.ID, err.RepoID, err.Index) } -// ErrNewIssueInsert is used when the INSERT statement in newIssue fails -type ErrNewIssueInsert struct { - OriginalError error -} - -// IsErrNewIssueInsert checks if an error is a ErrNewIssueInsert. -func IsErrNewIssueInsert(err error) bool { - _, ok := err.(ErrNewIssueInsert) - return ok -} - -func (err ErrNewIssueInsert) Error() string { - return err.OriginalError.Error() -} - // ErrIssueWasClosed is used when close a closed issue type ErrIssueWasClosed struct { ID int64 diff --git a/models/issues/issue_update.go b/models/issues/issue_update.go index 31c8bdc17b..775ebfadad 100644 --- a/models/issues/issue_update.go +++ b/models/issues/issue_update.go @@ -433,7 +433,7 @@ func NewIssue(ctx context.Context, repo *repo_model.Repository, issue *Issue, la LabelIDs: labelIDs, Attachments: uuids, }); err != nil { - if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) { + if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) { return err } return fmt.Errorf("newIssue: %w", err) diff --git a/models/issues/milestone.go b/models/issues/milestone.go index 4b3cb0e858..03fab8b3ee 100644 --- a/models/issues/milestone.go +++ b/models/issues/milestone.go @@ -251,21 +251,6 @@ func ChangeMilestoneStatusByRepoIDAndID(ctx context.Context, repoID, milestoneID return committer.Commit() } -// ChangeMilestoneStatus changes the milestone open/closed status. -func ChangeMilestoneStatus(ctx context.Context, m *Milestone, isClosed bool) (err error) { - ctx, committer, err := db.TxContext(ctx) - if err != nil { - return err - } - defer committer.Close() - - if err := changeMilestoneStatus(ctx, m, isClosed); err != nil { - return err - } - - return committer.Commit() -} - func changeMilestoneStatus(ctx context.Context, m *Milestone, isClosed bool) error { m.IsClosed = isClosed if isClosed { diff --git a/models/issues/milestone_test.go b/models/issues/milestone_test.go index 314cba308c..c83450e9db 100644 --- a/models/issues/milestone_test.go +++ b/models/issues/milestone_test.go @@ -298,17 +298,16 @@ func TestNewMilestone(t *testing.T) { unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: milestone.RepoID}, &issues_model.Milestone{}) } -func TestChangeMilestoneStatus(t *testing.T) { +func TestChangeMilestoneStatusByRepoIDAndID(t *testing.T) { require.NoError(t, unittest.PrepareTestDatabase()) - milestone := unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}) - require.NoError(t, issues_model.ChangeMilestoneStatus(db.DefaultContext, milestone, true)) - unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}, "is_closed=1") - unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: milestone.RepoID}, &issues_model.Milestone{}) + require.NoError(t, issues_model.ChangeMilestoneStatusByRepoIDAndID(db.DefaultContext, 1, 1, true)) + unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1, IsClosed: true}) + unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: 1}, &issues_model.Milestone{}) - require.NoError(t, issues_model.ChangeMilestoneStatus(db.DefaultContext, milestone, false)) + require.NoError(t, issues_model.ChangeMilestoneStatusByRepoIDAndID(db.DefaultContext, 1, 1, false)) unittest.AssertExistsAndLoadBean(t, &issues_model.Milestone{ID: 1}, "is_closed=0") - unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: milestone.RepoID}, &issues_model.Milestone{}) + unittest.CheckConsistencyFor(t, &repo_model.Repository{ID: 1}, &issues_model.Milestone{}) } func TestDeleteMilestoneByRepoID(t *testing.T) { diff --git a/models/issues/pull.go b/models/issues/pull.go index 13eafccdc7..708dff9b8e 100644 --- a/models/issues/pull.go +++ b/models/issues/pull.go @@ -575,7 +575,7 @@ func NewPullRequest(ctx context.Context, repo *repo_model.Repository, issue *Iss Attachments: uuids, IsPull: true, }); err != nil { - if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) || IsErrNewIssueInsert(err) { + if repo_model.IsErrUserDoesNotHaveAccessToRepo(err) { return err } return fmt.Errorf("newIssue: %w", err) diff --git a/models/repo/language_stats.go b/models/repo/language_stats.go index 0bc0f1fb40..d44fea5375 100644 --- a/models/repo/language_stats.go +++ b/models/repo/language_stats.go @@ -4,9 +4,10 @@ package repo import ( + "cmp" "context" "math" - "sort" + "slices" "strings" "code.gitea.io/gitea/models/db" @@ -67,34 +68,37 @@ func (stats LanguageStatList) getLanguagePercentages() map[string]float32 { return langPerc } -// Rounds to 1 decimal point, target should be the expected sum of percs +// Use the quota method to round the percentages to one decimal place while +// keeping the sum of the percentages at 100%. func roundByLargestRemainder(percs map[string]float32, target float32) { + // Tracks the difference between the sum of percentage and 100%. leftToDistribute := int(target * 10) - keys := make([]string, 0, len(percs)) + type key struct { + language string + remainder float64 + } + keys := make([]key, 0, len(percs)) for k, v := range percs { - percs[k] = v * 10 - floored := math.Floor(float64(percs[k])) + floored, frac := math.Modf(float64(v * 10)) + percs[k] = float32(floored) leftToDistribute -= int(floored) - keys = append(keys, k) + keys = append(keys, key{language: k, remainder: frac}) } - // Sort the keys by the largest remainder - sort.SliceStable(keys, func(i, j int) bool { - _, remainderI := math.Modf(float64(percs[keys[i]])) - _, remainderJ := math.Modf(float64(percs[keys[j]])) - return remainderI > remainderJ + // Sort the fractional part in an ascending order. + slices.SortFunc(keys, func(b, a key) int { + return cmp.Compare(a.remainder, b.remainder) }) - // Increment the values in order of largest remainder + // As long as the sum of 100% is not reached, add 0.1% percentage. for _, k := range keys { - percs[k] = float32(math.Floor(float64(percs[k]))) if leftToDistribute > 0 { - percs[k]++ + percs[k.language]++ leftToDistribute-- } - percs[k] /= 10 + percs[k.language] /= 10 } } diff --git a/models/repo/language_stats_test.go b/models/repo/language_stats_test.go new file mode 100644 index 0000000000..dcfaeee6c9 --- /dev/null +++ b/models/repo/language_stats_test.go @@ -0,0 +1,66 @@ +// Copyright 2025 The Forgejo Authors. All rights reserved. +// SPDX-License-Identifier: GPL-3.0-or-later + +package repo + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLanguagePercentages(t *testing.T) { + testCases := []struct { + input LanguageStatList + output map[string]float32 + }{ + { + []*LanguageStat{{Language: "Go", Size: 500}, {Language: "Rust", Size: 501}}, + map[string]float32{ + "Go": 50.0, + "Rust": 50.0, + }, + }, + { + []*LanguageStat{{Language: "Go", Size: 10}, {Language: "Rust", Size: 91}}, + map[string]float32{ + "Go": 9.9, + "Rust": 90.1, + }, + }, + { + []*LanguageStat{{Language: "Go", Size: 1}, {Language: "Rust", Size: 2}}, + map[string]float32{ + "Go": 33.3, + "Rust": 66.7, + }, + }, + { + []*LanguageStat{{Language: "Go", Size: 1}, {Language: "Rust", Size: 2}, {Language: "Shell", Size: 3}, {Language: "C#", Size: 4}, {Language: "Zig", Size: 5}, {Language: "Coq", Size: 6}, {Language: "Haskell", Size: 7}}, + map[string]float32{ + "Go": 3.6, + "Rust": 7.1, + "Shell": 10.7, + "C#": 14.3, + "Zig": 17.9, + "Coq": 21.4, + "Haskell": 25, + }, + }, + { + []*LanguageStat{{Language: "Go", Size: 1000}, {Language: "PHP", Size: 1}, {Language: "Java", Size: 1}}, + map[string]float32{ + "Go": 99.8, + "other": 0.2, + }, + }, + { + []*LanguageStat{}, + map[string]float32{}, + }, + } + + for _, testCase := range testCases { + assert.Equal(t, testCase.output, testCase.input.getLanguagePercentages()) + } +} diff --git a/models/repo/repo_list.go b/models/repo/repo_list.go index fc51f64f6a..693f8f12af 100644 --- a/models/repo/repo_list.go +++ b/models/repo/repo_list.go @@ -36,18 +36,6 @@ const RepositoryListDefaultPageSize = 64 // RepositoryList contains a list of repositories type RepositoryList []*Repository -func (repos RepositoryList) Len() int { - return len(repos) -} - -func (repos RepositoryList) Less(i, j int) bool { - return repos[i].FullName() < repos[j].FullName() -} - -func (repos RepositoryList) Swap(i, j int) { - repos[i], repos[j] = repos[j], repos[i] -} - // ValuesRepository converts a repository map to a list // FIXME: Remove in favor of maps.values when MIN_GO_VERSION >= 1.18 func ValuesRepository(m map[int64]*Repository) []*Repository { diff --git a/models/user/error.go b/models/user/error.go index cbf19998d1..5a956a2afe 100644 --- a/models/user/error.go +++ b/models/user/error.go @@ -71,27 +71,6 @@ func (err ErrUserProhibitLogin) Unwrap() error { return util.ErrPermissionDenied } -// ErrUserInactive represents a "ErrUserInactive" kind of error. -type ErrUserInactive struct { - UID int64 - Name string -} - -// IsErrUserInactive checks if an error is a ErrUserInactive -func IsErrUserInactive(err error) bool { - _, ok := err.(ErrUserInactive) - return ok -} - -func (err ErrUserInactive) Error() string { - return fmt.Sprintf("user is inactive [uid: %d, name: %s]", err.UID, err.Name) -} - -// Unwrap unwraps this error as a ErrPermission error -func (err ErrUserInactive) Unwrap() error { - return util.ErrPermissionDenied -} - // ErrUserIsNotLocal represents a "ErrUserIsNotLocal" kind of error. type ErrUserIsNotLocal struct { UID int64 diff --git a/models/user/search.go b/models/user/search.go index cb90ca850e..ecc95caf3d 100644 --- a/models/user/search.go +++ b/models/user/search.go @@ -40,6 +40,7 @@ type SearchUserOptions struct { IsProhibitLogin optional.Option[bool] IncludeReserved bool + Load2FAStatus bool ExtraParamStrings map[string]string } @@ -126,17 +127,15 @@ func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Sess return e.Where(cond) } - // 2fa filter uses LEFT JOIN to check whether a user has a 2fa record - // While using LEFT JOIN, sometimes the performance might not be good, but it won't be a problem now, such SQL is seldom executed. - // There are some possible methods to refactor this SQL in future when we really need to optimize the performance (but not now): - // (1) add a column in user table (2) add a setting value in user_setting table (3) use search engines (bleve/elasticsearch) + // Check if the user has two factor enabled, which is TOTP or Webauthn. if opts.IsTwoFactorEnabled.Value() { - cond = cond.And(builder.Expr("two_factor.uid IS NOT NULL")) + cond = cond.And(builder.Expr("two_factor.uid IS NOT NULL OR webauthn_credential.user_id IS NOT NULL")) } else { - cond = cond.And(builder.Expr("two_factor.uid IS NULL")) + cond = cond.And(builder.Expr("two_factor.uid IS NULL AND webauthn_credential.user_id IS NULL")) } return e.Join("LEFT OUTER", "two_factor", "two_factor.uid = `user`.id"). + Join("LEFT OUTER", "webauthn_credential", "webauthn_credential.user_id = `user`.id"). Where(cond) } diff --git a/models/user/user.go b/models/user/user.go index 423a26c8d3..f986ac5482 100644 --- a/models/user/user.go +++ b/models/user/user.go @@ -1043,22 +1043,6 @@ func GetUserByName(ctx context.Context, name string) (*User, error) { return u, nil } -// GetUserEmailsByNames returns a list of e-mails corresponds to names of users -// that have their email notifications set to enabled or onmention. -func GetUserEmailsByNames(ctx context.Context, names []string) []string { - mails := make([]string, 0, len(names)) - for _, name := range names { - u, err := GetUserByName(ctx, name) - if err != nil { - continue - } - if u.IsMailable() && u.EmailNotificationsPreference != EmailNotificationsDisabled { - mails = append(mails, u.Email) - } - } - return mails -} - // GetMaileableUsersByIDs gets users from ids, but only if they can receive mails func GetMaileableUsersByIDs(ctx context.Context, ids []int64, isMention bool) ([]*User, error) { if len(ids) == 0 { @@ -1085,17 +1069,6 @@ func GetMaileableUsersByIDs(ctx context.Context, ids []int64, isMention bool) ([ Find(&ous) } -// GetUserNamesByIDs returns usernames for all resolved users from a list of Ids. -func GetUserNamesByIDs(ctx context.Context, ids []int64) ([]string, error) { - unames := make([]string, 0, len(ids)) - err := db.GetEngine(ctx).In("id", ids). - Table("user"). - Asc("name"). - Cols("name"). - Find(&unames) - return unames, err -} - // GetUserNameByID returns username for the id func GetUserNameByID(ctx context.Context, id int64) (string, error) { var name string diff --git a/models/user/user_test.go b/models/user/user_test.go index df0c3856e9..263c38933a 100644 --- a/models/user/user_test.go +++ b/models/user/user_test.go @@ -102,16 +102,6 @@ func TestGetUserByName(t *testing.T) { } } -func TestGetUserEmailsByNames(t *testing.T) { - require.NoError(t, unittest.PrepareTestDatabase()) - - // ignore none active user email - assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user9"})) - assert.ElementsMatch(t, []string{"user8@example.com", "user5@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "user5"})) - - assert.ElementsMatch(t, []string{"user8@example.com"}, user_model.GetUserEmailsByNames(db.DefaultContext, []string{"user8", "org7"})) -} - func TestCanCreateOrganization(t *testing.T) { require.NoError(t, unittest.PrepareTestDatabase()) @@ -222,7 +212,7 @@ func TestSearchUsers(t *testing.T) { []int64{1041, 37}) testUserSuccess(&user_model.SearchUserOptions{ListOptions: db.ListOptions{Page: 1}, IsTwoFactorEnabled: optional.Some(true)}, - []int64{24}) + []int64{24, 32}) } func TestEmailNotificationPreferences(t *testing.T) { @@ -715,7 +705,7 @@ func TestDisabledUserFeatures(t *testing.T) { // no features should be disabled with a plain login type assert.LessOrEqual(t, user.LoginType, auth.Plain) assert.Empty(t, user_model.DisabledFeaturesWithLoginType(user).Values()) - for _, f := range testValues.Values() { + for f := range testValues.Seq() { assert.False(t, user_model.IsFeatureDisabledWithLoginType(user, f)) } @@ -724,7 +714,7 @@ func TestDisabledUserFeatures(t *testing.T) { // all features should be disabled assert.NotEmpty(t, user_model.DisabledFeaturesWithLoginType(user).Values()) - for _, f := range testValues.Values() { + for f := range testValues.Seq() { assert.True(t, user_model.IsFeatureDisabledWithLoginType(user, f)) } } diff --git a/modules/assetfs/layered.go b/modules/assetfs/layered.go index 9678d23ad6..9feabc3f8c 100644 --- a/modules/assetfs/layered.go +++ b/modules/assetfs/layered.go @@ -11,7 +11,7 @@ import ( "net/http" "os" "path/filepath" - "sort" + "slices" "time" "code.gitea.io/gitea/modules/container" @@ -143,8 +143,7 @@ func (l *LayeredFS) ListFiles(name string, fileMode ...bool) ([]string, error) { } } } - files := fileSet.Values() - sort.Strings(files) + files := slices.Sorted(fileSet.Seq()) return files, nil } @@ -184,8 +183,7 @@ func listAllFiles(layers []*Layer, name string, fileMode ...bool) ([]string, err if err := list(name); err != nil { return nil, err } - files := fileSet.Values() - sort.Strings(files) + files := slices.Sorted(fileSet.Seq()) return files, nil } diff --git a/modules/container/set.go b/modules/container/set.go index 2d654d0aee..70f837bc66 100644 --- a/modules/container/set.go +++ b/modules/container/set.go @@ -3,6 +3,11 @@ package container +import ( + "iter" + "maps" +) + type Set[T comparable] map[T]struct{} // SetOf creates a set and adds the specified elements to it. @@ -63,3 +68,9 @@ func (s Set[T]) Values() []T { } return keys } + +// Seq returns a iterator over the elements in the set. +// It returns a single-use iterator. +func (s Set[T]) Seq() iter.Seq[T] { + return maps.Keys(s) +} diff --git a/modules/container/set_test.go b/modules/container/set_test.go index 3cfbf7cc2c..e54e31a052 100644 --- a/modules/container/set_test.go +++ b/modules/container/set_test.go @@ -4,6 +4,7 @@ package container import ( + "slices" "testing" "github.com/stretchr/testify/assert" @@ -29,6 +30,14 @@ func TestSet(t *testing.T) { assert.True(t, s.Contains("key4")) assert.True(t, s.Contains("key5")) + values := s.Values() + called := 0 + for value := range s.Seq() { + called++ + assert.True(t, slices.Contains(values, value)) + } + assert.EqualValues(t, len(values), called) + s = SetOf("key6", "key7") assert.False(t, s.Contains("key1")) assert.True(t, s.Contains("key6")) diff --git a/modules/git/diff.go b/modules/git/diff.go index d9f3f6dda9..8374101d2a 100644 --- a/modules/git/diff.go +++ b/modules/git/diff.go @@ -64,7 +64,10 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff } else if commit.ParentCount() == 0 { cmd.AddArguments("show").AddDynamicArguments(endCommit).AddDashesAndList(files...) } else { - c, _ := commit.Parent(0) + c, err := commit.Parent(0) + if err != nil { + return err + } cmd.AddArguments("diff", "-M").AddDynamicArguments(c.ID.String(), endCommit).AddDashesAndList(files...) } case RawDiffPatch: @@ -74,7 +77,10 @@ func GetRepoRawDiffForFile(repo *Repository, startCommit, endCommit string, diff } else if commit.ParentCount() == 0 { cmd.AddArguments("format-patch", "--no-signature", "--stdout", "--root").AddDynamicArguments(endCommit).AddDashesAndList(files...) } else { - c, _ := commit.Parent(0) + c, err := commit.Parent(0) + if err != nil { + return err + } query := fmt.Sprintf("%s...%s", endCommit, c.ID.String()) cmd.AddArguments("format-patch", "--no-signature", "--stdout").AddDynamicArguments(query).AddDashesAndList(files...) } diff --git a/modules/git/ref_test.go b/modules/git/ref_test.go index 58f679b7d6..1fd33b5163 100644 --- a/modules/git/ref_test.go +++ b/modules/git/ref_test.go @@ -20,6 +20,8 @@ func TestRefName(t *testing.T) { // Test pull names assert.Equal(t, "1", RefName("refs/pull/1/head").PullName()) + assert.True(t, RefName("refs/pull/1/head").IsPull()) + assert.True(t, RefName("refs/pull/1/merge").IsPull()) assert.Equal(t, "my/pull", RefName("refs/pull/my/pull/head").PullName()) // Test for branch names diff --git a/modules/git/repo_tag.go b/modules/git/repo_tag.go index 12b0c022cb..3b48b1fb9b 100644 --- a/modules/git/repo_tag.go +++ b/modules/git/repo_tag.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "io" + "slices" "strings" "code.gitea.io/gitea/modules/git/foreachref" @@ -153,7 +154,9 @@ func (repo *Repository) GetTagInfos(page, pageSize int) ([]*Tag, int, error) { return nil, 0, fmt.Errorf("GetTagInfos: parse output: %w", err) } - sortTagsByTime(tags) + slices.SortFunc(tags, func(b, a *Tag) int { + return a.Tagger.When.Compare(b.Tagger.When) + }) tagsTotal := len(tags) if page != 0 { tags = util.PaginateSlice(tags, page, pageSize).([]*Tag) diff --git a/modules/git/repo_tag_test.go b/modules/git/repo_tag_test.go index 1cf420ad63..a4b13bf03d 100644 --- a/modules/git/repo_tag_test.go +++ b/modules/git/repo_tag_test.go @@ -6,6 +6,7 @@ package git import ( "path/filepath" "testing" + "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -30,9 +31,11 @@ func TestRepository_GetTags(t *testing.T) { assert.EqualValues(t, "signed-tag", tags[0].Name) assert.EqualValues(t, "36f97d9a96457e2bab511db30fe2db03893ebc64", tags[0].ID.String()) assert.EqualValues(t, "tag", tags[0].Type) + assert.EqualValues(t, time.Date(2022, time.November, 13, 16, 40, 20, 0, time.FixedZone("", 3600)), tags[0].Tagger.When) assert.EqualValues(t, "test", tags[1].Name) assert.EqualValues(t, "3ad28a9149a2864384548f3d17ed7f38014c9e8a", tags[1].ID.String()) assert.EqualValues(t, "tag", tags[1].Type) + assert.EqualValues(t, time.Date(2018, time.June, 16, 20, 13, 18, 0, time.FixedZone("", -25200)), tags[1].Tagger.When) } func TestRepository_GetTag(t *testing.T) { diff --git a/modules/git/tag.go b/modules/git/tag.go index 04f50e8db8..34ce8f6fc3 100644 --- a/modules/git/tag.go +++ b/modules/git/tag.go @@ -5,7 +5,6 @@ package git import ( "bytes" - "sort" "strings" api "code.gitea.io/gitea/modules/structs" @@ -107,23 +106,3 @@ l: return tag, nil } - -type tagSorter []*Tag - -func (ts tagSorter) Len() int { - return len([]*Tag(ts)) -} - -func (ts tagSorter) Less(i, j int) bool { - return []*Tag(ts)[i].Tagger.When.After([]*Tag(ts)[j].Tagger.When) -} - -func (ts tagSorter) Swap(i, j int) { - []*Tag(ts)[i], []*Tag(ts)[j] = []*Tag(ts)[j], []*Tag(ts)[i] -} - -// sortTagsByTime -func sortTagsByTime(tags []*Tag) { - sorter := tagSorter(tags) - sort.Sort(sorter) -} diff --git a/modules/markup/file_preview.go b/modules/markup/file_preview.go index 49a5f1e8ba..2171f6097b 100644 --- a/modules/markup/file_preview.go +++ b/modules/markup/file_preview.go @@ -8,6 +8,7 @@ import ( "bytes" "html/template" "io" + "net/url" "regexp" "slices" "strconv" @@ -77,6 +78,16 @@ func newFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca commitSha := node.Data[m[4]:m[5]] filePath := node.Data[m[6]:m[7]] + urlFullSource := urlFull + if strings.HasSuffix(filePath, "?display=source") { + filePath = strings.TrimSuffix(filePath, "?display=source") + } else if Type(filePath) != "" { + urlFullSource = node.Data[m[0]:m[6]] + filePath + "?display=source#" + node.Data[m[8]:m[1]] + } + filePath, err := url.QueryUnescape(filePath) + if err != nil { + return nil + } hash := node.Data[m[8]:m[9]] preview.start = m[0] @@ -113,7 +124,7 @@ func newFilePreview(ctx *RenderContext, node *html.Node, locale translation.Loca titleBuffer.WriteString(" – ") } - err = html.Render(titleBuffer, createLink(urlFull, filePath, "muted")) + err = html.Render(titleBuffer, createLink(urlFullSource, filePath, "muted")) if err != nil { log.Error("failed to render filepathLink: %v", err) } diff --git a/modules/markup/html.go b/modules/markup/html.go index 3355c021ce..ca9857d2bf 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -759,9 +759,6 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) { title = path.Base(name) } alt := props["alt"] - if alt == "" { - alt = name - } // make the childNode an image - if we can, we also place the alt childNode.Type = html.ElementNode @@ -772,9 +769,6 @@ func shortLinkProcessor(ctx *RenderContext, node *html.Node) { {Key: "title", Val: title}, {Key: "alt", Val: alt}, } - if alt == "" { - childNode.Attr = childNode.Attr[:2] - } } else { if !absoluteLink { if ctx.IsWiki { diff --git a/modules/markup/html_test.go b/modules/markup/html_test.go index 50ea70905c..6c00c5e7c8 100644 --- a/modules/markup/html_test.go +++ b/modules/markup/html_test.go @@ -416,7 +416,7 @@ func TestRender_ShortLinks(t *testing.T) { otherImgurlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw", "Link+Other.jpg") encodedImgurlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw", "Link+%23.jpg") notencodedImgurlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw", "some", "path", "Link+#.jpg") - favicon := "http://google.com/favicon.ico" + favicon := "https://forgejo.org/favicon.ico" test( "[[Link]]", @@ -424,28 +424,28 @@ func TestRender_ShortLinks(t *testing.T) { `

Link

`) test( "[[Link.jpg]]", - `

Link.jpg

`, - `

Link.jpg

`) + `

`, + `

`) test( "[["+favicon+"]]", - `

`+favicon+`

`, - `

`+favicon+`

`) + `

`, + `

`) test( "[[Name|Link]]", `

Name

`, `

Name

`) test( "[[Name|Link.jpg]]", - `

Name

`, - `

Name

`) + `

`, + `

`) test( "[[Name|Link.jpg|alt=AltName]]", `

AltName

`, `

AltName

`) test( "[[Name|Link.jpg|title=Title]]", - `

Title

`, - `

Title

`) + `

`, + `

`) test( "[[Name|Link.jpg|alt=AltName|title=Title]]", `

AltName

`, @@ -472,16 +472,16 @@ func TestRender_ShortLinks(t *testing.T) { `

Link Other Link Link?

`) test( "[[Link #.jpg]]", - `

Link #.jpg

`, - `

Link #.jpg

`) + `

`, + `

`) test( "[[Name|Link #.jpg|alt=\"AltName\"|title='Title']]", `

AltName

`, `

AltName

`) test( "[[some/path/Link #.jpg]]", - `

some/path/Link #.jpg

`, - `

some/path/Link #.jpg

`) + `

`, + `

`) test( "

[[foobar]]

", `

[[foobar]]

`, @@ -1026,4 +1026,138 @@ func TestRender_FilePreview(t *testing.T) { localMetas, ) }) + + commitFileURL := util.URLJoin(markup.TestRepoURL, "src", "commit", "c9913120ed2c1e27c1d7752ecdb7a504dc7cf6be", "path", "to", "file.md") + + t.Run("rendered file with ?display=source", func(t *testing.T) { + testRender( + commitFileURL+"?display=source"+"#L1-L2", + `

`+ + `
`+ + `
`+ + `
`+ + `path/to/file.md`+ + `
`+ + ``+ + `Lines 1 to 2 in c991312`+ + ``+ + `
`+ + `
`+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + `
# A`+"\n"+`
B`+"\n"+`
`+ + `
`+ + `
`+ + `

`, + localMetas, + ) + }) + + t.Run("rendered file without ?display=source", func(t *testing.T) { + testRender( + commitFileURL+"#L1-L2", + `

`+ + `
`+ + `
`+ + `
`+ + `path/to/file.md`+ + `
`+ + ``+ + `Lines 1 to 2 in c991312`+ + ``+ + `
`+ + `
`+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + `
# A`+"\n"+`
B`+"\n"+`
`+ + `
`+ + `
`+ + `

`, + localMetas, + ) + }) + + commitFileURL = util.URLJoin(markup.TestRepoURL, "src", "commit", "190d9492934af498c3f669d6a2431dc5459e5b20", "path", "to", "file.go") + + t.Run("normal file with ?display=source", func(t *testing.T) { + testRender( + commitFileURL+"?display=source"+"#L2-L3", + `

`+ + `
`+ + `
`+ + `
`+ + `path/to/file.go`+ + `
`+ + ``+ + `Lines 2 to 3 in 190d949`+ + ``+ + `
`+ + `
`+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + `
B`+"\n"+`
C`+"\n"+`
`+ + `
`+ + `
`+ + `

`, + localMetas, + ) + }) + + commitFileURL = util.URLJoin(markup.TestRepoURL, "src", "commit", "eeb243c3395e1921c5d90e73bd739827251fc99d", "path", "to", "file%20%23.txt") + + t.Run("file with strange characters in name", func(t *testing.T) { + testRender( + commitFileURL+"#L1", + `

`+ + `
`+ + `
`+ + `
`+ + `path/to/file #.txt`+ + `
`+ + ``+ + `Line 1 in eeb243c`+ + ``+ + `
`+ + `
`+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + ``+ + `
A`+"\n"+`
`+ + `
`+ + `
`+ + `

`, + localMetas, + ) + }) } diff --git a/modules/markup/markdown/markdown_test.go b/modules/markup/markdown/markdown_test.go index 4c9ab42806..e64ab6a6f4 100644 --- a/modules/markup/markdown/markdown_test.go +++ b/modules/markup/markdown/markdown_test.go @@ -108,7 +108,7 @@ func TestRender_Images(t *testing.T) { test( "[["+title+"|"+url+"]]", - `

`+title+`

`) + `

`) test( "[!["+title+"]("+url+")]("+href+")", `

`+title+`

`) @@ -119,7 +119,7 @@ func TestRender_Images(t *testing.T) { test( "[["+title+"|"+url+"]]", - `

`+title+`

`) + `

`) test( "[!["+title+"]("+url+")]("+href+")", `

`+title+`

`) @@ -149,13 +149,13 @@ func testAnswers(baseURLContent, baseURLImages string) []string { - + - + @@ -164,9 +164,9 @@ func testAnswers(baseURLContent, baseURLImages string) []string { `

Excelsior JET allows you to create native executables for Windows, Linux and Mac OS X.

  1. Package your libGDX application
    -images/1.png
  2. +
  3. Perform a test run by hitting the Run! button.
    -images/2.png
  4. +

More tests

(from https://www.markdownguide.org/extended-syntax/)

@@ -849,8 +849,8 @@ mail@domain.com local image
local image
remote image
-local image
-remote link
+
+
https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
@@ -876,8 +876,8 @@ space

local image
local image
remote image
-local image
-remote link
+
+
https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
@@ -905,8 +905,8 @@ space

local image
local image
remote image
-local image
-remote link
+
+
https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
@@ -934,8 +934,8 @@ space

local image
local image
remote image
-local image
-remote link
+
+
https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
@@ -963,8 +963,8 @@ space

local image
local image
remote image
-local image
-remote link
+
+
https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
@@ -992,8 +992,8 @@ space

local image
local image
remote image
-local image
-remote link
+
+
https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
@@ -1022,8 +1022,8 @@ space

local image
local image
remote image
-local image
-remote link
+
+
https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
@@ -1052,8 +1052,8 @@ space

local image
local image
remote image
-local image
-remote link
+
+
https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
@@ -1082,8 +1082,8 @@ space

local image
local image
remote image
-local image
-remote link
+
+
https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
@@ -1112,8 +1112,8 @@ space

local image
local image
remote image
-local image
-remote link
+
+
https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
@@ -1143,8 +1143,8 @@ space

local image
local image
remote image
-local image
-remote link
+
+
https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
@@ -1174,8 +1174,8 @@ space

local image
local image
remote image
-local image
-remote link
+
+
https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/0b/b53b56d70d253ce75c257d3cd6334a41ef2b6c b/modules/markup/tests/repo/repo1_filepreview/objects/0b/b53b56d70d253ce75c257d3cd6334a41ef2b6c new file mode 100644 index 0000000000..1ab268b76c Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/0b/b53b56d70d253ce75c257d3cd6334a41ef2b6c differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/18/9739e1c2a6cdb8ee094ba1ef8a2e40cf65b2ec b/modules/markup/tests/repo/repo1_filepreview/objects/18/9739e1c2a6cdb8ee094ba1ef8a2e40cf65b2ec new file mode 100644 index 0000000000..c8b99f906b Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/18/9739e1c2a6cdb8ee094ba1ef8a2e40cf65b2ec differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/2a/4032b49cff56d6d4921133e087d9dc0341a2c5 b/modules/markup/tests/repo/repo1_filepreview/objects/2a/4032b49cff56d6d4921133e087d9dc0341a2c5 new file mode 100644 index 0000000000..f799e8a988 Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/2a/4032b49cff56d6d4921133e087d9dc0341a2c5 differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/2d/2f8eaa17b17359ee1c73222065575d50d9a157 b/modules/markup/tests/repo/repo1_filepreview/objects/2d/2f8eaa17b17359ee1c73222065575d50d9a157 new file mode 100644 index 0000000000..7f4c451d00 Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/2d/2f8eaa17b17359ee1c73222065575d50d9a157 differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/2f/b9577a8e940a0a84a789cdd4a45d0f172e3fdd b/modules/markup/tests/repo/repo1_filepreview/objects/2f/b9577a8e940a0a84a789cdd4a45d0f172e3fdd new file mode 100644 index 0000000000..fc97712911 Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/2f/b9577a8e940a0a84a789cdd4a45d0f172e3fdd differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/2f/f8eb63aad050c3f20e9cb27090ab7378267ab2 b/modules/markup/tests/repo/repo1_filepreview/objects/2f/f8eb63aad050c3f20e9cb27090ab7378267ab2 new file mode 100644 index 0000000000..e230df1343 Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/2f/f8eb63aad050c3f20e9cb27090ab7378267ab2 differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/35/75ed7948fe86ab56b0a76f796f7995222bec65 b/modules/markup/tests/repo/repo1_filepreview/objects/35/75ed7948fe86ab56b0a76f796f7995222bec65 new file mode 100644 index 0000000000..1493caa3df Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/35/75ed7948fe86ab56b0a76f796f7995222bec65 differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/3c/95f14e5a0ab2c5ba9ee9a47ddc261af4968043 b/modules/markup/tests/repo/repo1_filepreview/objects/3c/95f14e5a0ab2c5ba9ee9a47ddc261af4968043 new file mode 100644 index 0000000000..3e9c0c0d8b Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/3c/95f14e5a0ab2c5ba9ee9a47ddc261af4968043 differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/3e/2a4f1b9a15ffa15ea7ffdc06acd302442b3eca b/modules/markup/tests/repo/repo1_filepreview/objects/3e/2a4f1b9a15ffa15ea7ffdc06acd302442b3eca new file mode 100644 index 0000000000..78189a52f6 --- /dev/null +++ b/modules/markup/tests/repo/repo1_filepreview/objects/3e/2a4f1b9a15ffa15ea7ffdc06acd302442b3eca @@ -0,0 +1 @@ +x•ŽANร0EY๛ณGB;a U=D9€=&–ฺูำr} 7่๊ญำำ๋ๅาŒBœ^ฌดค˜yY8ฯ:AІ X}RืXkฮsญ"๎;u์Fบฎ9x” Œ สEdะ’%อ~**Z฿3\บูvํ๔9ะ™>n8Žfxk๛=[9K”%L>ฎ๔ู๊{ง7รs–;aีv4hXO๛Hทิ“ี†๛๐`Kั \ No newline at end of file diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/72/1f0ce13d83f93d431b849a554a62948b85f573 b/modules/markup/tests/repo/repo1_filepreview/objects/72/1f0ce13d83f93d431b849a554a62948b85f573 new file mode 100644 index 0000000000..d781d4d248 --- /dev/null +++ b/modules/markup/tests/repo/repo1_filepreview/objects/72/1f0ce13d83f93d431b849a554a62948b85f573 @@ -0,0 +1 @@ +x•ŽKŠ1@]็ต$ฟJฅaๆz€JRม@w+ุ้s๕ฎโ๑เๅฒดึร่"@VL&J3%f-ัGDาq2>F็jBOEนห:ภgร\1คœฆ๊ฆ’kภ๊ชEM6Dิ,ลธ\‚โวธ:\6้พOlmศฉญ;ฯญ|ƒ!GไŒE‚ฃ6Zซz๒Yฅฮฒ จmธwู›ยi‘.x-oณ๒"›๚ŒLฬ \ No newline at end of file diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/72/e0a44ea5761c9055995db18019e459576b3b27 b/modules/markup/tests/repo/repo1_filepreview/objects/72/e0a44ea5761c9055995db18019e459576b3b27 new file mode 100644 index 0000000000..7b926dc0d8 Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/72/e0a44ea5761c9055995db18019e459576b3b27 differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/72/e1c77b65c7baa0e848557089148833fb54705e b/modules/markup/tests/repo/repo1_filepreview/objects/72/e1c77b65c7baa0e848557089148833fb54705e new file mode 100644 index 0000000000..0bbca73af2 Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/72/e1c77b65c7baa0e848557089148833fb54705e differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/8a/3b1881b5c4e7dc2be7ee1c0f37f93ffbb5ff77 b/modules/markup/tests/repo/repo1_filepreview/objects/8a/3b1881b5c4e7dc2be7ee1c0f37f93ffbb5ff77 new file mode 100644 index 0000000000..0ea93376dc Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/8a/3b1881b5c4e7dc2be7ee1c0f37f93ffbb5ff77 differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/8b/ccd5176c25898b57da2551e076f769054e0d8e b/modules/markup/tests/repo/repo1_filepreview/objects/8b/ccd5176c25898b57da2551e076f769054e0d8e new file mode 100644 index 0000000000..394a7bb50d Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/8b/ccd5176c25898b57da2551e076f769054e0d8e differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/95/31b649823095acf5d79ab9e4f8b8d86046352f b/modules/markup/tests/repo/repo1_filepreview/objects/95/31b649823095acf5d79ab9e4f8b8d86046352f new file mode 100644 index 0000000000..ab36311f6f Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/95/31b649823095acf5d79ab9e4f8b8d86046352f differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/ac/769ab4baa91060a4c2f828f53e6c3cc2f708f8 b/modules/markup/tests/repo/repo1_filepreview/objects/ac/769ab4baa91060a4c2f828f53e6c3cc2f708f8 new file mode 100644 index 0000000000..59afaebf4a Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/ac/769ab4baa91060a4c2f828f53e6c3cc2f708f8 differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/c5/3110b1957cefc56c4b2d879476ddbe905980bf b/modules/markup/tests/repo/repo1_filepreview/objects/c5/3110b1957cefc56c4b2d879476ddbe905980bf new file mode 100644 index 0000000000..3de089bf6a Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/c5/3110b1957cefc56c4b2d879476ddbe905980bf differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/c9/8762531dd068cd818300a5f5c7dca5da79b510 b/modules/markup/tests/repo/repo1_filepreview/objects/c9/8762531dd068cd818300a5f5c7dca5da79b510 new file mode 100644 index 0000000000..af5b784773 Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/c9/8762531dd068cd818300a5f5c7dca5da79b510 differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/c9/913120ed2c1e27c1d7752ecdb7a504dc7cf6be b/modules/markup/tests/repo/repo1_filepreview/objects/c9/913120ed2c1e27c1d7752ecdb7a504dc7cf6be new file mode 100644 index 0000000000..9fc2b7c312 Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/c9/913120ed2c1e27c1d7752ecdb7a504dc7cf6be differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/e7/99b34ea867a0364d0df33f382562db9ff39084 b/modules/markup/tests/repo/repo1_filepreview/objects/e7/99b34ea867a0364d0df33f382562db9ff39084 new file mode 100644 index 0000000000..ef73ed1791 Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/e7/99b34ea867a0364d0df33f382562db9ff39084 differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/ee/b243c3395e1921c5d90e73bd739827251fc99d b/modules/markup/tests/repo/repo1_filepreview/objects/ee/b243c3395e1921c5d90e73bd739827251fc99d new file mode 100644 index 0000000000..5515b07d4a Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/ee/b243c3395e1921c5d90e73bd739827251fc99d differ diff --git a/modules/markup/tests/repo/repo1_filepreview/objects/f7/0f10e4db19068f79bc43844b49f3eece45c4e8 b/modules/markup/tests/repo/repo1_filepreview/objects/f7/0f10e4db19068f79bc43844b49f3eece45c4e8 new file mode 100644 index 0000000000..2e15b4fb0a Binary files /dev/null and b/modules/markup/tests/repo/repo1_filepreview/objects/f7/0f10e4db19068f79bc43844b49f3eece45c4e8 differ diff --git a/modules/markup/tests/repo/repo1_filepreview/refs/heads/master b/modules/markup/tests/repo/repo1_filepreview/refs/heads/master index df25bf45f0..709cffca17 100644 --- a/modules/markup/tests/repo/repo1_filepreview/refs/heads/master +++ b/modules/markup/tests/repo/repo1_filepreview/refs/heads/master @@ -1 +1 @@ -4c1aaf56bcb9f39dcf65f3f250726850aed13cd6 +eeb243c3395e1921c5d90e73bd739827251fc99d diff --git a/modules/setting/service.go b/modules/setting/service.go index 9807f33352..7a907023c4 100644 --- a/modules/setting/service.go +++ b/modules/setting/service.go @@ -140,6 +140,11 @@ func CompileEmailGlobList(sec ConfigSection, keys ...string) (globs []glob.Glob) return globs } +// LoadServiceSetting loads the service settings +func LoadServiceSetting() { + loadServiceFrom(CfgProvider) +} + func loadServiceFrom(rootCfg ConfigProvider) { sec := rootCfg.Section("service") Service.ActiveCodeLives = sec.Key("ACTIVE_CODE_LIVE_MINUTES").MustInt(180) diff --git a/modules/templates/util_avatar.go b/modules/templates/util_avatar.go index afc1091516..a468361101 100644 --- a/modules/templates/util_avatar.go +++ b/modules/templates/util_avatar.go @@ -34,7 +34,7 @@ func AvatarHTML(src string, size int, class, name string) template.HTML { name = "avatar" } - return template.HTML(``) + return template.HTML(``) } // Avatar renders user avatars. args: user, size (int), class (string) diff --git a/modules/templates/util_render_test.go b/modules/templates/util_render_test.go index 3479d94039..028cd2ba76 100644 --- a/modules/templates/util_render_test.go +++ b/modules/templates/util_render_test.go @@ -194,8 +194,8 @@ func TestRenderMarkdownToHtml(t *testing.T) { remote linklocal imageremote image -local image -remote link + +88fc37a3c0...12fc37a3c0 (hash) com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare 88fc37a3c0 diff --git a/modules/translation/i18n/i18n_test.go b/modules/translation/i18n/i18n_test.go index 41f85931aa..66f5d5c402 100644 --- a/modules/translation/i18n/i18n_test.go +++ b/modules/translation/i18n/i18n_test.go @@ -158,6 +158,7 @@ commits = fallback value for commits found := lang1.HasKey("no-such") assert.False(t, found) + assert.EqualValues(t, "no-such", lang1.TrString("no-such")) require.NoError(t, ls.Close()) } diff --git a/modules/translation/i18n/localestore.go b/modules/translation/i18n/localestore.go index e80b2592ae..a38e967838 100644 --- a/modules/translation/i18n/localestore.go +++ b/modules/translation/i18n/localestore.go @@ -225,9 +225,9 @@ func (l *locale) TrString(trKey string, trArgs ...any) string { format = msg } else { // First fallback: old-style translation - idx, ok := l.store.trKeyToIdxMap[trKey] + idx, foundIndex := l.store.trKeyToIdxMap[trKey] found := false - if ok { + if foundIndex { if msg, ok := l.idxToMsgMap[idx]; ok { format = msg // use the found translation found = true @@ -239,7 +239,7 @@ func (l *locale) TrString(trKey string, trArgs ...any) string { if defaultLang, ok := l.store.localeMap[l.store.defaultLang]; ok { if msg := defaultLang.LookupNewStyleMessage(trKey); msg != "" { format = msg - } else { + } else if foundIndex { // Third fallback: old-style default language if msg, ok := defaultLang.idxToMsgMap[idx]; ok { format = msg diff --git a/modules/util/slice.go b/modules/util/slice.go index 9c878c24be..80c8e62f6f 100644 --- a/modules/util/slice.go +++ b/modules/util/slice.go @@ -4,7 +4,6 @@ package util import ( - "cmp" "slices" "strings" ) @@ -47,13 +46,6 @@ func SliceRemoveAll[T comparable](slice []T, target T) []T { return slices.DeleteFunc(slice, func(t T) bool { return t == target }) } -// Sorted returns the sorted slice -// Note: The parameter is sorted inline. -func Sorted[S ~[]E, E cmp.Ordered](values S) S { - slices.Sort(values) - return values -} - // TODO: Replace with "maps.Values" once available, current it only in golang.org/x/exp/maps but not in standard library func ValuesOfMap[K comparable, V any](m map[K]V) []V { values := make([]V, 0, len(m)) diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index 2c47093cea..f5b6540023 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -680,6 +680,8 @@ Location = Umรญstฤ›nรญ To = Nรกzev vฤ›tve Biography = ลฝivotopis AccessToken = Pล™รญstupovรฝ token +username_claiming_cooldown = Uลพivatelskรฉ jmรฉno nelze zรญskat, protoลพe jeลกtฤ› neskonฤila doba jeho platnosti. Pลฏjde jej zรญskat %[1]s. +email_domain_is_not_allowed = Domรฉna uลพivatelskรฉ e-mailovรฉ adresy %s je v rozporu se seznamem EMAIL_DOMAIN_ALLOWLIST nebo EMAIL_DOMAIN_BLOCKLIST. Ujistฤ›te se, ลพe je vaลกe adresa sprรกvnฤ› nastavena. [user] change_avatar=Zmฤ›nit vรกลก avatarโ€ฆ @@ -804,7 +806,7 @@ old_password=Stรกvajรญcรญ heslo new_password=Novรฉ heslo retype_new_password=Potvrzenรญ novรฉho hesla password_incorrect=Zadanรฉ heslo nenรญ sprรกvnรฉ. -change_password_success=Vaลกe heslo bylo aktualizovรกno. Od teฤ se pล™ihlaลกujte novรฝm heslem. +change_password_success=Vaลกe heslo bylo aktualizovรกno. Od nynฤ›jลกka pouลพรญvejte kpล™ihlรกลกenรญ novรฉ heslo. password_change_disabled=Externฤ› ovฤ›ล™ovanรญ uลพivatelรฉ nemohou aktualizovat svรฉ heslo prostล™ednictvรญm webovรฉho rozhranรญ Forgejo. emails=E-mailovรฉ adresy @@ -1056,6 +1058,8 @@ language.description = Tento jazyk bude uloลพen do vaลกeho รบฤtu a po pล™ihlรก language.localization_project = Pomozte nรกm s pล™ekladem Forgejo do vaลกeho jazyka! Vรญce informacรญ. user_block_yourself = Nemลฏลพete zablokovat sami sebe. pronouns_custom_label = Vlastnรญ zรกjmena +change_username_redirect_prompt.with_cooldown.few = Starรฉ uลพivatelskรฉ jmรฉno bude dostupnรฉ ostatnรญm po %[1]d dnech. Do tรฉ doby budete moci svรฉ starรฉ uลพivatelskรฉ jmรฉno znovu zรญskat. +change_username_redirect_prompt.with_cooldown.one = Starรฉ uลพivatelskรฉ jmรฉno bude dostupnรฉ ostatnรญm po %[1]d dni. Do tรฉ doby budete moci svรฉ starรฉ uลพivatelskรฉ jmรฉno znovu zรญskat. [repo] new_repo_helper=Repozitรกล™ obsahuje vลกechny soubory projektu, vฤetnฤ› historie revizรญ. Uลพ jej hostujete jinde? Migrovat repozitรกล™. @@ -3001,6 +3005,8 @@ teams.invite.by=Pozvรกnรญ od %s teams.invite.description=Pro pล™ipojenรญ k tรฝmu kliknฤ›te na tlaฤรญtko nรญลพe. follow_blocked_user = Tuto organizaci nemลฏลพete sledovat, protoลพe jste v nรญ zablokovรกni. open_dashboard = Otevล™รญt nรกstฤ›nku +settings.change_orgname_redirect_prompt.with_cooldown.one = Starรฉ uลพivatelskรฉ jmรฉno bude dostupnรฉ ostatnรญm po %[1]d dni. Do tรฉ doby budete moci svรฉ starรฉ uลพivatelskรฉ jmรฉno znovu zรญskat. +settings.change_orgname_redirect_prompt.with_cooldown.few = Starรฉ uลพivatelskรฉ jmรฉno bude dostupnรฉ ostatnรญm po %[1]d dnech. Do tรฉ doby budete moci svรฉ starรฉ uลพivatelskรฉ jmรฉno znovu zรญskat. [admin] dashboard=Pล™ehled diff --git a/options/locale/locale_da.ini b/options/locale/locale_da.ini index 2df385460b..a08fcaf8bc 100644 --- a/options/locale/locale_da.ini +++ b/options/locale/locale_da.ini @@ -629,6 +629,8 @@ password_special_one = Mindst รฉt specialtegn (tegnsรฆtning, parenteser, anfรธrs unsupported_login_type = Login typen understรธttes ikke for at slette kontoen. cannot_add_org_to_team = En organisation kan ikke tilfรธjes som et holdmedlem. must_use_public_key = Nรธglen du har angivet er en privat nรธgle. Lad vรฆre med at uploade din private nรธgle nogen steder. Brug din offentlige nรธgle i stedet. +username_claiming_cooldown = Brugernavnet kan ikke gรธres krav pรฅ, fordi dets nedkรธlingsperiode endnu ikke er forbi. Det kan gรธres krav pรฅ %[1]s. +email_domain_is_not_allowed = Domรฆnet for brugerens e-mailadresse %s er i konflikt med EMAIL_DOMAIN_ALLOWLIST eller EMAIL_DOMAIN_BLOCKLIST. Sรธrg for, at du har indstillet e-mailadressen korrekt. [user] change_avatar = Skift din avatarโ€ฆ @@ -782,7 +784,7 @@ uploaded_avatar_is_too_big = Den uploadede filstรธrrelse (%d KiB) overstiger den email_deletion_desc = E-mailadressen og relaterede oplysninger vil blive fjernet fra din konto. Git commits af denne e-mailadresse forbliver uรฆndret. Fortsรฆtte? choose_new_avatar = Vรฆlg ny avatar update_avatar = Opdater avatar -change_password_success = Din adgangskode er blevet opdateret. Log ind med din nye adgangskode fra nu af. +change_password_success = Din adgangskode er blevet opdateret. Fra nu af skal du bruge din nye adgangskode til at logge ind. add_email = Tilfรธj e-mailadresse add_openid = Tilfรธj OpenID URI add_email_confirmation_sent = En bekrรฆftelses-e-mail er blevet sendt til "%s". For at bekrรฆfte din e-mailadresse, tjek venligst din indbakke og fรธlg det medfรธlgende link inden for de nรฆste %s. @@ -972,6 +974,8 @@ delete_with_all_comments = Din konto er yngre end %s. For at undgรฅ spรธgelsesko delete_account_title = Slet brugerkonto user_block_yourself = Du kan ikke blokere dig selv. pronouns_custom_label = Brugerdefinerede stedord +change_username_redirect_prompt.with_cooldown.one = Det gamle brugernavn vil vรฆre tilgรฆngeligt for alle efter en nedkรธlingsperiode pรฅ %[1]d dag, du kan stadig krรฆve det gamle brugernavn tilbage i nedkรธlingsperioden. +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. [repo] rss.must_be_on_branch = Du skal vรฆre pรฅ en gren for at have et RSS-feed. @@ -1761,6 +1765,28 @@ pulls.compare_changes = Ny pull-anmodning issues.summary_card_alt = Oversigtskort over et problem med titlen "%s" i depotet %s pulls.edit.already_changed = Kunne ikke gemme รฆndringer af pull-anmodningen. Det ser ud til, at indholdet allerede er blevet รฆndret af en anden bruger. Opdater siden, og prรธv at redigere igen for at undgรฅ at overskrive deres รฆndringer pulls.sign_in_require = Log ind for at oprette en ny pull-anmodning. +pulls.allow_edits_from_maintainers = Tillad redigeringer fra vedligeholdere +pulls.allow_edits_from_maintainers_desc = Brugere med skriveadgang til basisgrenen kan ogsรฅ trykke til denne gren +pulls.allow_edits_from_maintainers_err = Opdatering mislykkedes +pulls.compare_base = flet ind i +pulls.compare_compare = pull fra +pulls.switch_comparison_type = Skift sammenligningstype +pulls.switch_head_and_base = Skift hoved og base +pulls.filter_branch = Filter gren +pulls.compare_changes_desc = Vรฆlg den gren, der skal flettes ind i, og den gren, der skal trรฆkkes fra. +pulls.has_viewed_file = Set +pulls.has_changed_since_last_review = ร†ndret siden din sidste gennemgang +pulls.no_results = Ingen resultater fundet. +pulls.show_all_commits = Vis alle commits +pulls.show_changes_since_your_last_review = Vis รฆndringer siden din sidste gennemgang +pulls.showing_only_single_commit = Viser kun รฆndringer af commit %[1]s +pulls.showing_specified_commit_range = Viser kun รฆndringer mellem %[1]s..%[2]s +pulls.select_commit_hold_shift_for_range = Vรฆlg commit. Hold Shift + klik for at vรฆlge et omrรฅde +pulls.review_only_possible_for_full_diff = Gennemgang er kun mulig, nรฅr du ser den fulde diff +pulls.filter_changes_by_commit = Filtrer efter commit +pulls.expand_files = Udvid alle filer +pulls.collapse_files = Skjul alle filer +pulls.viewed_files_label = %[1]d / %[2]d filer set [notification] watching = Overvรฅger diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index f5e0d314ae..28e0016348 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -681,6 +681,8 @@ Website = Webseite Location = Ort To = Branchname AccessToken = Zugangstoken +username_claiming_cooldown = Der Benutzername kann nicht beansprucht werden, weil seine Schutzzeit noch nicht vorbei ist. Er kann am %[1]s beansprucht werden. +email_domain_is_not_allowed = Die Domain der E-Mail-Adresse des Benutzers %s steht in Konflikt mit EMAIL_DOMAIN_ALLOWLIST oder EMAIL_DOMAIN_BLOCKLIST. Bitte stelle sicher, dass du die E-Mail-Adresse richtig gesetzt hast. [user] @@ -806,7 +808,7 @@ old_password=Aktuelles Passwort new_password=Neues Passwort retype_new_password=Neues Passwort bestรคtigen password_incorrect=Das aktuelle Passwort ist falsch. -change_password_success=Dein Passwort wurde aktualisiert. Bitte verwende dieses beim nรคchsten Einloggen. +change_password_success=Dein Passwort wurde aktualisiert. Verwende ab jetzt dein neues Passwort zum Einloggen. password_change_disabled=Benutzer, die nicht von Forgejo verwaltet werden, kรถnnen ihr Passwort im Web-Interface nicht รคndern. emails=E-Mail-Adressen @@ -1058,6 +1060,8 @@ language.localization_project = Hilf uns, Forgejo in deine Sprache zu รผbersetze language.description = Diese Sprache wird in deinem Konto gespeichert und standardmรครŸig nach dem Anmelden benutzt. user_block_yourself = Du kannst dich nicht selbst blockieren. pronouns_custom_label = Individuelle Pronomen +change_username_redirect_prompt.with_cooldown.one = Der alte Benutzername ist nach einer Schutzzeit von einem Tag wieder fรผr alle Verfรผgbar. Du kannst den alten Benutzername wรคhrend dieser Schutzzeit erneut beanspruchen. +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. [repo] owner=Besitzer @@ -3004,6 +3008,8 @@ teams.invite.by=Von %s eingeladen teams.invite.description=Bitte klicke auf die folgende Schaltflรคche, um dem Team beizutreten. follow_blocked_user = Du kannst dieser Organisation nicht folgen, weil diese Organisation dich blockiert hat. open_dashboard = รœbersicht รถffnen +settings.change_orgname_redirect_prompt.with_cooldown.one = Der alte Benutzername ist nach einer Schutzzeit von einem Tag wieder fรผr alle Verfรผgbar. Du kannst den alten Benutzername wรคhrend dieser Schutzzeit erneut beanspruchen. +settings.change_orgname_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. [admin] dashboard=รœbersicht @@ -3109,7 +3115,7 @@ dashboard.sync_branch.started=Synchronisierung der Branches gestartet dashboard.rebuild_issue_indexer=Issue-Indexer neu bauen users.user_manage_panel=Benutzerkonten verwalten -users.new_account=Benutzerkonto erstellen +users.new_account=Benutzeraccount erstellen users.name=Benutzername users.full_name=Vollstรคndiger Name users.activated=Aktiviert @@ -3579,7 +3585,7 @@ comment_pull=`hat den Pull-Request %[3]s#%[2]s kommentiert` merge_pull_request=`fรผhrte Pull-Request %[3]s#%[2]s zusammen` auto_merge_pull_request=`fรผhrte Pull-Request %[3]s#%[2]s automatisch zusammen` transfer_repo=hat Repository %s รผbertragen zu %s -push_tag=Tag %[3]s nach %[4]s wurde gepusht +push_tag=hat Tag %[3]s auf %[4]s gepusht delete_tag=hat Tag %[2]s in %[3]s gelรถscht delete_branch=hat Branch %[2]s in %[3]s gelรถscht compare_branch=Vergleichen diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 2c7dbe69ad..166616ba91 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -644,6 +644,7 @@ team_name_been_taken = The team name is already taken. team_no_units_error = Allow access to at least one repository section. email_been_used = The email address is already used. email_invalid = The email address is invalid. +email_domain_is_not_allowed = The domain of the user's email address %s conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST. Make sure you have set the email address correctly. openid_been_used = The OpenID address "%s" is already used. username_password_incorrect = Username or password is incorrect. password_complexity = Password does not pass complexity requirements: @@ -819,7 +820,7 @@ old_password = Current password new_password = New password retype_new_password = Confirm new password password_incorrect = The current password is incorrect. -change_password_success = Your password has been updated. Sign in using your new password from now on. +change_password_success = Your password has been updated. From now on, use your new password to sign in. password_change_disabled = Non-local users cannot update their password through the Forgejo web interface. manage_emails = Manage email addresses @@ -1192,6 +1193,7 @@ archive.title = This repository is archived. You can view files and clone it, bu archive.title_date = This repository has been archived on %s. You can view files and clone it, but cannot push or open issues or pull requests. archive.issue.nocomment = This repository is archived. You cannot comment on issues. archive.pull.nocomment = This repository is archived. You cannot comment on pull requests. +archive.pull.noreview = This repository is archived. You cannot review pull requests. form.reach_limit_of_creation_1 = The owner has already reached the limit of %d repository. form.reach_limit_of_creation_n = The owner has already reached the limit of %d repositories. @@ -1430,6 +1432,7 @@ editor.user_no_push_to_branch = User cannot push to branch editor.require_signed_commit = Branch requires a signed commit editor.cherry_pick = Cherry-pick %s onto: editor.revert = Revert %s onto: +editor.commit_email = Commit email commits.desc = Browse source code change history. commits.commits = Commits diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index ec0239648c..abdadc6c66 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -56,7 +56,7 @@ mirror=Rรฉplica new_repo=Nuevo repositorio new_migrate=Nueva migraciรณn new_mirror=Nueva rรฉplica -new_fork=Nuevo fork de repositorio +new_fork=Nueva bifurcaciรณn del repositorio new_org=Nueva organizaciรณn new_project=Nuevo proyecto new_project_column=Nueva columna @@ -75,7 +75,7 @@ collaborative=Colaborativo forks=Forks activities=Actividades -pull_requests=Pull requests +pull_requests=Solicitudes de incorporaciรณn de cambios issues=Incidencias milestones=Hitos @@ -156,8 +156,8 @@ invalid_data = Datos invรกlidos: %v confirm_delete_artifact = ยฟEstรกs seguro de que deseas eliminar el artefacto "%s"? more_items = Mas cosas copy_generic = Copiar al portapapeles -filter.not_fork = No forks -filter.is_fork = Forks +filter.not_fork = No hay bifurcaciones +filter.is_fork = Bifurcaciones test = Test error413 = Has agotado tu cuota. new_repo.title = Nuevo repositorio @@ -397,8 +397,8 @@ code_search_results=Resultados de bรบsqueda para ยซ%sยป code_last_indexed_at=Indexado por รบltima vez %s relevant_repositories_tooltip=Repositorios que son bifurcaciones o que no tienen ningรบn tema, ningรบn icono, y ninguna descripciรณn estรกn ocultos. relevant_repositories=Solo se muestran repositorios relevantes, mostrar resultados sin filtrar. -forks_few = %d forks -forks_one = %d fork +forks_few = %d bifurcaciones +forks_one = %d bifurcaciรณn stars_few = %d estrellas stars_one = %d estrella @@ -1070,10 +1070,10 @@ visibility=Visibilidad visibility_description=Sรณlo el propietario o los miembros de la organizaciรณn -si tienen derechos- podrรกn verlo. visibility_helper=Hacer el repositorio privado visibility_helper_forced=El administrador de su sitio obliga a nuevos repositorios a ser privados. -visibility_fork_helper=(Cambiar esto afectarรก a la visibilidad de todos los forks.) +visibility_fork_helper=(Cambiar esto afectarรก a la visibilidad de todas las bifurcaciones.) clone_helper=ยฟNecesita ayuda para clonar? Visite Ayuda. -fork_repo=Hacer fork del repositorio -fork_from=Crear un fork desde +fork_repo=Hacer una bifurcaciรณn del repositorio +fork_from=Crear una bifurcaciรณn desde already_forked=Ya ha forkeado %s fork_to_different_account=Forkear a una cuenta diferente fork_visibility_helper=La visibilidad de un repositorio del cual se ha hecho fork no puede ser cambiada. @@ -3852,7 +3852,7 @@ search = Buscar... type_tooltip = Tipo de bรบsqueda project_kind = Buscar proyectos... branch_kind = Buscar ramas... -commit_kind = Buscar commits... +commit_kind = Buscar confirmaciones... repo_kind = Buscar repositorios... user_kind = Buscar usuarios... org_kind = Buscar organizaciones... diff --git a/options/locale/locale_et.ini b/options/locale/locale_et.ini index 26621dd16b..e54ceadbb5 100644 --- a/options/locale/locale_et.ini +++ b/options/locale/locale_et.ini @@ -108,7 +108,7 @@ never = Mitte kunagi unknown = Teadmata rss_feed = RSS infovoog confirm_delete_artifact = Kas oled kindel et soovite artefakti "%s" kustutada? -pin = +pin = artifacts = Artefaktid archived = Arhiveeritud concept_system_global = รœlemaailmne diff --git a/options/locale/locale_fi-FI.ini b/options/locale/locale_fi-FI.ini index dd1f97bd00..84940f5e45 100644 --- a/options/locale/locale_fi-FI.ini +++ b/options/locale/locale_fi-FI.ini @@ -1756,7 +1756,7 @@ issues.close = Sulje ongelma issues.no_content = Ei kuvausta. pulls.reject_count_1 = %d muutospyyntรถ pulls.update_branch_success = Haarapรคivitys onnistui -milestones.completeness = %d%% valmiina +milestones.completeness = %d% % valmiina contributors.contribution_type.additions = Lisรคykset contributors.contribution_type.deletions = Poistot settings.webhook_deletion_success = Webkoukku on poistettu. @@ -2088,6 +2088,9 @@ new_from_template_description = Voit valita olemassa olevan repon mallipohjan ja new_advanced = Lisรคasetukset new_advanced_expand = Laajenna napsauttamalla template_description = Repojen mallipohjat mahdollistavat uusien repojen luomisen halutulla hakemistorakenteella, tiedostoilla ja valinnaisilla asetuksilla. +settings.enter_repo_name = Kirjoita omistajan ja repon nimi tรคsmรคlleen kuten esitetty: +settings.confirmation_string = Vahvistusteksti +settings.delete_notices_2 = - Tรคmรค toiminto poistaa pysyvรคsti repon %s mukaan lukien koodin, ongelmat, kommentit, wikidatan ja avustaja-asetukset. @@ -2578,6 +2581,12 @@ commit_repo = tyรถnsi haaraan %[3]s repossa create_issue = `avasi ongelman %[3]s#%[2]s` reopen_issue = `avasi uudelleen ongelman %[3]s#%[2]s` create_pull_request = `loi vetopyynnรถn %[3]s#%[2]s` +reopen_pull_request = `avasi uudelleen vetopyynnรถn %[3]s#%[2]s` +close_pull_request = `sulki vetopyynnรถn %[3]s#%[2]s` +comment_issue = `kommentoi ongelmaa %[3]s#%[2]s` +close_issue = `sulki ongelman %[3]s#%[2]s` +merge_pull_request = `yhdisti vetopyynnรถn %[3]s#%[2]s` +comment_pull = `kommentoi vetopyyntรถรค %[3]s#%[2]s` [tool] now=nyt @@ -2624,6 +2633,7 @@ error.not_signed_commit=Kommitti ei ole allekirjoitettu error.extract_sign = Allekirjoituksen purkaminen epรคonnistui default_key = Allekirjoitettu oletusavaimella error.failed_retrieval_gpg_keys = Ei saatu yhtรคkรครคn kommitin tekijรคn tiliin liitettyรค avainta +error.generate_hash = Tiivisteen luominen kommitista epรคonnistui [units] unit = Yksikkรถ @@ -2765,6 +2775,11 @@ alpine.registry.info = Valitse $branch ja $repository alla olevasta listasta. container.images.title = Levykuvat owner.settings.cargo.initialize = Alusta indeksi owner.settings.cargo.initialize.description = Erityinen Git-repoindeksi vaaditaan Cargo-rekisterin kรคyttรคmiseksi. Tรคmรคn valinnan kรคyttรคminen luo (tarvittaessa uudelleen) repon ja mรครคrittรครค sen asetukset automaattisesti. +settings.link.error = Repositorion linkin pรคivittรคminen epรคonnistui. +alt.repository.multiple_groups = Tรคmรค paketti on saatavilla useissa ryhmissรค. +alt.repository.architectures = Arkkitehtuurit +alt.install = Asenna paketti +alt.registry.install = Asenna paketti suorittamalla komento: [secrets] creation.failed = Salaisuuden lisรครคminen epรคonnistui. diff --git a/options/locale/locale_fil.ini b/options/locale/locale_fil.ini index 07372944bb..dea74a642c 100644 --- a/options/locale/locale_fil.ini +++ b/options/locale/locale_fil.ini @@ -640,6 +640,8 @@ AccessToken = Token ng pag-access Biography = Byograpya Location = Lokasyon visit_rate_limit = Natugunan ang limitasyon sa rate ng malayuang pagbisita. +username_claiming_cooldown = Hindi ma-claim ang username na ito, dahil hindi pa tapos ang panahon ng cooldown. Maari itong i-claim sa %[1]s. +email_domain_is_not_allowed = Sumasalungat ang domain ng email address ng user %s sa EMAIL_DOMAIN_ALLOWLIST o EMAIL_DOMAIN_BLOCKLIST. Siguraduhing natakda mo ang email address nang tama. [user] joined_on = Sumali noong %s @@ -784,7 +786,7 @@ old_password = Kasalukuyang password new_password = Bagong password retype_new_password = Kumpirmahin ang bagong password password_incorrect = Mali ang kasalukuyang password. -change_password_success = Na-update na ang iyong password. Mag-sign in gamit ng bagong password simula ngayon. +change_password_success = Na-update na ang iyong password. Simula ngayon, gamitin ang iyong bagong password para mag-sign in. password_change_disabled = Hindi mababago ng mga di-lokal na gumagamit ang kanilang password sa pamamagitan ng Forgejo web interface. emails = Mga email address manage_emails = Ipamahala ang mga email address @@ -999,6 +1001,8 @@ language.description = Mase-save ang wika sa iyong account at gagamitin bilang d 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. +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. [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. @@ -2911,7 +2915,7 @@ dashboard.delete_old_system_notices = Burahin ang lahat ng mga lumang paunawa ng dashboard.gc_lfs = I-garbage collect ang mga LFS meta object dashboard.stop_zombie_tasks = Itigil ang mga zombie action task users.user_manage_panel = Ipamahala ang mga user account -users.new_account = Gumawa ng User Account +users.new_account = Gumawa ng user account users.auth_login_name = Pangalan ng sign-in authentication users.password_helper = Iwanang walang laman ang password upang panatilihing hindi nabago. users.max_repo_creation = Pinakamataas na numero ng mga repositoryo @@ -3394,6 +3398,8 @@ teams.owners_permission_desc = Ang mga owner ay may punong access sa lah teams.add_nonexistent_repo = Hindi pa umiiral ang repositoryo na sinusubukan mong idagdag. Mangyaring gawin iyan muna. teams.all_repositories = Lahat ng mga repositoryo teams.all_repositories_helper = Ang koponan ay may access sa lahat ng mga repositoryo. Ang pagpili nito ay idadagdag ang lahat ng mga umiiral na repositoryo sa koponan. +settings.change_orgname_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. +settings.change_orgname_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. [packages] @@ -3571,7 +3577,7 @@ 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.repository = Info ng Repositoryo +alt.repository = Info ng repositoryo alt.repository.architectures = Mga architechture alt.repository.multiple_groups = Available ang package na ito sa iba't ibang grupo. @@ -3693,7 +3699,7 @@ approve_pull_request = `inaprubahan ang %[3]s#%[2]s` review_dismissed_reason = Dahilan: compare_branch = Ikumpara reject_pull_request = `nagmungkahi ng mga pagbabago para sa %[3]s#%[2]s` -rename_repo = pinalitan ang pangalan ng repositoryo mula %[1]s sa %[3]# +rename_repo = pinalitan ang pangalan ng repositoryo mula %[1]s sa %[3]s close_issue = `sinara ang isyu na %[3]s#%[2]s` review_dismissed = `na-dismiss ang pagsusuri mula %[4]s para sa %[3]s#%[2]s` close_pull_request = `sinara ang hiling sa paghila na %[3]s#%[2]s` diff --git a/options/locale/locale_fr-FR.ini b/options/locale/locale_fr-FR.ini index f67b11dc04..7924c6eea4 100644 --- a/options/locale/locale_fr-FR.ini +++ b/options/locale/locale_fr-FR.ini @@ -680,6 +680,7 @@ Biography = Biographie Website = Site web Location = Emplacement To = Nom de la branche +email_domain_is_not_allowed = Le domaine %s du courriel utilisateur entre en conflit avec EMAIL_DOMAIN_ALLOWLIST ou EMAIL_DOMAIN_BLOCKLIST. Veuillez vous assurer le courriel est renseignรฉ. [user] change_avatar=Changer votre avatarโ€ฆ diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 829459f202..1b0001d44b 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -75,7 +75,7 @@ mirrors=Spoguฤผglabฤtavas collaborative=Lฤซdzdarboลกanฤs forks=Atzarojumi -activities=Aktivitฤte +activities=Darbฤซbas pull_requests=Izmaiล†u pieprasฤซjumi issues=Pieteikumi milestones=Atskaites punkti @@ -678,6 +678,8 @@ Biography = Dzฤซves un darbฤซbas apraksts Website = Tฤซmekฤผvietne AccessToken = Piekฤผuves pilnvara To = Zara nosaukums +username_claiming_cooldown = ล o lietotฤjvฤrdu vฤ“l nevar izmantot, jo tฤ noilgums vฤ“l nav beidzies. To varฤ“s izmantot %[1]s. +email_domain_is_not_allowed = Lietotฤja e-pasta adreses %s domฤ“na vฤrds ir pretrunฤt ar EMAIL_DOMAIN_ALLOWLIST vai EMAIL_DOMAIN_BLOCKLIST. Jฤpฤrliecinฤs, ka e-pasta adrese ir norฤdฤซta pareizi. [user] @@ -695,7 +697,7 @@ following_few=%d seko follow=Sekot unfollow=Nesekot user_bio=Biogrฤfija -disabled_public_activity=ล is lietotฤjs ir atslฤ“dzies iespฤ“ju aplลซkot tฤ aktivitฤti. +disabled_public_activity=ล is lietotฤjs ir atspฤ“jojis darbฤซbu redzamฤซbu visiem. email_visibility.limited=E-pasta adrese ir redzama visiem autentificฤ“tajiem lietotฤjiem email_visibility.private=E-pasta adrese ir redzama tikai administratoriem show_on_map=Rฤdฤซt ลกo vietu kartฤ“ @@ -803,7 +805,7 @@ old_password=Paลกreizฤ“jฤ parole new_password=Jaunฤ parole retype_new_password=Apstiprinฤt jauno paroli password_incorrect=Ievadฤซta nepareiza paลกreizฤ“jฤ parole. -change_password_success=Parole tika sekmฤซgi nomainฤซta. Turpmฤk jฤpiesakฤs ar savu jauno paroli. +change_password_success=Parole tika atjauninฤta. Turpmฤk jฤizmanto sava jaunฤ parole, lai pieteiktos. password_change_disabled=ฤ€rฤ“jie lietotฤji nevar mainฤซt savu paroli Forgejo tฤซmekฤผa saskarnฤ“. emails=E-pasta adreses @@ -904,7 +906,7 @@ added_on=Pievienots %s valid_until_date=Derฤซgs lฤซdz %s valid_forever=Derฤซgs mลซลพฤซgi last_used=Pฤ“dฤ“jo reizi izmantota -no_activity=Nav nesenas aktivitฤtes +no_activity=Nav nesenu darbฤซbu can_read_info=Lasฤซt can_write_info=Rakstฤซt key_state_desc=ล ฤซ atslฤ“ga ir izmantota pฤ“dฤ“jo 7 dienu laikฤ @@ -1055,6 +1057,8 @@ additional_repo_units_hint_description = Attฤ“lot norฤdi "Iespฤ“jot vฤ“l" glab language.description = ล ฤซ valoda tiks saglabฤta kontฤ un pฤ“c pieteikลกanฤs tiks izmantota kฤ noklusฤ“juma. user_block_yourself = Nevar liegt sevi. pronouns_custom_label = Pielฤgoti vietniekvฤrdi +change_username_redirect_prompt.with_cooldown.one = Vecais lietotฤjvฤrds bลซs pieejams visiem pฤ“c noilguma, kas ir %[1]d diena. ล ajฤ laikฤ ir iespฤ“jams to atkal sฤkt izmantot. +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. [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. @@ -1652,7 +1656,7 @@ issues.save=Saglabฤt issues.label_title=Nosaukums issues.label_description=Apraksts issues.label_color=Krฤsa -issues.label_exclusive=Ekskluzฤซvs +issues.label_exclusive=Seviลกฤทa issues.label_archive=Arhivฤ“t iezฤซmi issues.label_archived_filter=Rฤdฤซt arhivฤ“tฤs iezฤซmes issues.label_archive_tooltip=Arhivฤ“tฤs iezฤซmes pฤ“c noklusฤ“juma netiek iekฤผautas ieteikumos, kad meklฤ“ pฤ“c iezฤซmes. @@ -2033,7 +2037,7 @@ wiki.last_updated=Pฤ“dฤ“jo reizi labota %s wiki.page_name_desc=Jฤievada ลกฤซs vikivietnes lapas nosaukums. Daลพi ฤซpaลกie nosaukumi ir: "Home", "_Sidebar" un "_Footer". wiki.original_git_entry_tooltip=Rฤdฤซt sฤkotnฤ“jo Git datni, nevis izmantot draudzฤซgo saiti. -activity=Aktivitฤte +activity=Notikumi activity.period.filter_label=Laika periods: activity.period.daily=1 diena activity.period.halfweekly=3 dienas @@ -2999,6 +3003,8 @@ teams.invite.by=Uzaicinฤja %s teams.invite.description=Lลซgums nospiest zemฤk esoลกo pogu, lai pievienotos komandai. open_dashboard = Atvฤ“rt pฤrskata paneli follow_blocked_user = Tu nevari sekot ลกai apvienฤซbai, jo tฤ ir liegusi Tevi. +settings.change_orgname_redirect_prompt.with_cooldown.one = Vecais lietotฤjvฤrds bลซs pieejams visiem pฤ“c noilguma, kas ir %[1]d diena. ล ajฤ laikฤ ir iespฤ“jams to atkal sฤkt izmantot. +settings.change_orgname_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. [admin] dashboard=Pฤrskata panelis @@ -3761,7 +3767,7 @@ settings.link.select=Atlasฤซt glabฤtavu settings.link.button=Atjauninฤt glabฤtavas saiti settings.link.success=Glabฤtavas saite tika sekmฤซgi atjauninฤta. settings.link.error=Neizdevฤs atjauninฤt glabฤtavas saiti. -settings.delete=Dzฤ“st pakotni +settings.delete=Izdzฤ“st pakotni settings.delete.description=Pakotne tiks neatgriezeniski izdzฤ“sta. settings.delete.notice=Tiks izdzฤ“sta pakotne %s (%s). ล ฤซ darbฤซba ir neatgriezeniska. Tieลกฤm turpinฤt? settings.delete.success=Pakotne tika izdzฤ“sta. diff --git a/options/locale/locale_nb_NO.ini b/options/locale/locale_nb_NO.ini index 18c9835df0..2c8b5cfc64 100644 --- a/options/locale/locale_nb_NO.ini +++ b/options/locale/locale_nb_NO.ini @@ -1,5 +1,5 @@ [common] -enable_javascript = Denne nettsiden behรธver JavaScript. +enable_javascript = Denne nettsiden krever JavaScript. toc = Innholdsfortegnelse licenses = Lisenser return_to_forgejo = Tilbake til Forgejo @@ -126,6 +126,13 @@ webauthn_sign_in = Trykk pรฅ knappen pรฅ sikkerhetsnรธkkelen din. Dersom nรธkkel copy_path = Kopier sti webauthn_error_unable_to_process = Tjeneren kunne ikke behandle forespรธrselen din. webauthn_error_empty = Du mรฅ gi nรธkkelen et navn. +toggle_menu = ร…pne/lukke meny +twofa_scratch = To-faktor skrapekode +webauthn_press_button = Vennligst trykk pรฅ knappen pรฅ sikkerhetsnรธkkelenโ€ฆ +webauthn_error_duplicated = Sikkerhetsnรธkkelen er ikke tillatt for denne forespรธrselen. Vennligst sรธrg for at nรธkkelen ikke allerede er registrert. +webauthn_error_timeout = Et tidsavbrudd oppsto fรธr nรธkkelen din kunne leses. Vennligst last inn siden pรฅ nytt og prรธv igjen. +new_fork = Ny fork av repository +collaborative = Samarbeidende [search] search = Sรธk... diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index 30d2c0ebdf..ac34b83f4e 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -681,6 +681,8 @@ To = Branch naam Website = Website AccessToken = Toegangstoken Pronouns = Voornaamwoorden +username_claiming_cooldown = De gebruikersnaam kan niet opgeรซist worden, omdat de afkoelperiode nog niet voorbij is. Hij kan worden opgeรซist op %[1]s. +email_domain_is_not_allowed = Het domein van het e-mailadres van de gebruiker %s is in strijd met EMAIL_DOMAIN_ALLOWLIST of EMAIL_DOMAIN_BLOCKLIST. Controleer of u het e-mailadres correct hebt ingesteld. [user] @@ -1058,6 +1060,8 @@ language.description = Deze taal wordt opgeslagen in uw account en wordt als sta language.localization_project = Help ons Forgejo in uw taal te vertalen! Leer meer. user_block_yourself = U kunt niet zichzelf blokkeren. pronouns_custom_label = Aangepaste voornaamwoorden +change_username_redirect_prompt.with_cooldown.few = De oude gebruikersnaam zal voor iedereen beschikbaar zijn na een afkoelperiode van %[1]d dagen. U kunt de oude gebruikersnaam nog steeds opeisen tijdens de afkoelperiode. +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. [repo] owner=Eigenaar @@ -2863,6 +2867,7 @@ summary_card_alt = Overzichtskaart van repository %s release.summary_card_alt = Samenvattende kaart van een release met de titel "%s" in repository %s issues.reaction.alt_remove = Verwijder %[1]s reactie van bericht. issues.reaction.alt_many = %[1]s en %[2]d meer gereageerd %[3]s. +editor.commit_email = Commit e-mail @@ -3001,6 +3006,8 @@ settings.visibility.limited = Beperkt (alleen zichtbaar voor ingelogde gebruiker teams.add_nonexistent_repo = De repository die u probeert toe te voegen bestaat niet, maak deze eerst aan alstublieft. teams.all_repositories_write_permission_desc = Dit team verleent Schrijf permissies tot alle repositories: leden kunnen lezen en pushen naar repositories. open_dashboard = Open dashboard +settings.change_orgname_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. +settings.change_orgname_redirect_prompt.with_cooldown.few = De oude gebruikersnaam zal voor iedereen beschikbaar zijn na een afkoelperiode van %[1]d dagen. U kunt de oude gebruikersnaam nog steeds opeisen tijdens de afkoelperiode. [admin] dashboard=Overzicht @@ -3083,7 +3090,7 @@ dashboard.gc_times=GC verwerkingen dashboard.delete_old_system_notices=Verwijder alle oude systeemmededelingen uit de database users.user_manage_panel=Gebruikersaccounts beheren -users.new_account=Nieuw account aanmaken +users.new_account=Gebruikersaccount aanmaken users.name=Gebruikersnaam users.full_name=Volledige naam users.activated=Geactiveerd diff --git a/options/locale/locale_pt-BR.ini b/options/locale/locale_pt-BR.ini index f6cf73620c..5f4ebe389a 100644 --- a/options/locale/locale_pt-BR.ini +++ b/options/locale/locale_pt-BR.ini @@ -680,6 +680,8 @@ required_prefix = A entrada deve comeรงar com "%s" FullName = Nome completo Description = Descriรงรฃo unset_password = O usuรกrio de login nรฃo definiu a senha. +username_claiming_cooldown = Este nome de usuรกrio nรฃo pode ser registrado porque o perรญodo de espera ainda nรฃo acabou. Ele poderรก ser registrado em %[1]s. +email_domain_is_not_allowed = O domรญnio do endereรงo de email da conta %s estรก em conflito com EMAIL_DOMAIN_ALLOWLIST ou EMAIL_DOMAIN_BLOCKLIST. Certifique-se de que vocรช colocou o endereรงo de email correto. [user] @@ -805,7 +807,7 @@ old_password=Senha atual new_password=Nova senha retype_new_password=Confirme a nova senha password_incorrect=A senha atual estรก incorreta. -change_password_success=Sua senha foi atualizada. Acesse usando sua nova senha de agora em diante. +change_password_success=Sua senha foi atualizada. A partir de agora, use sua nova senha para acessar sua conta. password_change_disabled=Contas nรฃo-locais nรฃo podem alterar sua senha atravรฉs da interface web do Forgejo. emails=Endereรงos de e-mail @@ -1057,6 +1059,8 @@ language.localization_project = Ajude-nos a traduzir Forgejo para o seu idioma! language.description = Essa lรญngua serรก salva em sua conta e serรก usada como padrรฃo apรณs vocรช iniciar a sessรฃo. 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. [repo] owner=Proprietรกrio @@ -2619,7 +2623,7 @@ tag.create_tag_from=`Criar nova tag a partir de "%s"` tag.create_success=Tag "%s" criada. -topic.manage_topics=Gerenciar Tรณpicos +topic.manage_topics=Gerenciar tรณpicos topic.done=Feito topic.count_prompt=Vocรช nรฃo pode selecionar mais de 25 tรณpicos @@ -3000,6 +3004,8 @@ open_dashboard = Abrir painel settings.change_orgname_prompt = Obs.: Alterar o nome de uma organizaรงรฃo resultarรก na alteraรงรฃo do URL dela e disponibilizarรก o nome antigo para uso. follow_blocked_user = Nรฃo foi possรญvel seguir esta organizaรงรฃo porque ela bloqueou-o(a). form.name_pattern_not_allowed = O padrรฃo "%s" nรฃo รฉ permitido no nome de uma organizaรงรฃo. +settings.change_orgname_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. +settings.change_orgname_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. [admin] dashboard=Painel @@ -3820,6 +3826,13 @@ owner.settings.cargo.initialize.description = ร‰ necessรกrio um repositรณrio Git owner.settings.chef.keypair.description = ร‰ necessรกrio um par de chaves para autenticar no registro Chef. Se vocรช jรก gerou um par de chaves, gere um novo par e descarte o antigo. container.images.title = Imagens search_in_external_registry = Buscar em %s +alt.registry.install = Para instalar o pacote, execute o seguinte comando: +alt.registry = Configurar este registro da linha de comando: +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_'): [secrets] secrets=Segredos diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index ca1199dd54..d00c5810d4 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -681,6 +681,8 @@ AccessToken = Cรณdigo de acesso FullName = Nome completo Description = Descriรงรฃo Pronouns = Pronomes +username_claiming_cooldown = O nome de utilizador nรฃo pode ser reivindicado, porque o perรญodo de espera do mesmo ainda nรฃo terminou. Pode ser reivindicado em %[1]s. +email_domain_is_not_allowed = O domรญnio do endereรงo de email %s do utilizador entra em conflito com EMAIL_DOMAIN_ALLOWLIST ou EMAIL_DOMAIN_BLOCKLIST. Certifique-se de que definiu corretamente o endereรงo de email. [user] change_avatar=Mude o seu avatarโ€ฆ @@ -805,7 +807,7 @@ old_password=Senha corrente new_password=Nova senha retype_new_password=Confirme a nova senha password_incorrect=A senha corrente estรก errada. -change_password_success=A sua senha foi substituรญda. Inicie a sessรฃo com a nova senha a partir de agora. +change_password_success=A sua senha foi atualizada. A partir de agora, utilize a sua nova senha para iniciar sessรฃo. password_change_disabled=Os utilizadores nรฃo-locais nรฃo podem alterar a sua senha atravรฉs da interface web do Forgejo. emails=Endereรงos de email @@ -1057,6 +1059,8 @@ language.description = Este idioma vai ser guardado na sua conta e ser usado com language.localization_project = Ajude-nos a traduzir o Forgejo para o seu idioma! Saiba mais. 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. [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. @@ -3001,6 +3005,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. [admin] dashboard=Painel de controlo @@ -3821,6 +3827,13 @@ arch.version.backup = Cรณpia de seguranรงa arch.version.replaces = Substitui container.images.title = Imagens search_in_external_registry = Procurar em %s +alt.registry = Configure este registo a partir da linha de comandos: +alt.registry.install = Para instalar o pacote, execute o seguinte comando: +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_'): [secrets] secrets=Segredos diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index 5ed8dae67b..de6d9292af 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -680,6 +680,8 @@ Biography = ะž ัะตะฑะต Website = ะ’ะตะฑ-ัะฐะนั‚ Location = ะœะตัั‚ะพะฟะพะปะพะถะตะฝะธะต To = ะะฐะทะฒะฐะฝะธะต ะฒะตั‚ะฒะธ +email_domain_is_not_allowed = ะ”ะพะผะตะฝ ะฐะดั€ะตัะฐ ัะป. ะฟะพั‡ั‚ั‹ %s ะฝะต ั€ะฐะทั€ะตัˆั‘ะฝ ะบ ะธัะฟะพะปัŒะทะพะฒะฐะฝะธัŽ. ะฃะฑะตะดะธั‚ะตััŒ, ั‡ั‚ะพ ะพะฝ ะฒะฒะตะดั‘ะฝ ะฟั€ะฐะฒะธะปัŒะฝะพ ะธะปะธ ะฟะพะฟั€ะพะฑัƒะนั‚ะต ะดั€ัƒะณะพะน ะฐะดั€ะตั. +username_claiming_cooldown = ะญั‚ะพ ะธะผั ะฟะพะบะฐ ะฝะต ะผะพะถะตั‚ ะฑั‹ั‚ัŒ ะทะฐะฝัั‚ะพ, ั‚.ะบ. ัั€ะพะบ ะตะณะพ ะทะฐั‰ะธั‚ั‹ ะตั‰ั‘ ะฝะต ะฒั‹ัˆะตะป. ะ•ะณะพ ะฟะพะปัƒั‡ะธั‚ัั ะทะฐะฝัั‚ัŒ ะฟะพัะปะต %[1]s. [user] @@ -761,7 +763,7 @@ update_language_success=ะฏะทั‹ะบ ะพะฑะฝะพะฒะปั‘ะฝ. update_profile_success=ะ’ะฐัˆ ะฟั€ะพั„ะธะปัŒ ัƒัะฟะตัˆะฝะพ ะพะฑะฝะพะฒะปั‘ะฝ. change_username=ะ’ะฐัˆะต ะธะผั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะฑั‹ะปะพ ะธะทะผะตะฝะตะฝะพ. change_username_prompt=ะžะฑั€ะฐั‚ะธั‚ะต ะฒะฝะธะผะฐะฝะธะต: ะธะทะผะตะฝะตะฝะธะต ะธะผะตะฝะธ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ั‚ะฐะบะถะต ะผะตะฝัะตั‚ URL ะฒะฐัˆะตะน ัƒั‡ั‘ั‚ะฝะพะน ะทะฐะฟะธัะธ. -change_username_redirect_prompt=ะกั‚ะฐั€ะพะต ะธะผั ะฟะพะปัŒะทะพะฒะฐั‚ะตะปั ะฑัƒะดะตั‚ ะฟะตั€ะตะฝะฐะฟั€ะฐะฒะปัั‚ัŒ ะฝะฐ ะฝะพะฒะพะต ะดะพ ั‚ะตั… ะฟะพั€, ะฟะพะบะฐ ะตะณะพ ะฝะต ะทะฐะนะผัƒั‚. +change_username_redirect_prompt=ะกั‚ะฐั€ะพะต ะธะผั ะฑัƒะดะตั‚ ะฟะตั€ะตะฝะฐะฟั€ะฐะฒะปัั‚ัŒ ะฝะฐ ะฝะพะฒะพะต ะดะพ ั‚ะตั… ะฟะพั€, ะฟะพะบะฐ ะพะฝะพ ะฝะต ะฑัƒะดะตั‚ ะทะฐะฝัั‚ะพ. continue=ะ”ะฐะปะตะต cancel=ะžั‚ะผะตะฝะฐ language=ะฏะทั‹ะบ @@ -1057,6 +1059,8 @@ language.description = ะ’ั‹ะฑั€ะฐะฝะฝั‹ะน ัะทั‹ะบ ะฑัƒะดะตั‚ ัะพั…ั€ะฐะฝั‘ะฝ language.localization_project = ะŸะพะผะพะณะธั‚ะต ั ะฟะตั€ะตะฒะพะดะพะผ Forgejo ะฝะฐ ัะฒะพะน ัะทั‹ะบ! ะŸะพะดั€ะพะฑะฝะตะต. user_block_yourself = ะะตะปัŒะทั ะทะฐะฑะปะพะบะธั€ะพะฒะฐั‚ัŒ ัะตะฑั. pronouns_custom_label = ะ”ั€ัƒะณะธะต ะผะตัั‚ะพะธะผะตะฝะธั +change_username_redirect_prompt.with_cooldown.one = ะŸั€ะตะถะฝะตะต ะธะผั ะฑัƒะดะตั‚ ะดะพัั‚ัƒะฟะฝะพ ะดะปั ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั ะดั€ัƒะณะธะผ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัะผ ะฟะพัะปะต ะธัั‚ะตั‡ะตะฝะธั ะทะฐั‰ะธั‚ั‹ ะฒ %[1]d ะดะตะฝัŒ. ะ’ั‹ ัะผะพะถะตั‚ะต ะฒะตั€ะฝัƒั‚ัŒ ะตะณะพ ัะตะฑะต ะฒะพ ะฒั€ะตะผั ัั€ะพะบะฐ ะทะฐั‰ะธั‚ั‹. +change_username_redirect_prompt.with_cooldown.few = ะŸั€ะตะถะฝะตะต ะธะผั ะฑัƒะดะตั‚ ะดะพัั‚ัƒะฟะฝะพ ะดะปั ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั ะดั€ัƒะณะธะผ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัะผ ะฟะพัะปะต ะธัั‚ะตั‡ะตะฝะธั ะทะฐั‰ะธั‚ั‹ ะฒ %[1]d ะดะฝะตะน. ะ’ั‹ ัะผะพะถะตั‚ะต ะฒะตั€ะฝัƒั‚ัŒ ะตะณะพ ัะตะฑะต ะฒะพ ะฒั€ะตะผั ัั€ะพะบะฐ ะทะฐั‰ะธั‚ั‹. [repo] owner=ะ’ะปะฐะดะตะปะตั† @@ -2650,7 +2654,7 @@ tag.create_tag_from=ะกะพะทะดะฐั‚ัŒ ะฝะพะฒั‹ะน ั‚ะตะณ ะธะท ยซ%sยป tag.create_success=ะขะตะณ ยซ%sยป ัะพะทะดะฐะฝ. -topic.manage_topics=ะ ะตะดะฐะบั‚ะธั€ะพะฒะฐั‚ัŒ ั‚ะตะผะฐั‚ะธั‡ะตัะบะธะต ะผะตั‚ะบะธ +topic.manage_topics=ะ˜ะทะผะตะฝะธั‚ัŒ ั‚ะตะผั‹ topic.done=ะกะพั…ั€ะฐะฝะธั‚ัŒ topic.count_prompt=ะะตะปัŒะทั ะฒั‹ะฑั€ะฐั‚ัŒ ะฑะพะปะตะต 25 ั‚ะตะผ topic.format_prompt=ะขะตะผั‹ ะดะพะปะถะฝั‹ ะฝะฐั‡ะธะฝะฐั‚ัŒัั ั ะฑัƒะบะฒั‹ ะธะปะธ ั†ะธั„ั€ั‹ ะธ ะผะพะณัƒั‚ ัะพะดะตั€ะถะฐั‚ัŒ ะดะตั„ะธัั‹ (ยซ-ยป) ะธ ั‚ะพั‡ะบะธ (ยซ.ยป). ะ”ะปะธะฝะฐ ั‚ะตะผั‹ ะฝะต ะดะพะปะถะฝะฐ ะฟั€ะตะฒั‹ัˆะฐั‚ัŒ 35 ัะธะผะฒะพะปะพะฒ. ะ’ัะต ะฑัƒะบะฒั‹ ะดะพะปะถะฝั‹ ะฑั‹ั‚ัŒ ัั‚ั€ะพั‡ะฝั‹ะผะธ. @@ -3003,6 +3007,8 @@ teams.invite.description=ะะฐะถะผะธั‚ะต ะฝะฐ ะบะฝะพะฟะบัƒ ะฝะธะถะต, ั‡ั‚ะพะฑั‹ follow_blocked_user = ะ’ั‹ ะฝะต ะผะพะถะตั‚ะต ะฟะพะดะฟะธัะฐั‚ัŒัั ะฝะฐ ัั‚ัƒ ะพั€ะณะฐะฝะธะทะฐั†ะธัŽ, ั‚.ะบ. ะฒั‹ ะฒ ะฝะตะน ะทะฐะฑะปะพะบะธั€ะพะฒะฐะฝั‹. teams.general_access = ะะฐัั‚ั€ะฐะธะฒะฐะตะผั‹ะน ะดะพัั‚ัƒะฟ open_dashboard = ะžั‚ะบั€ั‹ั‚ัŒ ะฟะฐะฝะตะปัŒ +settings.change_orgname_redirect_prompt.with_cooldown.few = ะŸั€ะตะถะฝะตะต ะฝะฐะทะฒะฐะฝะธะต ะฑัƒะดะตั‚ ะดะพัั‚ัƒะฟะฝะพ ะดะปั ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั ะดั€ัƒะณะธะผ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัะผ ะฟะพัะปะต ะธัั‚ะตั‡ะตะฝะธั ะทะฐั‰ะธั‚ั‹ ะฒ %[1]d ะดะฝะตะน. ะ’ั‹ ัะผะพะถะตั‚ะต ะฒะตั€ะฝัƒั‚ัŒ ะตะณะพ ะฒะพ ะฒั€ะตะผั ัั€ะพะบะฐ ะทะฐั‰ะธั‚ั‹. +settings.change_orgname_redirect_prompt.with_cooldown.one = ะŸั€ะตะถะฝะตะต ะฝะฐะทะฒะฐะฝะธะต ะฑัƒะดะตั‚ ะดะพัั‚ัƒะฟะฝะพ ะดะปั ะธัะฟะพะปัŒะทะพะฒะฐะฝะธั ะดั€ัƒะณะธะผ ะฟะพะปัŒะทะพะฒะฐั‚ะตะปัะผ ะฟะพัะปะต ะธัั‚ะตั‡ะตะฝะธั ะทะฐั‰ะธั‚ั‹ ะฒ %[1]d ะดะตะฝัŒ. ะ’ั‹ ัะผะพะถะตั‚ะต ะฒะตั€ะฝัƒั‚ัŒ ะตะณะพ ะฒะพ ะฒั€ะตะผั ัั€ะพะบะฐ ะทะฐั‰ะธั‚ั‹. [admin] dashboard=ะŸะฐะฝะตะปัŒ ัƒะฟั€ะฐะฒะปะตะฝะธั @@ -3708,7 +3714,7 @@ composer.install=ะงั‚ะพะฑั‹ ัƒัั‚ะฐะฝะพะฒะธั‚ัŒ ะฟะฐะบะตั‚ ั ะฟะพะผะพั‰ัŒัŽ Co composer.dependencies=ะ—ะฐะฒะธัะธะผะพัั‚ะธ composer.dependencies.development=ะ—ะฐะฒะธัะธะผะพัั‚ะธ ะดะปั ั€ะฐะทั€ะฐะฑะพั‚ะบะธ conan.details.repository=ะ ะตะฟะพะทะธั‚ะพั€ะธะน -conan.registry=ะะฐัั‚ั€ะพะธั‚ัŒ ั€ะตะตัั‚ั€ ะธะท ะบะพะผะฐะฝะดะฝะพะน ัั‚ั€ะพะบะธ: +conan.registry=ะ”ะพะฑะฐะฒัŒั‚ะต ั€ะตะตัั‚ั€ ะบะพะผะฐะฝะดะพะน: conan.install=ะงั‚ะพะฑั‹ ัƒัั‚ะฐะฝะพะฒะธั‚ัŒ ะฟะฐะบะตั‚ ั ะฟะพะผะพั‰ัŒัŽ Conan, ะฒั‹ะฟะพะปะฝะธั‚ะต ัะปะตะดัƒัŽั‰ัƒัŽ ะบะพะผะฐะฝะดัƒ: conda.registry=ะŸั€ะพะฟะธัˆะธั‚ะต ัั‚ะพั‚ ั€ะตะตัั‚ั€ ะฒ ะบะฐั‡ะตัั‚ะฒะต ั€ะตะฟะพะทะธั‚ะพั€ะธั Conda ะฒ ัะฒะพั‘ะผ ั„ะฐะนะปะต .condarc: conda.install=ะงั‚ะพะฑั‹ ัƒัั‚ะฐะฝะพะฒะธั‚ัŒ ะฟะฐะบะตั‚ ั ะฟะพะผะพั‰ัŒัŽ Conda, ะฒั‹ะฟะพะปะฝะธั‚ะต ัะปะตะดัƒัŽั‰ัƒัŽ ะบะพะผะฐะฝะดัƒ: @@ -3723,7 +3729,7 @@ container.labels.key=ะšะปัŽั‡ container.labels.value=ะ—ะฝะฐั‡ะตะฝะธะต cran.registry=ะะฐัั‚ั€ะพะนั‚ะต ัั‚ะพั‚ ั€ะตะตัั‚ั€ ะฒ ั„ะฐะนะปะต Rprofile.site: cran.install=ะงั‚ะพะฑั‹ ัƒัั‚ะฐะฝะพะฒะธั‚ัŒ ะฟะฐะบะตั‚, ะฒั‹ะฟะพะปะฝะธั‚ะต ัะปะตะดัƒัŽั‰ัƒัŽ ะบะพะผะฐะฝะดัƒ: -debian.registry=ะะฐัั‚ั€ะพะธั‚ัŒ ั€ะตะตัั‚ั€ ะธะท ะบะพะผะฐะฝะดะฝะพะน ัั‚ั€ะพะบะธ: +debian.registry=ะ”ะพะฑะฐะฒัŒั‚ะต ั€ะตะตัั‚ั€ ะบะพะผะฐะฝะดะพะน: debian.registry.info=ะ’ั‹ะฑะตั€ะธั‚ะต $distribution ะธ $component ะธะท ัะฟะธัะบะฐ ะฝะธะถะต. debian.install=ะงั‚ะพะฑั‹ ัƒัั‚ะฐะฝะพะฒะธั‚ัŒ ะฟะฐะบะตั‚, ะฒั‹ะฟะพะปะฝะธั‚ะต ัะปะตะดัƒัŽั‰ัƒัŽ ะบะพะผะฐะฝะดัƒ: debian.repository=ะž ั€ะตะฟะพะทะธั‚ะพั€ะธะธ @@ -3732,13 +3738,13 @@ debian.repository.components=ะšะพะผะฟะพะฝะตะฝั‚ั‹ debian.repository.architectures=ะั€ั…ะธั‚ะตะบั‚ัƒั€ั‹ generic.download=ะกะบะฐั‡ะฐั‚ัŒ ะฟะฐะบะตั‚ ะธะท ะบะพะผะฐะฝะดะฝะพะน ัั‚ั€ะพะบะธ: go.install=ะฃัั‚ะฐะฝะพะฒะธั‚ะต ะฟะฐะบะตั‚ ะธะท ะบะพะผะฐะฝะดะฝะพะน ัั‚ั€ะพะบะธ: -helm.registry=ะะฐัั‚ั€ะพะธั‚ัŒ ั€ะตะตัั‚ั€ ะธะท ะบะพะผะฐะฝะดะฝะพะน ัั‚ั€ะพะบะธ: +helm.registry=ะ”ะพะฑะฐะฒัŒั‚ะต ั€ะตะตัั‚ั€ ะบะพะผะฐะฝะดะพะน: helm.install=ะงั‚ะพะฑั‹ ัƒัั‚ะฐะฝะพะฒะธั‚ัŒ ะฟะฐะบะตั‚, ะฒั‹ะฟะพะปะฝะธั‚ะต ัะปะตะดัƒัŽั‰ัƒัŽ ะบะพะผะฐะฝะดัƒ: maven.registry=ะะฐัั‚ั€ะพะนั‚ะต ั€ะตะตัั‚ั€ ะฒ ั„ะฐะนะปะต pom.xml ะฒะฐัˆะตะณะพ ะฟั€ะพะตะบั‚ะฐ: maven.install=ะงั‚ะพะฑั‹ ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ ะฟะฐะบะตั‚, ะฒะบะปัŽั‡ะธั‚ะต ะฒ ะฑะปะพะบ dependencies ะฒ ั„ะฐะนะปะต pom.xml ัะปะตะดัƒัŽั‰ะตะต: maven.install2=ะ’ั‹ะฟะพะปะฝะธั‚ัŒ ั‡ะตั€ะตะท ะบะพะผะฐะฝะดะฝัƒัŽ ัั‚ั€ะพะบัƒ: maven.download=ะงั‚ะพะฑั‹ ัะบะฐั‡ะฐั‚ัŒ ะทะฐะฒะธัะธะผะพัั‚ัŒ, ะทะฐะฟัƒัั‚ะธั‚ะต ะฒ ะบะพะผะฐะฝะดะฝะพะน ัั‚ั€ะพะบะต: -nuget.registry=ะะฐัั‚ั€ะพะธั‚ัŒ ั€ะตะตัั‚ั€ ะธะท ะบะพะผะฐะฝะดะฝะพะน ัั‚ั€ะพะบะธ: +nuget.registry=ะ”ะพะฑะฐะฒัŒั‚ะต ั€ะตะตัั‚ั€ ะบะพะผะฐะฝะดะพะน: nuget.install=ะงั‚ะพะฑั‹ ัƒัั‚ะฐะฝะพะฒะธั‚ัŒ ะฟะฐะบะตั‚ ั ะฟะพะผะพั‰ัŒัŽ NuGet, ะฒั‹ะฟะพะปะฝะธั‚ะต ัะปะตะดัƒัŽั‰ัƒัŽ ะบะพะผะฐะฝะดัƒ: nuget.dependency.framework=ะฆะตะปะตะฒะพะน ั„ั€ะตะนะผะฒะพั€ะบ npm.registry=ะะฐัั‚ั€ะพะนั‚ะต ั€ะตะตัั‚ั€ ะฒ ั„ะฐะนะปะต .npmrc ะฒะฐัˆะตะณะพ ะฟั€ะพะตะบั‚ะฐ: @@ -3752,7 +3758,7 @@ npm.details.tag=ะขะตะณ pub.install=ะงั‚ะพะฑั‹ ัƒัั‚ะฐะฝะพะฒะธั‚ัŒ ะฟะฐะบะตั‚ ั ะฟะพะผะพั‰ัŒัŽ Dart, ะฒั‹ะฟะพะปะฝะธั‚ะต ัะปะตะดัƒัŽั‰ัƒัŽ ะบะพะผะฐะฝะดัƒ: pypi.requires=ะขั€ะตะฑัƒะตั‚ัั Python pypi.install=ะงั‚ะพะฑั‹ ัƒัั‚ะฐะฝะพะฒะธั‚ัŒ ะฟะฐะบะตั‚ ั ะฟะพะผะพั‰ัŒัŽ pip, ะฒั‹ะฟะพะปะฝะธั‚ะต ัะปะตะดัƒัŽั‰ัƒัŽ ะบะพะผะฐะฝะดัƒ: -rpm.registry=ะะฐัั‚ั€ะพะธั‚ัŒ ั€ะตะตัั‚ั€ ะธะท ะบะพะผะฐะฝะดะฝะพะน ัั‚ั€ะพะบะธ: +rpm.registry=ะ”ะพะฑะฐะฒัŒั‚ะต ั€ะตะตัั‚ั€ ะบะพะผะฐะฝะดะพะน: rpm.distros.redhat=ะฝะฐ ะดะธัั‚ั€ะธะฑัƒั‚ะธะฒะฐั… ัะตะผะตะนัั‚ะฒะฐ RedHat rpm.distros.suse=ะฝะฐ ะดะธัั‚ั€ะธะฑัƒั‚ะธะฒะฐั… ัะตะผะตะนัั‚ะฒะฐ SUSE rpm.install=ะงั‚ะพะฑั‹ ัƒัั‚ะฐะฝะพะฒะธั‚ัŒ ะฟะฐะบะตั‚, ะฒั‹ะฟะพะปะฝะธั‚ะต ัะปะตะดัƒัŽั‰ัƒัŽ ะบะพะผะฐะฝะดัƒ: @@ -3764,7 +3770,7 @@ rubygems.dependencies.runtime=ะ—ะฐะฒะธัะธะผะพัั‚ะธ ะฒั€ะตะผะตะฝะธ ะฒั‹ะฟะพะปะฝ rubygems.dependencies.development=ะ—ะฐะฒะธัะธะผะพัั‚ะธ ะดะปั ั€ะฐะทั€ะฐะฑะพั‚ะบะธ rubygems.required.ruby=ะขั€ะตะฑัƒะตั‚ัั ะฒะตั€ัะธั Ruby rubygems.required.rubygems=ะขั€ะตะฑัƒะตั‚ัั ะฒะตั€ัะธั RubyGem -swift.registry=ะะฐัั‚ั€ะพะธั‚ัŒ ั€ะตะตัั‚ั€ ะธะท ะบะพะผะฐะฝะดะฝะพะน ัั‚ั€ะพะบะธ: +swift.registry=ะ”ะพะฑะฐะฒัŒั‚ะต ั€ะตะตัั‚ั€ ะบะพะผะฐะฝะดะพะน: swift.install=ะ”ะพะฑะฐะฒัŒั‚ะต ะฟะฐะบะตั‚ ะฒ ัะฒะพะน ั„ะฐะนะป Package.swift: swift.install2=ะธ ะทะฐะฟัƒัั‚ะธั‚ะต ัะปะตะดัƒัŽั‰ัƒัŽ ะบะพะผะฐะฝะดัƒ: vagrant.install=ะงั‚ะพะฑั‹ ะดะพะฑะฐะฒะธั‚ัŒ ะฑะพะบั Vagrant, ะฒั‹ะฟะพะปะฝะธั‚ะต ัะปะตะดัƒัŽั‰ัƒัŽ ะบะพะผะฐะฝะดัƒ: @@ -3834,6 +3840,9 @@ container.images.title = ะžะฑั€ะฐะทั‹ search_in_external_registry = ะะฐะนั‚ะธ ะฒ %s alt.repository = ะž ั€ะตะฟะพะทะธั‚ะพั€ะธะธ alt.repository.architectures = ะั€ั…ะธั‚ะตะบั‚ัƒั€ั‹ +alt.registry = ะ”ะพะฑะฐะฒัŒั‚ะต ั€ะตะตัั‚ั€ ะบะพะผะฐะฝะดะพะน: +alt.repository.multiple_groups = ะญั‚ะพั‚ ะฟะฐะบะตั‚ ะดะพัั‚ัƒะฟะตะฝ ะฒ ะฝะตัะบะพะปัŒะบะธั… ะณั€ัƒะฟะฟะฐั…. +alt.setup = ะ”ะพะฑะฐะฒัŒั‚ะต ั€ะตะฟะพะทะธั‚ะพั€ะธะน ะฒ ัะฒะพะน ัะฟะธัะพะบ ั€ะตะฟะพะทะธั‚ะพั€ะธะตะฒ (ะฒั‹ะฑะตั€ะธั‚ะต ะฟะพะดั…ะพะดัั‰ัƒัŽ ะฐั€ั…ะธั‚ะตะบั‚ัƒั€ัƒ ะฒะผะตัั‚ะพ ยซ_arch_ยป): [secrets] secrets=ะกะตะบั€ะตั‚ั‹ diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 472ce499f1..a9aee2c373 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -666,6 +666,8 @@ invalid_group_team_map_error = ` ะฟั€ะธะทะฝะฐั‡ะตะฝะฝั ะฝะตะดั–ะนัะฝะต: %s` unsupported_login_type = ะฆะตะน ั‚ะธะฟ ะฒั…ะพะดัƒ ะฝะต ะฟั–ะดั‚ั€ะธะผัƒั” ะฒะธะดะฐะปะตะฝะฝั ะพะฑะปั–ะบะพะฒะพะณะพ ะทะฐะฟะธััƒ. admin_cannot_delete_self = ะ’ะธ ะฝะต ะผะพะถะตั‚ะต ะฒะธะดะฐะปะธั‚ะธ ัะตะฑะต, ัะบั‰ะพ ะฒะธ ั” ะฐะดะผั–ะฝั–ัั‚ั€ะฐั‚ะพั€ะพะผ. ะกะฟะพั‡ะฐั‚ะบัƒ ะทะฝั–ะผั–ั‚ัŒ ั–ะท ัะตะฑะต ะฟั€ะฐะฒะฐ ะฐะดะผั–ะฝั–ัั‚ั€ะฐั‚ะพั€ะฐ. unset_password = ะ”ะปั ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ ะฝะต ะฒัั‚ะฐะฝะพะฒะปะตะฝะพ ะฟะฐั€ะพะปัŒ. +username_claiming_cooldown = ะฆะต ั–ะผ'ั ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ ะฝะต ะผะพะถะฝะฐ ะฟั€ะธัะฒะพั—ั‚ะธ, ะพัะบั–ะปัŒะบะธ ะนะพะณะพ ะฟะตั€ั–ะพะด ะทะฐั…ะธัั‚ัƒ ั‰ะต ะฝะต ะทะฐะบั–ะฝั‡ะธะฒัั. ะ†ะผ'ั ะผะพะถะฝะฐ ะฑัƒะดะต ะฟั€ะธัะฒะพั—ั‚ะธ %[1]s. +email_domain_is_not_allowed = ะ”ะพะผะตะฝ ะฐะดั€ะตัะธ ะตะปะตะบั‚ั€ะพะฝะฝะพั— ะฟะพัˆั‚ะธ %s ะบะพะฝั„ะปั–ะบั‚ัƒั” ะท EMAIL_DOMAIN_ALLOWLIST ะฐะฑะพ EMAIL_DOMAIN_BLOCKLIST. ะŸะตั€ะตะฒั–ั€ั‚ะต, ั‡ะธ ะฒะธ ะฟั€ะฐะฒะธะปัŒะฝะพ ะฒะบะฐะทะฐะปะธ ะฐะดั€ะตััƒ ะตะปะตะบั‚ั€ะพะฝะฝะพั— ะฟะพัˆั‚ะธ. [user] @@ -761,7 +763,7 @@ update_password=ะžะฝะพะฒะธั‚ะธ ะฟะฐั€ะพะปัŒ old_password=ะŸะพั‚ะพั‡ะฝะธะน ะฟะฐั€ะพะปัŒ new_password=ะะพะฒะธะน ะฟะฐั€ะพะปัŒ password_incorrect=ะŸะพั‚ะพั‡ะฝะธะน ะฟะฐั€ะพะปัŒ ะฝะตะฟั€ะฐะฒะธะปัŒะฝะธะน. -change_password_success=ะ’ะฐัˆ ะฟะฐั€ะพะปัŒ ะฑัƒะฒ ะพะฝะพะฒะปะตะฝะธะน. ะขะตะฟะตั€ ัƒะฒั–ะนะดั–ั‚ัŒ ะฒ ัะธัั‚ะตะผัƒ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะฝะพะฒะธะน ะฟะฐั€ะพะปัŒ. +change_password_success=ะ’ะฐัˆ ะฟะฐั€ะพะปัŒ ะพะฝะพะฒะปะตะฝะพ. ะ’ั–ะดั‚ะตะฟะตั€ ะฒั…ะพะดัŒั‚ะต ะฒ ัะธัั‚ะตะผัƒ, ะฒะธะบะพั€ะธัั‚ะพะฒัƒัŽั‡ะธ ะฝะพะฒะธะน ะฟะฐั€ะพะปัŒ. password_change_disabled=ะะตะปะพะบะฐะปัŒะฝั– ะฐะบะฐัƒะฝั‚ะธ ะฝะต ะผะพะถัƒั‚ัŒ ะทะผั–ะฝะธั‚ะธ ะฟะฐั€ะพะปัŒ ั‡ะตั€ะตะท Forgejo. emails=ะะดั€ะตัะฐ ะตะปะตะบั‚ั€ะพะฝะฝะพั— ะฟะพัˆั‚ะธ @@ -1006,6 +1008,9 @@ key_signature_ssh_placeholder = ะŸะพั‡ะธะฝะฐั”ั‚ัŒัั ะท ยซ-----BEGIN SSH SIGNA user_block_yourself = ะ’ะธ ะฝะต ะผะพะถะตั‚ะต ะทะฐะฑะปะพะบัƒะฒะฐั‚ะธ ัะตะฑะต. pronouns_custom_label = ะ†ะฝัˆั– ะทะฐะนะผะตะฝะฝะธะบะธ repo_and_org_access = ะ”ะพัั‚ัƒะฟ ะดะพ ั€ะตะฟะพะทะธั‚ะพั€ั–ัŽ ั‚ะฐ ะพั€ะณะฐะฝั–ะทะฐั†ั–ั— +change_username_redirect_prompt.with_cooldown.few = ะกั‚ะฐั€ะต ั–ะผ'ั ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ ะฑัƒะดะต ะดะพัั‚ัƒะฟะฝะต ะฒัั–ะผ ะฟั–ัะปั ะฟะตั€ั–ะพะดัƒ ะทะฐั…ะธัั‚ัƒ, ัะบะธะน ั‚ั€ะธะฒะฐั‚ะธะผะต %[1]d ะดะฝั–ะฒ. ะŸั€ะพั‚ัะณะพะผ ะฟะตั€ั–ะพะดัƒ ะทะฐั…ะธัั‚ัƒ ะฒะธ ั‰ะต ะผะพะถะตั‚ะต ะฟะพะฒะตั€ะฝัƒั‚ะธ ัะพะฑั– ัั‚ะฐั€ะต ั–ะผ'ั. +change_username_redirect_prompt.with_cooldown.one = ะกั‚ะฐั€ะต ั–ะผ'ั ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ ะฑัƒะดะต ะดะพัั‚ัƒะฟะฝะต ะฒัั–ะผ ะฟั–ัะปั ะฟะตั€ั–ะพะดัƒ ะทะฐั…ะธัั‚ัƒ, ัะบะธะน ั‚ั€ะธะฒะฐั‚ะธะผะต %[1]d ะดะตะฝัŒ. ะŸั€ะพั‚ัะณะพะผ ะฟะตั€ั–ะพะดัƒ ะทะฐั…ะธัั‚ัƒ ะฒะธ ั‰ะต ะผะพะถะตั‚ะต ะฟะพะฒะตั€ะฝัƒั‚ะธ ัะพะฑั– ัั‚ะฐั€ะต ั–ะผ'ั. +change_username_redirect_prompt = ะกั‚ะฐั€ะต ั–ะผ'ั ะบะพั€ะธัั‚ัƒะฒะฐั‡ะฐ ะฑัƒะดะต ะฟะตั€ะตะฝะฐะฟั€ะฐะฒะปะตะฝะฝัะผ, ะฟะพะบะธ ั…ั‚ะพััŒ ะฝะต ะฟั€ะธัะฒะพั—ั‚ัŒ ั–ะผ'ั ัะพะฑั–. [repo] owner=ะ’ะปะฐัะฝะธะบ @@ -2595,6 +2600,8 @@ follow_blocked_user = ะ’ะธ ะฝะต ะผะพะถะตั‚ะต ัั‚ะตะถะธั‚ะธ ะทะฐ ั†ั–ั”ัŽ ะพั€ะณ teams.invite.description = ะฉะพะฑ ะฟั€ะธั”ะดะฝะฐั‚ะธัั ะดะพ ะบะพะผะฐะฝะดะธ, ะฝะฐั‚ะธัะฝั–ั‚ัŒ ะบะฝะพะฟะบัƒ ะฝะธะถั‡ะต. teams.invite.title = ะ’ะฐั ะทะฐะฟั€ะพัˆะตะฝะพ ะฟั€ะธั”ะดะฝะฐั‚ะธัั ะดะพ ะบะพะผะฐะฝะดะธ %s ะฒ ะพั€ะณะฐะฝั–ะทะฐั†ั–ั— %s. form.name_reserved = ะะฐะทะฒัƒ ะพั€ะณะฐะฝั–ะทะฐั†ั–ั— ยซ%sยป ะทะฐั€ะตะทะตั€ะฒะพะฒะฐะฝะพ. +settings.change_orgname_redirect_prompt.with_cooldown.one = ะกั‚ะฐั€ะฐ ะฝะฐะทะฒะฐ ะฑัƒะดะต ะดะพัั‚ัƒะฟะฝะฐ ะฒัั–ะผ ะฟั–ัะปั ะฟะตั€ั–ะพะดัƒ ะทะฐั…ะธัั‚ัƒ, ัะบะธะน ั‚ั€ะธะฒะฐั‚ะธะผะต %[1]d ะดะตะฝัŒ. ะŸั€ะพั‚ัะณะพะผ ะฟะตั€ั–ะพะดัƒ ะทะฐั…ะธัั‚ัƒ ะฒะธ ั‰ะต ะผะพะถะตั‚ะต ะฟะพะฒะตั€ะฝัƒั‚ะธ ัั‚ะฐั€ัƒ ะฝะฐะทะฒัƒ. +settings.change_orgname_redirect_prompt.with_cooldown.few = ะกั‚ะฐั€ะฐ ะฝะฐะทะฒะฐ ะฑัƒะดะต ะดะพัั‚ัƒะฟะฝะฐ ะฒัั–ะผ ะฟั–ัะปั ะฟะตั€ั–ะพะดัƒ ะทะฐั…ะธัั‚ัƒ, ัะบะธะน ั‚ั€ะธะฒะฐั‚ะธะผะต %[1]d ะดะฝั–ะฒ. ะŸั€ะพั‚ัะณะพะผ ะฟะตั€ั–ะพะดัƒ ะทะฐั…ะธัั‚ัƒ ะฒะธ ั‰ะต ะผะพะถะตั‚ะต ะฟะพะฒะตั€ะฝัƒั‚ะธ ัั‚ะฐั€ัƒ ะฝะฐะทะฒัƒ. [admin] dashboard=ะŸะฐะฝะตะปัŒ ัƒะฟั€ะฐะฒะปั–ะฝะฝั diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index b17d85cffa..a527197ce3 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -680,6 +680,8 @@ AccessToken = ่ฎฟ้—ฎไปค็‰Œ Description = ๆ่ฟฐ Pronouns = ไปฃ็งฐ Biography = ็ฎ€ๅކ +username_claiming_cooldown = ็”จๆˆทๅไธ่ƒฝ่ขซ่ฎค้ข†๏ผŒๅ› ไธบๅ…ถไปๅค„ไบŽๅ†ทๅดๆœŸ้—ดใ€‚ๅ…ถๅฏไปฅๅœจ%[1]sๅŽ่ขซ่ฎค้ข†ใ€‚ +email_domain_is_not_allowed = ็”จๆˆท็”ตๅญ้‚ฎ็ฎฑ็š„ๅŸŸๅ%sไธŽEMAIL_DOMAIN_ALLOWLISTๆˆ–EMAIL_DOMAIN_BLOCKLISTๅ†ฒ็ชใ€‚่ฏท็กฎไฟๆ‚จๆญฃ็กฎ่ฎพ็ฝฎไบ†็”ตๅญ้‚ฎไปถๅœฐๅ€ใ€‚ [user] change_avatar=ไฟฎๆ”นๅคดๅƒโ€ฆ @@ -804,7 +806,7 @@ old_password=ๅฝ“ๅ‰ๅฏ†็  new_password=ๆ–ฐ็š„ๅฏ†็  retype_new_password=็กฎ่ฎคๆ–ฐๅฏ†็  password_incorrect=ๅฝ“ๅ‰ๅฏ†็ ไธๆญฃ็กฎใ€‚ -change_password_success=ๆ‚จ็š„ๅฏ†็ ๅทฒๆ›ดๆ–ฐใ€‚ไปŽ็Žฐๅœจๅผ€ๅง‹ไฝฟ็”จๆ‚จ็š„ๆ–ฐๅฏ†็ ็™ปๅฝ•ใ€‚ +change_password_success=ๆ‚จ็š„ๅฏ†็ ๅทฒๆ›ดๆ–ฐใ€‚ไปŽ็Žฐๅœจๅผ€ๅง‹่ฏทไฝฟ็”จๆ‚จ็š„ๆ–ฐๅฏ†็ ็™ปๅฝ•ใ€‚ password_change_disabled=้žๆœฌๅœฐๅธๆˆทไธ่ƒฝ้€š่ฟ‡ Forgejo ็š„ web ็•Œ้ขๆ›ดๆ”นๅฏ†็ ใ€‚ emails=้‚ฎ็ฎฑๅœฐๅ€ @@ -1056,6 +1058,8 @@ language.description = ๆญค่ฏญ่จ€ๅฐ†ไฟๅญ˜ๅˆฐๆ‚จ็š„่ดฆๅทไธญ๏ผŒๅนถๅœจๆ‚จ็™ปๅฝ•ๅŽ language.localization_project = ๅธฎๅŠฉๆˆ‘ไปฌๅฐ† Forgejo ็ฟป่ฏ‘ๆˆๆ‚จ็š„่ฏญ่จ€๏ผไบ†่งฃๆ›ดๅคšใ€‚ user_block_yourself = ๆ‚จไธ่ƒฝๅฑ่”ฝ่‡ชๅทฑใ€‚ pronouns_custom_label = ่‡ชๅฎšไน‰ไปฃ่ฏ +change_username_redirect_prompt.with_cooldown.one = ๆ—ง็š„็”จๆˆทๅๅฐ†ๅœจ%[1]dๅคฉ็š„ๅ†ทๅดๆœŸๅŽๅฏนๆ‰€ๆœ‰ไบบๅฏ็”จ๏ผŒๆ‚จไปๅฏไปฅๅœจๆญคๆœŸ้—ด้‡ๆ–ฐ่ฎค้ข†ๆ—ง็š„็”จๆˆทๅใ€‚ +change_username_redirect_prompt.with_cooldown.few = ๆ—ง็š„็”จๆˆทๅๅฐ†ๅœจ%[1]dๅคฉ็š„ๅ†ทๅดๆœŸๅŽๅฏนๆ‰€ๆœ‰ไบบๅฏ็”จ๏ผŒๆ‚จไปๅฏไปฅๅœจๆญคๆœŸ้—ด้‡ๆ–ฐ่ฎค้ข†ๆ—ง็š„็”จๆˆทๅใ€‚ [repo] new_repo_helper=ไปฃ็ ไป“ๅบ“ๅŒ…ๅซไบ†ๆ‰€ๆœ‰็š„้กน็›ฎๆ–‡ไปถ๏ผŒๅŒ…ๆ‹ฌ็‰ˆๆœฌๅކๅฒ่ฎฐๅฝ•ใ€‚ๅทฒ็ปๅœจๅ…ถไป–ๅœฐๆ–นๆ‰˜็ฎกไบ†๏ผŸ่ฟ็งปไป“ๅบ“ใ€‚ @@ -2866,6 +2870,7 @@ issues.context.menu = ่ฏ„่ฎบ่œๅ• issues.reaction.alt_add = ๅฏน่ฏ„่ฎบๆทปๅŠ  %[1]s ๅ›žๅบ”ใ€‚ release.summary_card_alt = ไป“ๅบ“ %[2]s ไธญๆ ‡้ข˜ไธบ %[1]s ็š„็‰ˆๆœฌๅ‘ๅธƒ็š„ๆ‘˜่ฆๅก็‰‡ summary_card_alt = ไป“ๅบ“ %s ็š„ๆ‘˜่ฆๅก็‰‡ +editor.commit_email = ๆไบค็”ตๅญ้‚ฎไปถ [graphs] component_loading=ๆญฃๅœจๅŠ ่ฝฝ %sโ€ฆ @@ -3001,6 +3006,8 @@ teams.invite.by=้‚€่ฏทไบบ %s teams.invite.description=่ฏท็‚นๅ‡ปไธ‹้ข็š„ๆŒ‰้’ฎๅŠ ๅ…ฅๅ›ข้˜Ÿใ€‚ follow_blocked_user = ไฝ ๆ— ๆณ•ๅ…ณๆณจๆญค็ป„็ป‡๏ผŒๅ› ไธบๆญค็ป„็ป‡ๅทฒๅฑ่”ฝไฝ ใ€‚ open_dashboard = ๆ‰“ๅผ€ไปช่กจ็›˜ +settings.change_orgname_redirect_prompt.with_cooldown.one = ๆ—ง็š„็”จๆˆทๅๅฐ†ๅœจ%[1]dๅคฉ็š„ๅ†ทๅดๆœŸๅŽๅฏนๆ‰€ๆœ‰ไบบๅฏ็”จ๏ผŒๆ‚จไปๅฏไปฅๅœจๆญคๆœŸ้—ด้‡ๆ–ฐ่ฎค้ข†ๆ—ง็š„็”จๆˆทๅใ€‚ +settings.change_orgname_redirect_prompt.with_cooldown.few = ๆ—ง็š„็”จๆˆทๅๅฐ†ๅœจ%[1]dๅคฉ็š„ๅ†ทๅดๆœŸๅŽๅฏนๆ‰€ๆœ‰ไบบๅฏ็”จ๏ผŒๆ‚จไปๅฏไปฅๅœจๆญคๆœŸ้—ด้‡ๆ–ฐ่ฎค้ข†ๆ—ง็š„็”จๆˆทๅใ€‚ [admin] dashboard=็ฎก็†้ขๆฟ diff --git a/package-lock.json b/package-lock.json index 33ff9157ca..21596d1f45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -62,7 +62,7 @@ "devDependencies": { "@axe-core/playwright": "4.10.1", "@eslint-community/eslint-plugin-eslint-comments": "4.4.1", - "@playwright/test": "1.49.1", + "@playwright/test": "1.50.1", "@stoplight/spectral-cli": "6.14.2", "@stylistic/eslint-plugin-js": "2.12.1", "@stylistic/stylelint-plugin": "3.1.1", @@ -86,9 +86,9 @@ "eslint-plugin-vue-scoped-css": "2.9.0", "eslint-plugin-wc": "2.2.0", "globals": "15.14.0", - "happy-dom": "16.3.0", + "happy-dom": "16.8.1", "license-checker-rseidelsohn": "4.4.2", - "markdownlint-cli": "0.43.0", + "markdownlint-cli": "0.44.0", "postcss-html": "1.8.0", "stylelint": "16.12.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", @@ -3350,13 +3350,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.49.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz", - "integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==", + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.50.1.tgz", + "integrity": "sha512-Jii3aBg+CEDpgnuDxEp/h7BimHcUTDlpEtce89xEumlJ5ef2hqepZ+PWp1DDpYC/VO9fmWVI1IlEaoI5fK9FXQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.49.1" + "playwright": "1.50.1" }, "bin": { "playwright": "cli.js" @@ -4533,6 +4533,16 @@ "@types/d3-selection": "*" } }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/doctrine": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.9.tgz", @@ -4594,6 +4604,13 @@ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "license": "MIT" }, + "node_modules/@types/katex": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/markdown-escape": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@types/markdown-escape/-/markdown-escape-1.1.3.tgz", @@ -4607,6 +4624,13 @@ "integrity": "sha512-a79Yc3TOk6dGdituy8hmTTJXjOkZ7zsFYV10L337ttq/rec8lRMDBpV7fL3uLx6TgbFCa5DU/h8FmIBQPSbU0w==", "license": "MIT" }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.10.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.7.tgz", @@ -4646,6 +4670,13 @@ "license": "MIT", "optional": true }, + "node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/urijs": { "version": "1.19.25", "resolved": "https://registry.npmjs.org/@types/urijs/-/urijs-1.19.25.tgz", @@ -6143,6 +6174,39 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chart.js": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.5.tgz", @@ -7271,6 +7335,20 @@ } } }, + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/decode-uri-component": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", @@ -7363,6 +7441,30 @@ "node": ">= 0.6.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -9155,9 +9257,9 @@ } }, "node_modules/happy-dom": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-16.3.0.tgz", - "integrity": "sha512-Q71RaIhyS21vhW17Tpa5W36yqQXIlE1TZ0A0Gguts8PShUSQE/7fBgxYGxgm3+5y0gF6afdlAVHLQqgrIcfRzg==", + "version": "16.8.1", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-16.8.1.tgz", + "integrity": "sha512-n0QrmT9lD81rbpKsyhnlz3DgnMZlaOkJPpgi746doA+HvaMC79bdWkwjrNnGJRvDrWTI8iOcJiVTJ5CdT/AZRw==", "dev": true, "license": "MIT", "dependencies": { @@ -9527,6 +9629,32 @@ "node": ">=10.13.0" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -9704,6 +9832,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -9783,6 +9922,17 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-js-type": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-js-type/-/is-js-type-3.0.0.tgz", @@ -10796,14 +10946,21 @@ } }, "node_modules/markdownlint": { - "version": "0.36.1", - "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.36.1.tgz", - "integrity": "sha512-s73fU2CQN7WCgjhaQUQ8wYESQNzGRNOKDd+3xgVqu8kuTEhmwepd/mxOv1LR2oV046ONrTLBFsM7IoKWNvmy5g==", + "version": "0.37.4", + "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.37.4.tgz", + "integrity": "sha512-u00joA/syf3VhWh6/ybVFkib5Zpj2e5KB/cfCei8fkSRuums6nyisTWGqjTWIOFoFwuXoTBQQiqlB4qFKp8ncQ==", "dev": true, "license": "MIT", "dependencies": { "markdown-it": "14.1.0", - "markdownlint-micromark": "0.1.12" + "micromark": "4.0.1", + "micromark-core-commonmark": "2.0.2", + "micromark-extension-directive": "3.0.2", + "micromark-extension-gfm-autolink-literal": "2.1.0", + "micromark-extension-gfm-footnote": "2.1.0", + "micromark-extension-gfm-table": "2.1.0", + "micromark-extension-math": "3.1.0", + "micromark-util-types": "2.0.1" }, "engines": { "node": ">=18" @@ -10813,20 +10970,20 @@ } }, "node_modules/markdownlint-cli": { - "version": "0.43.0", - "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.43.0.tgz", - "integrity": "sha512-6vwurKK4B21eyYzwgX6ph13cZS7hE6LZfcS8QyD722CyxVD2RtAvbZK2p7k+FZbbKORulEuwl+hJaEq1l6/hoQ==", + "version": "0.44.0", + "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.44.0.tgz", + "integrity": "sha512-ZJTAONlvF9NkrIBltCdW15DxN9UTbPiKMEqAh2EU2gwIFlrCMavyCEPPO121cqfYOrLUJWW8/XKWongstmmTeQ==", "dev": true, "license": "MIT", "dependencies": { - "commander": "~12.1.0", - "glob": "~11.0.0", - "ignore": "~6.0.2", - "js-yaml": "^4.1.0", + "commander": "~13.1.0", + "glob": "~10.4.5", + "ignore": "~7.0.3", + "js-yaml": "~4.1.0", "jsonc-parser": "~3.3.1", - "jsonpointer": "5.0.1", - "markdownlint": "~0.36.1", - "minimatch": "~10.0.1", + "jsonpointer": "~5.0.1", + "markdownlint": "~0.37.4", + "minimatch": "~9.0.5", "run-con": "~1.3.2", "smol-toml": "~1.3.1" }, @@ -10838,9 +10995,9 @@ } }, "node_modules/markdownlint-cli/node_modules/commander": { - "version": "12.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", - "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", "dev": true, "license": "MIT", "engines": { @@ -10848,55 +11005,36 @@ } }, "node_modules/markdownlint-cli/node_modules/glob": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.1.tgz", - "integrity": "sha512-zrQDm8XPnYEKawJScsnM0QzobJxlT/kHOOlRTio8IH/GrmxRE5fjllkzdaHclIuNjUQTJYH2xHNIGfdpJkDJUw==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^4.0.1", - "minimatch": "^10.0.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", - "path-scurry": "^2.0.0" + "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": "20 || >=22" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/markdownlint-cli/node_modules/ignore": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-6.0.2.tgz", - "integrity": "sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==", + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.3.tgz", + "integrity": "sha512-bAH5jbK/F3T3Jls4I0SO1hmPR0dKU0a7+SY6n1yzRtG54FLO8d6w/nxLFX2Nb7dBu6cCWXPaAME6cYqFUMmuCA==", "dev": true, "license": "MIT", "engines": { "node": ">= 4" } }, - "node_modules/markdownlint-cli/node_modules/jackspeak": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz", - "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "engines": { - "node": "20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/markdownlint-cli/node_modules/jsonc-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", @@ -10904,46 +11042,22 @@ "dev": true, "license": "MIT" }, - "node_modules/markdownlint-cli/node_modules/lru-cache": { - "version": "11.0.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.2.tgz", - "integrity": "sha512-123qHRfJBmo2jXDbo/a5YOQrJoHF/GNQTLzQ5+IdK5pWpceK17yRc6ozlWd25FxvGKQbIUs91fDFkXmDHTKcyA==", + "node_modules/markdownlint-cli/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/markdownlint-cli/node_modules/path-scurry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", - "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", - "dev": true, - "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^11.0.0", - "minipass": "^7.1.2" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "20 || >=22" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/markdownlint-micromark": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/markdownlint-micromark/-/markdownlint-micromark-0.1.12.tgz", - "integrity": "sha512-RlB6EwMGgc0sxcIhOQ2+aq7Zw1V2fBnzbXKGgYK/mVWdT7cz34fteKSwfYeo4rL6+L/q2tyC9QtD/PgZbkdyJQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/DavidAnson" - } - }, "node_modules/marked": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", @@ -11059,6 +11173,542 @@ "node": ">= 18" } }, + "node_modules/micromark": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.1.tgz", + "integrity": "sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz", + "integrity": "sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-directive": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", + "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "parse-entities": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "dev": true, + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.0.tgz", + "integrity": "sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==", + "dev": true, + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.4.tgz", + "integrity": "sha512-N6hXjrin2GTJDe3MVjf5FuXpm12PGm80BrUAeub9XFXca8JZbP+oIwY4LJSVwFUCL1IPm/WwSVUN7goFHmSGGQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.1.tgz", + "integrity": "sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -11581,6 +12231,26 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -11817,13 +12487,13 @@ } }, "node_modules/playwright": { - "version": "1.49.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz", - "integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==", + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.50.1.tgz", + "integrity": "sha512-G8rwsOQJ63XG6BbKj2w5rHeavFjy5zynBA9zsJMMtBoe/Uf757oG12NXz6e6OirF7RCrTVAKFXbLmn1RbL7Qaw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.49.1" + "playwright-core": "1.50.1" }, "bin": { "playwright": "cli.js" @@ -11836,9 +12506,9 @@ } }, "node_modules/playwright-core": { - "version": "1.49.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz", - "integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==", + "version": "1.50.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.50.1.tgz", + "integrity": "sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==", "dev": true, "license": "Apache-2.0", "bin": { diff --git a/package.json b/package.json index 95129444fb..0736be5026 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "devDependencies": { "@axe-core/playwright": "4.10.1", "@eslint-community/eslint-plugin-eslint-comments": "4.4.1", - "@playwright/test": "1.49.1", + "@playwright/test": "1.50.1", "@stoplight/spectral-cli": "6.14.2", "@stylistic/eslint-plugin-js": "2.12.1", "@stylistic/stylelint-plugin": "3.1.1", @@ -85,9 +85,9 @@ "eslint-plugin-vue-scoped-css": "2.9.0", "eslint-plugin-wc": "2.2.0", "globals": "15.14.0", - "happy-dom": "16.3.0", + "happy-dom": "16.8.1", "license-checker-rseidelsohn": "4.4.2", - "markdownlint-cli": "0.43.0", + "markdownlint-cli": "0.44.0", "postcss-html": "1.8.0", "stylelint": "16.12.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", diff --git a/renovate.json b/renovate.json index c34ae1aaba..7efe4123b0 100644 --- a/renovate.json +++ b/renovate.json @@ -62,6 +62,7 @@ "description": "Group nodejs packages", "matchPackageNames": [ "code.forgejo.org/oci/node", + "data.forgejo.org/oci/node", "docker.io/library/node", "docker.io/node", "node" diff --git a/routers/api/actions/runner/utils.go b/routers/api/actions/runner/utils.go index 539be8d889..0fd7ca5c44 100644 --- a/routers/api/actions/runner/utils.go +++ b/routers/api/actions/runner/utils.go @@ -8,14 +8,8 @@ import ( "fmt" actions_model "code.gitea.io/gitea/models/actions" - "code.gitea.io/gitea/models/db" secret_model "code.gitea.io/gitea/models/secret" - actions_module "code.gitea.io/gitea/modules/actions" - "code.gitea.io/gitea/modules/container" - "code.gitea.io/gitea/modules/git" - "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" - "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/services/actions" runnerv1 "code.gitea.io/actions-proto-go/runner/v1" @@ -65,82 +59,16 @@ func pickTask(ctx context.Context, runner *actions_model.ActionRunner) (*runnerv } func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct { - event := map[string]any{} - _ = json.Unmarshal([]byte(t.Job.Run.EventPayload), &event) - - // TriggerEvent is added in https://github.com/go-gitea/gitea/pull/25229 - // This fallback is for the old ActionRun that doesn't have the TriggerEvent field - // and should be removed in 1.22 - eventName := t.Job.Run.TriggerEvent - if eventName == "" { - eventName = t.Job.Run.Event.Event() - } - - baseRef := "" - headRef := "" - ref := t.Job.Run.Ref - sha := t.Job.Run.CommitSHA - if pullPayload, err := t.Job.Run.GetPullRequestEventPayload(); err == nil && pullPayload.PullRequest != nil && pullPayload.PullRequest.Base != nil && pullPayload.PullRequest.Head != nil { - baseRef = pullPayload.PullRequest.Base.Ref - headRef = pullPayload.PullRequest.Head.Ref - - // if the TriggerEvent is pull_request_target, ref and sha need to be set according to the base of pull request - // In GitHub's documentation, ref should be the branch or tag that triggered workflow. But when the TriggerEvent is pull_request_target, - // the ref will be the base branch. - if t.Job.Run.TriggerEvent == actions_module.GithubEventPullRequestTarget { - ref = git.BranchPrefix + pullPayload.PullRequest.Base.Name - sha = pullPayload.PullRequest.Base.Sha - } - } - - refName := git.RefName(ref) - giteaRuntimeToken, err := actions.CreateAuthorizationToken(t.ID, t.Job.RunID, t.JobID) if err != nil { log.Error("actions.CreateAuthorizationToken failed: %v", err) } - taskContext, err := structpb.NewStruct(map[string]any{ - // standard contexts, see https://docs.github.com/en/actions/learn-github-actions/contexts#github-context - "action": "", // string, The name of the action currently running, or the id of a step. GitHub removes special characters, and uses the name __run when the current step runs a script without an id. If you use the same action more than once in the same job, the name will include a suffix with the sequence number with underscore before it. For example, the first script you run will have the name __run, and the second script will be named __run_2. Similarly, the second invocation of actions/checkout will be actionscheckout2. - "action_path": "", // string, The path where an action is located. This property is only supported in composite actions. You can use this path to access files located in the same repository as the action. - "action_ref": "", // string, For a step executing an action, this is the ref of the action being executed. For example, v2. - "action_repository": "", // string, For a step executing an action, this is the owner and repository name of the action. For example, actions/checkout. - "action_status": "", // string, For a composite action, the current result of the composite action. - "actor": t.Job.Run.TriggerUser.Name, // string, The username of the user that triggered the initial workflow run. If the workflow run is a re-run, this value may differ from github.triggering_actor. Any workflow re-runs will use the privileges of github.actor, even if the actor initiating the re-run (github.triggering_actor) has different privileges. - "api_url": setting.AppURL + "api/v1", // string, The URL of the GitHub REST API. - "base_ref": baseRef, // string, The base_ref or target branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either pull_request or pull_request_target. - "env": "", // string, Path on the runner to the file that sets environment variables from workflow commands. This file is unique to the current step and is a different file for each step in a job. For more information, see "Workflow commands for GitHub Actions." - "event": event, // object, The full event webhook payload. You can access individual properties of the event using this context. This object is identical to the webhook payload of the event that triggered the workflow run, and is different for each event. The webhooks for each GitHub Actions event is linked in "Events that trigger workflows." For example, for a workflow run triggered by the push event, this object contains the contents of the push webhook payload. - "event_name": eventName, // string, The name of the event that triggered the workflow run. - "event_path": "", // string, The path to the file on the runner that contains the full event webhook payload. - "graphql_url": "", // string, The URL of the GitHub GraphQL API. - "head_ref": headRef, // string, The head_ref or source branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either pull_request or pull_request_target. - "job": fmt.Sprint(t.JobID), // string, The job_id of the current job. - "ref": ref, // string, The fully-formed ref of the branch or tag that triggered the workflow run. For workflows triggered by push, this is the branch or tag ref that was pushed. For workflows triggered by pull_request, this is the pull request merge branch. For workflows triggered by release, this is the release tag created. For other triggers, this is the branch or tag ref that triggered the workflow run. This is only set if a branch or tag is available for the event type. The ref given is fully-formed, meaning that for branches the format is refs/heads/, for pull requests it is refs/pull//merge, and for tags it is refs/tags/. For example, refs/heads/feature-branch-1. - "ref_name": refName.ShortName(), // string, The short ref name of the branch or tag that triggered the workflow run. This value matches the branch or tag name shown on GitHub. For example, feature-branch-1. - "ref_protected": false, // boolean, true if branch protections are configured for the ref that triggered the workflow run. - "ref_type": refName.RefType(), // string, The type of ref that triggered the workflow run. Valid values are branch or tag. - "path": "", // string, Path on the runner to the file that sets system PATH variables from workflow commands. This file is unique to the current step and is a different file for each step in a job. For more information, see "Workflow commands for GitHub Actions." - "repository": t.Job.Run.Repo.OwnerName + "/" + t.Job.Run.Repo.Name, // string, The owner and repository name. For example, Codertocat/Hello-World. - "repository_owner": t.Job.Run.Repo.OwnerName, // string, The repository owner's name. For example, Codertocat. - "repositoryUrl": t.Job.Run.Repo.HTMLURL(), // string, The Git URL to the repository. For example, git://github.com/codertocat/hello-world.git. - "retention_days": "", // string, The number of days that workflow run logs and artifacts are kept. - "run_id": fmt.Sprint(t.Job.RunID), // string, A unique number for each workflow run within a repository. This number does not change if you re-run the workflow run. - "run_number": fmt.Sprint(t.Job.Run.Index), // string, A unique number for each run of a particular workflow in a repository. This number begins at 1 for the workflow's first run, and increments with each new run. This number does not change if you re-run the workflow run. - "run_attempt": fmt.Sprint(t.Job.Attempt), // string, A unique number for each attempt of a particular workflow run in a repository. This number begins at 1 for the workflow run's first attempt, and increments with each re-run. - "secret_source": "Actions", // string, The source of a secret used in a workflow. Possible values are None, Actions, Dependabot, or Codespaces. - "server_url": setting.AppURL, // string, The URL of the GitHub server. For example: https://github.com. - "sha": sha, // string, The commit SHA that triggered the workflow. The value of this commit SHA depends on the event that triggered the workflow. For more information, see "Events that trigger workflows." For example, ffac537e6cbbf934b08745a378932722df287a53. - "token": t.Token, // string, A token to authenticate on behalf of the GitHub App installed on your repository. This is functionally equivalent to the GITHUB_TOKEN secret. For more information, see "Automatic token authentication." - "triggering_actor": "", // string, The username of the user that initiated the workflow run. If the workflow run is a re-run, this value may differ from github.actor. Any workflow re-runs will use the privileges of github.actor, even if the actor initiating the re-run (github.triggering_actor) has different privileges. - "workflow": t.Job.Run.WorkflowID, // string, The name of the workflow. If the workflow file doesn't specify a name, the value of this property is the full path of the workflow file in the repository. - "workspace": "", // string, The default working directory on the runner for steps, and the default location of your repository when using the checkout action. + gitCtx := actions.GenerateGiteaContext(t.Job.Run, t.Job) + gitCtx["token"] = t.Token + gitCtx["gitea_runtime_token"] = giteaRuntimeToken - // additional contexts - "gitea_default_actions_url": setting.Actions.DefaultActionsURL.URL(), - "gitea_runtime_token": giteaRuntimeToken, - }) + taskContext, err := structpb.NewStruct(gitCtx) if err != nil { log.Error("structpb.NewStruct failed: %v", err) } @@ -150,68 +78,18 @@ func generateTaskContext(t *actions_model.ActionTask) *structpb.Struct { func findTaskNeeds(ctx context.Context, task *actions_model.ActionTask) (map[string]*runnerv1.TaskNeed, error) { if err := task.LoadAttributes(ctx); err != nil { - return nil, fmt.Errorf("LoadAttributes: %w", err) + return nil, fmt.Errorf("task LoadAttributes: %w", err) } - if len(task.Job.Needs) == 0 { - return nil, nil - } - needs := container.SetOf(task.Job.Needs...) - - jobs, err := db.Find[actions_model.ActionRunJob](ctx, actions_model.FindRunJobOptions{RunID: task.Job.RunID}) + taskNeeds, err := actions.FindTaskNeeds(ctx, task.Job) if err != nil { - return nil, fmt.Errorf("FindRunJobs: %w", err) + return nil, err } - - jobIDJobs := make(map[string][]*actions_model.ActionRunJob) - for _, job := range jobs { - jobIDJobs[job.JobID] = append(jobIDJobs[job.JobID], job) - } - - ret := make(map[string]*runnerv1.TaskNeed, len(needs)) - for jobID, jobsWithSameID := range jobIDJobs { - if !needs.Contains(jobID) { - continue - } - var jobOutputs map[string]string - for _, job := range jobsWithSameID { - if job.TaskID == 0 || !job.Status.IsDone() { - // it shouldn't happen, or the job has been rerun - continue - } - got, err := actions_model.FindTaskOutputByTaskID(ctx, job.TaskID) - if err != nil { - return nil, fmt.Errorf("FindTaskOutputByTaskID: %w", err) - } - outputs := make(map[string]string, len(got)) - for _, v := range got { - outputs[v.OutputKey] = v.OutputValue - } - if len(jobOutputs) == 0 { - jobOutputs = outputs - } else { - jobOutputs = mergeTwoOutputs(outputs, jobOutputs) - } - } + ret := make(map[string]*runnerv1.TaskNeed, len(taskNeeds)) + for jobID, taskNeed := range taskNeeds { ret[jobID] = &runnerv1.TaskNeed{ - Outputs: jobOutputs, - Result: runnerv1.Result(actions_model.AggregateJobStatus(jobsWithSameID)), + Outputs: taskNeed.Outputs, + Result: runnerv1.Result(taskNeed.Result), } } - return ret, nil } - -// mergeTwoOutputs merges two outputs from two different ActionRunJobs -// Values with the same output name may be overridden. The user should ensure the output names are unique. -// See https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#using-job-outputs-in-a-matrix-job -func mergeTwoOutputs(o1, o2 map[string]string) map[string]string { - ret := make(map[string]string, len(o1)) - for k1, v1 := range o1 { - if len(v1) > 0 { - ret[k1] = v1 - } else { - ret[k1] = o2[k1] - } - } - return ret -} diff --git a/routers/api/v1/misc/markup_test.go b/routers/api/v1/misc/markup_test.go index df70ee49ef..32568b805f 100644 --- a/routers/api/v1/misc/markup_test.go +++ b/routers/api/v1/misc/markup_test.go @@ -112,7 +112,7 @@ Here are some links to the most important topics. You can find the full list of

Here are some links to the most important topics. You can find the full list of pages at the sidebar.

Configuration -images/icon-bug.png

+

`, } diff --git a/routers/api/v1/user/gpg_key.go b/routers/api/v1/user/gpg_key.go index 2fe4eb8e78..652007a78b 100644 --- a/routers/api/v1/user/gpg_key.go +++ b/routers/api/v1/user/gpg_key.go @@ -303,11 +303,7 @@ func DeleteGPGKey(ctx *context.APIContext) { } if err := asymkey_model.DeleteGPGKey(ctx, ctx.Doer, ctx.ParamsInt64(":id")); err != nil { - if asymkey_model.IsErrGPGKeyAccessDenied(err) { - ctx.Error(http.StatusForbidden, "", "You do not have access to this key") - } else { - ctx.Error(http.StatusInternalServerError, "DeleteGPGKey", err) - } + ctx.Error(http.StatusInternalServerError, "DeleteGPGKey", err) return } @@ -317,8 +313,6 @@ func DeleteGPGKey(ctx *context.APIContext) { // HandleAddGPGKeyError handle add GPGKey error func HandleAddGPGKeyError(ctx *context.APIContext, err error, token string) { switch { - case asymkey_model.IsErrGPGKeyAccessDenied(err): - ctx.Error(http.StatusUnprocessableEntity, "GPGKeyAccessDenied", "You do not have access to this GPG key") case asymkey_model.IsErrGPGKeyIDAlreadyUsed(err): ctx.Error(http.StatusUnprocessableEntity, "GPGKeyIDAlreadyUsed", "A key with the same id already exists") case asymkey_model.IsErrGPGKeyParsing(err): diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index 6bfc35cb99..36ce8d286c 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -83,6 +83,7 @@ func Users(ctx *context.Context) { IsTwoFactorEnabled: util.OptionalBoolParse(statusFilterMap["is_2fa_enabled"]), IsProhibitLogin: util.OptionalBoolParse(statusFilterMap["is_prohibit_login"]), IncludeReserved: true, // administrator needs to list all accounts include reserved, bot, remote ones + Load2FAStatus: true, ExtraParamStrings: extraParamStrings, }, tplUsers) } diff --git a/routers/web/auth/auth.go b/routers/web/auth/auth.go index 5cb4ebb440..ca8f06bdb1 100644 --- a/routers/web/auth/auth.go +++ b/routers/web/auth/auth.go @@ -225,15 +225,6 @@ func SignInPost(ctx *context.Context) { log.Warn("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err) ctx.Data["Title"] = ctx.Tr("auth.prohibit_login") ctx.HTML(http.StatusOK, "user/auth/prohibit_login") - } else if user_model.IsErrUserInactive(err) { - if setting.Service.RegisterEmailConfirm { - ctx.Data["Title"] = ctx.Tr("auth.active_your_account") - ctx.HTML(http.StatusOK, TplActivate) - } else { - log.Warn("Failed authentication attempt for %s from %s: %v", form.UserName, ctx.RemoteAddr(), err) - ctx.Data["Title"] = ctx.Tr("auth.prohibit_login") - ctx.HTML(http.StatusOK, "user/auth/prohibit_login") - } } else { ctx.ServerError("UserSignIn", err) } diff --git a/routers/web/auth/linkaccount.go b/routers/web/auth/linkaccount.go index 9b0141c14e..8dce0a30a4 100644 --- a/routers/web/auth/linkaccount.go +++ b/routers/web/auth/linkaccount.go @@ -99,16 +99,6 @@ func handleSignInError(ctx *context.Context, userName string, ptrForm any, tmpl log.Info("Failed authentication attempt for %s from %s: %v", userName, ctx.RemoteAddr(), err) ctx.Data["Title"] = ctx.Tr("auth.prohibit_login") ctx.HTML(http.StatusOK, "user/auth/prohibit_login") - } else if user_model.IsErrUserInactive(err) { - ctx.Data["user_exists"] = true - if setting.Service.RegisterEmailConfirm { - ctx.Data["Title"] = ctx.Tr("auth.active_your_account") - ctx.HTML(http.StatusOK, TplActivate) - } else { - log.Info("Failed authentication attempt for %s from %s: %v", userName, ctx.RemoteAddr(), err) - ctx.Data["Title"] = ctx.Tr("auth.prohibit_login") - ctx.HTML(http.StatusOK, "user/auth/prohibit_login") - } } else { ctx.ServerError(invoker, err) } diff --git a/routers/web/explore/org.go b/routers/web/explore/org.go index 7178630b64..d13271ae53 100644 --- a/routers/web/explore/org.go +++ b/routers/web/explore/org.go @@ -39,7 +39,11 @@ func Organizations(ctx *context.Context) { ) sortOrder := ctx.FormString("sort") if sortOrder == "" { - sortOrder = "newest" + if supportedSortOrders.Contains(setting.UI.ExploreDefaultSort) { + sortOrder = setting.UI.ExploreDefaultSort + } else { + sortOrder = "newest" + } ctx.SetFormString("sort", sortOrder) } diff --git a/routers/web/explore/user.go b/routers/web/explore/user.go index 15c60f546f..241e5f61a1 100644 --- a/routers/web/explore/user.go +++ b/routers/web/explore/user.go @@ -114,7 +114,9 @@ func RenderUserSearch(ctx *context.Context, opts *user_model.SearchUserOptions, ctx.Data["Keyword"] = opts.Keyword ctx.Data["Total"] = count ctx.Data["Users"] = users - ctx.Data["UsersTwoFaStatus"] = user_model.UserList(users).GetTwoFaStatus(ctx) + if opts.Load2FAStatus { + ctx.Data["UsersTwoFaStatus"] = user_model.UserList(users).GetTwoFaStatus(ctx) + } ctx.Data["ShowUserEmail"] = setting.UI.ShowUserEmail ctx.Data["IsRepoIndexerEnabled"] = setting.Indexer.RepoIndexerEnabled @@ -149,7 +151,11 @@ func Users(ctx *context.Context) { ) sortOrder := ctx.FormString("sort") if sortOrder == "" { - sortOrder = "newest" + if supportedSortOrders.Contains(setting.UI.ExploreDefaultSort) { + sortOrder = setting.UI.ExploreDefaultSort + } else { + sortOrder = "newest" + } ctx.SetFormString("sort", sortOrder) } diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go index 4f962d4c19..c7fbaaefcb 100644 --- a/routers/web/repo/blame.go +++ b/routers/web/repo/blame.go @@ -56,6 +56,11 @@ func RefBlame(ctx *context.Context) { HandleGitError(ctx, "Repo.Commit.GetTreeEntryByPath", err) return } + if entry.IsDir() { + ctx.NotFound("Cannot blame directory", nil) + return + } + blob := entry.Blob() ctx.Data["PageIsViewCode"] = true diff --git a/routers/web/user/home.go b/routers/web/user/home.go index c59dcf5c25..d67af29071 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -10,7 +10,6 @@ import ( "net/http" "regexp" "slices" - "sort" "strconv" "strings" @@ -242,7 +241,9 @@ func Milestones(ctx *context.Context) { ctx.ServerError("SearchRepositoryByCondition", err) return } - sort.Sort(showRepos) + slices.SortFunc(showRepos, func(a, b *repo_model.Repository) int { + return strings.Compare(a.FullName(), b.FullName()) + }) for i := 0; i < len(milestones); { for _, repo := range showRepos { diff --git a/routers/web/user/home_test.go b/routers/web/user/home_test.go index e1c8ca9a79..c09f609161 100644 --- a/routers/web/user/home_test.go +++ b/routers/web/user/home_test.go @@ -98,6 +98,8 @@ func TestMilestones(t *testing.T) { assert.EqualValues(t, 1, ctx.Data["Total"]) assert.Len(t, ctx.Data["Milestones"], 1) assert.Len(t, ctx.Data["Repos"], 2) // both repo 42 and 1 have milestones and both are owned by user 2 + assert.EqualValues(t, "user2/glob", ctx.Data["Repos"].(repo_model.RepositoryList)[0].FullName()) + assert.EqualValues(t, "user2/repo1", ctx.Data["Repos"].(repo_model.RepositoryList)[1].FullName()) } func TestMilestonesForSpecificRepo(t *testing.T) { diff --git a/routers/web/user/package.go b/routers/web/user/package.go index 707c86db7a..70ea20d388 100644 --- a/routers/web/user/package.go +++ b/routers/web/user/package.go @@ -6,6 +6,7 @@ package user import ( "fmt" "net/http" + "slices" "code.gitea.io/gitea/models/db" org_model "code.gitea.io/gitea/models/organization" @@ -23,7 +24,6 @@ import ( debian_module "code.gitea.io/gitea/modules/packages/debian" rpm_module "code.gitea.io/gitea/modules/packages/rpm" "code.gitea.io/gitea/modules/setting" - "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" packages_helper "code.gitea.io/gitea/routers/api/packages/helper" shared_user "code.gitea.io/gitea/routers/web/shared/user" @@ -200,9 +200,9 @@ func ViewPackageVersion(ctx *context.Context) { } } - ctx.Data["Branches"] = util.Sorted(branches.Values()) - ctx.Data["Repositories"] = util.Sorted(repositories.Values()) - ctx.Data["Architectures"] = util.Sorted(architectures.Values()) + ctx.Data["Branches"] = slices.Sorted(branches.Seq()) + ctx.Data["Repositories"] = slices.Sorted(repositories.Seq()) + ctx.Data["Architectures"] = slices.Sorted(architectures.Seq()) case packages_model.TypeArch: ctx.Data["SignMail"] = fmt.Sprintf("%s@noreply.%s", ctx.Package.Owner.Name, setting.Packages.RegistryHost) groups := make(container.Set[string]) @@ -213,7 +213,7 @@ func ViewPackageVersion(ctx *context.Context) { } } } - ctx.Data["Groups"] = util.Sorted(groups.Values()) + ctx.Data["Groups"] = slices.Sorted(groups.Seq()) case packages_model.TypeDebian: distributions := make(container.Set[string]) components := make(container.Set[string]) @@ -232,9 +232,9 @@ func ViewPackageVersion(ctx *context.Context) { } } - ctx.Data["Distributions"] = util.Sorted(distributions.Values()) - ctx.Data["Components"] = util.Sorted(components.Values()) - ctx.Data["Architectures"] = util.Sorted(architectures.Values()) + ctx.Data["Distributions"] = slices.Sorted(distributions.Seq()) + ctx.Data["Components"] = slices.Sorted(components.Seq()) + ctx.Data["Architectures"] = slices.Sorted(architectures.Seq()) case packages_model.TypeRpm, packages_model.TypeAlt: groups := make(container.Set[string]) architectures := make(container.Set[string]) @@ -250,8 +250,8 @@ func ViewPackageVersion(ctx *context.Context) { } } - ctx.Data["Groups"] = util.Sorted(groups.Values()) - ctx.Data["Architectures"] = util.Sorted(architectures.Values()) + ctx.Data["Groups"] = slices.Sorted(groups.Seq()) + ctx.Data["Architectures"] = slices.Sorted(architectures.Seq()) } var ( diff --git a/services/actions/context.go b/services/actions/context.go new file mode 100644 index 0000000000..be1c85522b --- /dev/null +++ b/services/actions/context.go @@ -0,0 +1,161 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package actions + +import ( + "context" + "fmt" + + actions_model "code.gitea.io/gitea/models/actions" + "code.gitea.io/gitea/models/db" + actions_module "code.gitea.io/gitea/modules/actions" + "code.gitea.io/gitea/modules/container" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/json" + "code.gitea.io/gitea/modules/setting" +) + +// GenerateGiteaContext generate the gitea context without token and gitea_runtime_token +// job can be nil when generating a context for parsing workflow-level expressions +func GenerateGiteaContext(run *actions_model.ActionRun, job *actions_model.ActionRunJob) map[string]any { + event := map[string]any{} + _ = json.Unmarshal([]byte(run.EventPayload), &event) + + baseRef := "" + headRef := "" + ref := run.Ref + sha := run.CommitSHA + if pullPayload, err := run.GetPullRequestEventPayload(); err == nil && pullPayload.PullRequest != nil && pullPayload.PullRequest.Base != nil && pullPayload.PullRequest.Head != nil { + baseRef = pullPayload.PullRequest.Base.Ref + headRef = pullPayload.PullRequest.Head.Ref + + // if the TriggerEvent is pull_request_target, ref and sha need to be set according to the base of pull request + // In GitHub's documentation, ref should be the branch or tag that triggered workflow. But when the TriggerEvent is pull_request_target, + // the ref will be the base branch. + if run.TriggerEvent == actions_module.GithubEventPullRequestTarget { + ref = git.BranchPrefix + pullPayload.PullRequest.Base.Name + sha = pullPayload.PullRequest.Base.Sha + } + } + + refName := git.RefName(ref) + + gitContext := map[string]any{ + // standard contexts, see https://docs.github.com/en/actions/learn-github-actions/contexts#github-context + "action": "", // string, The name of the action currently running, or the id of a step. GitHub removes special characters, and uses the name __run when the current step runs a script without an id. If you use the same action more than once in the same job, the name will include a suffix with the sequence number with underscore before it. For example, the first script you run will have the name __run, and the second script will be named __run_2. Similarly, the second invocation of actions/checkout will be actionscheckout2. + "action_path": "", // string, The path where an action is located. This property is only supported in composite actions. You can use this path to access files located in the same repository as the action. + "action_ref": "", // string, For a step executing an action, this is the ref of the action being executed. For example, v2. + "action_repository": "", // string, For a step executing an action, this is the owner and repository name of the action. For example, actions/checkout. + "action_status": "", // string, For a composite action, the current result of the composite action. + "actor": run.TriggerUser.Name, // string, The username of the user that triggered the initial workflow run. If the workflow run is a re-run, this value may differ from github.triggering_actor. Any workflow re-runs will use the privileges of github.actor, even if the actor initiating the re-run (github.triggering_actor) has different privileges. + "api_url": setting.AppURL + "api/v1", // string, The URL of the GitHub REST API. + "base_ref": baseRef, // string, The base_ref or target branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either pull_request or pull_request_target. + "env": "", // string, Path on the runner to the file that sets environment variables from workflow commands. This file is unique to the current step and is a different file for each step in a job. For more information, see "Workflow commands for GitHub Actions." + "event": event, // object, The full event webhook payload. You can access individual properties of the event using this context. This object is identical to the webhook payload of the event that triggered the workflow run, and is different for each event. The webhooks for each GitHub Actions event is linked in "Events that trigger workflows." For example, for a workflow run triggered by the push event, this object contains the contents of the push webhook payload. + "event_name": run.TriggerEvent, // string, The name of the event that triggered the workflow run. + "event_path": "", // string, The path to the file on the runner that contains the full event webhook payload. + "graphql_url": "", // string, The URL of the GitHub GraphQL API. + "head_ref": headRef, // string, The head_ref or source branch of the pull request in a workflow run. This property is only available when the event that triggers a workflow run is either pull_request or pull_request_target. + "job": "", // string, The job_id of the current job. + "ref": ref, // string, The fully-formed ref of the branch or tag that triggered the workflow run. For workflows triggered by push, this is the branch or tag ref that was pushed. For workflows triggered by pull_request, this is the pull request merge branch. For workflows triggered by release, this is the release tag created. For other triggers, this is the branch or tag ref that triggered the workflow run. This is only set if a branch or tag is available for the event type. The ref given is fully-formed, meaning that for branches the format is refs/heads/, for pull requests it is refs/pull//merge, and for tags it is refs/tags/. For example, refs/heads/feature-branch-1. + "ref_name": refName.ShortName(), // string, The short ref name of the branch or tag that triggered the workflow run. This value matches the branch or tag name shown on GitHub. For example, feature-branch-1. + "ref_protected": false, // boolean, true if branch protections are configured for the ref that triggered the workflow run. + "ref_type": refName.RefType(), // string, The type of ref that triggered the workflow run. Valid values are branch or tag. + "path": "", // string, Path on the runner to the file that sets system PATH variables from workflow commands. This file is unique to the current step and is a different file for each step in a job. For more information, see "Workflow commands for GitHub Actions." + "repository": run.Repo.OwnerName + "/" + run.Repo.Name, // string, The owner and repository name. For example, Codertocat/Hello-World. + "repository_owner": run.Repo.OwnerName, // string, The repository owner's name. For example, Codertocat. + "repositoryUrl": run.Repo.HTMLURL(), // string, The Git URL to the repository. For example, git://github.com/codertocat/hello-world.git. + "retention_days": "", // string, The number of days that workflow run logs and artifacts are kept. + "run_id": "", // string, A unique number for each workflow run within a repository. This number does not change if you re-run the workflow run. + "run_number": fmt.Sprint(run.Index), // string, A unique number for each run of a particular workflow in a repository. This number begins at 1 for the workflow's first run, and increments with each new run. This number does not change if you re-run the workflow run. + "run_attempt": "", // string, A unique number for each attempt of a particular workflow run in a repository. This number begins at 1 for the workflow run's first attempt, and increments with each re-run. + "secret_source": "Actions", // string, The source of a secret used in a workflow. Possible values are None, Actions, Dependabot, or Codespaces. + "server_url": setting.AppURL, // string, The URL of the GitHub server. For example: https://github.com. + "sha": sha, // string, The commit SHA that triggered the workflow. The value of this commit SHA depends on the event that triggered the workflow. For more information, see "Events that trigger workflows." For example, ffac537e6cbbf934b08745a378932722df287a53. + "triggering_actor": "", // string, The username of the user that initiated the workflow run. If the workflow run is a re-run, this value may differ from github.actor. Any workflow re-runs will use the privileges of github.actor, even if the actor initiating the re-run (github.triggering_actor) has different privileges. + "workflow": run.WorkflowID, // string, The name of the workflow. If the workflow file doesn't specify a name, the value of this property is the full path of the workflow file in the repository. + "workspace": "", // string, The default working directory on the runner for steps, and the default location of your repository when using the checkout action. + + // additional contexts + "gitea_default_actions_url": setting.Actions.DefaultActionsURL.URL(), + } + + if job != nil { + gitContext["job"] = job.JobID + gitContext["run_id"] = fmt.Sprint(job.RunID) + gitContext["run_attempt"] = fmt.Sprint(job.Attempt) + } + + return gitContext +} + +type TaskNeed struct { + Result actions_model.Status + Outputs map[string]string +} + +// FindTaskNeeds finds the `needs` for the task by the task's job +func FindTaskNeeds(ctx context.Context, job *actions_model.ActionRunJob) (map[string]*TaskNeed, error) { + if len(job.Needs) == 0 { + return nil, nil + } + needs := container.SetOf(job.Needs...) + + jobs, err := db.Find[actions_model.ActionRunJob](ctx, actions_model.FindRunJobOptions{RunID: job.RunID}) + if err != nil { + return nil, fmt.Errorf("FindRunJobs: %w", err) + } + + jobIDJobs := make(map[string][]*actions_model.ActionRunJob) + for _, job := range jobs { + jobIDJobs[job.JobID] = append(jobIDJobs[job.JobID], job) + } + + ret := make(map[string]*TaskNeed, len(needs)) + for jobID, jobsWithSameID := range jobIDJobs { + if !needs.Contains(jobID) { + continue + } + var jobOutputs map[string]string + for _, job := range jobsWithSameID { + if job.TaskID == 0 || !job.Status.IsDone() { + // it shouldn't happen, or the job has been rerun + continue + } + got, err := actions_model.FindTaskOutputByTaskID(ctx, job.TaskID) + if err != nil { + return nil, fmt.Errorf("FindTaskOutputByTaskID: %w", err) + } + outputs := make(map[string]string, len(got)) + for _, v := range got { + outputs[v.OutputKey] = v.OutputValue + } + if len(jobOutputs) == 0 { + jobOutputs = outputs + } else { + jobOutputs = mergeTwoOutputs(outputs, jobOutputs) + } + } + ret[jobID] = &TaskNeed{ + Outputs: jobOutputs, + Result: actions_model.AggregateJobStatus(jobsWithSameID), + } + } + return ret, nil +} + +// mergeTwoOutputs merges two outputs from two different ActionRunJobs +// Values with the same output name may be overridden. The user should ensure the output names are unique. +// See https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#using-job-outputs-in-a-matrix-job +func mergeTwoOutputs(o1, o2 map[string]string) map[string]string { + ret := make(map[string]string, len(o1)) + for k1, v1 := range o1 { + if len(v1) > 0 { + ret[k1] = v1 + } else { + ret[k1] = o2[k1] + } + } + return ret +} diff --git a/routers/api/actions/runner/utils_test.go b/services/actions/context_test.go similarity index 77% rename from routers/api/actions/runner/utils_test.go rename to services/actions/context_test.go index c8a0a28d65..a80d2d84e3 100644 --- a/routers/api/actions/runner/utils_test.go +++ b/services/actions/context_test.go @@ -1,7 +1,7 @@ // Copyright 2024 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT -package runner +package actions import ( "context" @@ -14,12 +14,13 @@ import ( "github.com/stretchr/testify/require" ) -func Test_findTaskNeeds(t *testing.T) { +func TestFindTaskNeeds(t *testing.T) { require.NoError(t, unittest.PrepareTestDatabase()) task := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: 51}) + job := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: task.JobID}) - ret, err := findTaskNeeds(context.Background(), task) + ret, err := FindTaskNeeds(context.Background(), job) require.NoError(t, err) assert.Len(t, ret, 1) assert.Contains(t, ret, "job1") diff --git a/services/context/repo.go b/services/context/repo.go index d294c00455..ff03844c03 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -937,6 +937,9 @@ func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string { // of repository reference func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context) context.CancelFunc { return func(ctx *Context) (cancel context.CancelFunc) { + if ctx.Repo.Repository.IsBeingCreated() { + return nil // no git repo, so do nothing + } // Empty repository does not have reference information. if ctx.Repo.Repository.IsEmpty { // assume the user is viewing the (non-existent) default branch diff --git a/services/doctor/breaking.go b/services/doctor/breaking.go index 683ec97389..ec8433b8de 100644 --- a/services/doctor/breaking.go +++ b/services/doctor/breaking.go @@ -10,6 +10,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/validation" "xorm.io/builder" @@ -30,6 +31,8 @@ func iterateUserAccounts(ctx context.Context, each func(*user.User) error) error // addresses would be currently facing a error due to their invalid email address. // Ref: https://github.com/go-gitea/gitea/pull/19085 & https://github.com/go-gitea/gitea/pull/17688 func checkUserEmail(ctx context.Context, logger log.Logger, _ bool) error { + setting.LoadServiceSetting() + // We could use quirky SQL to get all users that start without a [a-zA-Z0-9], but that would mean // DB provider-specific SQL and only works _now_. So instead we iterate through all user accounts // and use the validation.ValidateEmail function to be future-proof. @@ -61,6 +64,8 @@ func checkUserEmail(ctx context.Context, logger log.Logger, _ bool) error { // are allowed for various reasons. This check helps with detecting users that, according // to our reserved names, don't have a valid username. func checkUserName(ctx context.Context, logger log.Logger, _ bool) error { + setting.LoadServiceSetting() + var invalidUserCount int64 if err := iterateUserAccounts(ctx, func(u *user.User) error { if err := user.IsUsableUsername(u.Name); err != nil { diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index 7d137fb214..65f6ac8d12 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -1117,7 +1117,10 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi } else { actualBeforeCommitID := opts.BeforeCommitID if len(actualBeforeCommitID) == 0 { - parentCommit, _ := commit.Parent(0) + parentCommit, err := commit.Parent(0) + if err != nil { + return nil, err + } actualBeforeCommitID = parentCommit.ID.String() } @@ -1126,7 +1129,6 @@ func GetDiff(ctx context.Context, gitRepo *git.Repository, opts *DiffOptions, fi AddDynamicArguments(actualBeforeCommitID, opts.AfterCommitID) opts.BeforeCommitID = actualBeforeCommitID - var err error beforeCommit, err = gitRepo.GetCommit(opts.BeforeCommitID) if err != nil { return nil, err diff --git a/services/mirror/mirror_pull.go b/services/mirror/mirror_pull.go index bce8386f54..b180cf498f 100644 --- a/services/mirror/mirror_pull.go +++ b/services/mirror/mirror_pull.go @@ -96,6 +96,7 @@ type mirrorSyncResult struct { /* // * [new tag] v0.1.8 -> v0.1.8 // * [new branch] master -> origin/master +// * [new ref] refs/pull/2/head -> refs/pull/2/head" // - [deleted] (none) -> origin/test // delete a branch // - [deleted] (none) -> 1 // delete a tag // 957a993..a87ba5f test -> origin/test @@ -126,6 +127,11 @@ func parseRemoteUpdateOutput(output, remoteName string) []*mirrorSyncResult { refName: git.RefNameFromBranch(refName), oldCommitID: gitShortEmptySha, }) + case strings.HasPrefix(lines[i], " * [new ref]"): // new reference + results = append(results, &mirrorSyncResult{ + refName: git.RefName(refName), + oldCommitID: gitShortEmptySha, + }) case strings.HasPrefix(lines[i], " - "): // Delete reference isTag := !strings.HasPrefix(refName, remoteName+"/") var refFullName git.RefName @@ -168,13 +174,19 @@ func parseRemoteUpdateOutput(output, remoteName string) []*mirrorSyncResult { log.Error("Expect two SHAs but not what found: %q", lines[i]) continue } + var refFullName git.RefName + if strings.HasPrefix(refName, "refs/") { + refFullName = git.RefName(refName) + } else { + refFullName = git.RefNameFromBranch(strings.TrimPrefix(refName, remoteName+"/")) + } + results = append(results, &mirrorSyncResult{ - refName: git.RefNameFromBranch(strings.TrimPrefix(refName, remoteName+"/")), + refName: refFullName, oldCommitID: shas[0], newCommitID: shas[1], }) - case strings.HasPrefix(lines[i], " * [new ref]"): // new reference - nothing to do default: log.Warn("parseRemoteUpdateOutput: unexpected update line %q", lines[i]) } diff --git a/services/mirror/mirror_test.go b/services/mirror/mirror_test.go index 860470522e..e26204e2ec 100644 --- a/services/mirror/mirror_test.go +++ b/services/mirror/mirror_test.go @@ -21,7 +21,7 @@ func Test_parseRemoteUpdateOutput(t *testing.T) { * [new ref] refs/pull/516/head -> refs/pull/516/head ` results := parseRemoteUpdateOutput(output, "origin") - assert.Len(t, results, 6) + assert.Len(t, results, 8) assert.EqualValues(t, "refs/tags/v0.1.8", results[0].refName.String()) assert.EqualValues(t, gitShortEmptySha, results[0].oldCommitID) assert.EqualValues(t, "", results[0].newCommitID) @@ -45,4 +45,12 @@ func Test_parseRemoteUpdateOutput(t *testing.T) { assert.EqualValues(t, "refs/heads/test3", results[5].refName.String()) assert.EqualValues(t, "957a993", results[5].oldCommitID) assert.EqualValues(t, "a87ba5f", results[5].newCommitID) + + assert.EqualValues(t, "refs/pull/27/merge", results[6].refName.String()) + assert.EqualValues(t, gitShortEmptySha, results[6].oldCommitID) + assert.EqualValues(t, "", results[6].newCommitID) + + assert.EqualValues(t, "refs/pull/516/head", results[7].refName.String()) + assert.EqualValues(t, gitShortEmptySha, results[7].oldCommitID) + assert.EqualValues(t, "", results[7].newCommitID) } diff --git a/services/packages/alt/repository.go b/services/packages/alt/repository.go index 7b7951eebb..f49c435e64 100644 --- a/services/packages/alt/repository.go +++ b/services/packages/alt/repository.go @@ -711,7 +711,7 @@ func buildRelease(ctx context.Context, pv *packages_model.PackageVersion, pfs [] architectures.Add(pd.FileMetadata.Architecture) } - for architecture := range architectures { + for architecture := range architectures.Seq() { version := time.Now().Unix() label := setting.AppName data := fmt.Sprintf(`Archive: Alt Linux Team diff --git a/services/release/release.go b/services/release/release.go index 876514beec..b52e4b124e 100644 --- a/services/release/release.go +++ b/services/release/release.go @@ -372,7 +372,7 @@ func UpdateRelease(ctx context.Context, doer *user_model.User, gitRepo *git.Repo return err } - for _, uuid := range delAttachmentUUIDs.Values() { + for uuid := range delAttachmentUUIDs.Seq() { if err := storage.Attachments.Delete(repo_model.AttachmentRelativePath(uuid)); err != nil { // Even delete files failed, but the attachments has been removed from database, so we // should not return error but only record the error on logs. diff --git a/templates/install.tmpl b/templates/install.tmpl index ae800df130..7a9b40826f 100644 --- a/templates/install.tmpl +++ b/templates/install.tmpl @@ -363,5 +363,5 @@ - +{{ctx.Locale.Tr {{template "base/footer" .}} diff --git a/templates/repo/diff/image_diff.tmpl b/templates/repo/diff/image_diff.tmpl index 0612854609..a793f54da1 100644 --- a/templates/repo/diff/image_diff.tmpl +++ b/templates/repo/diff/image_diff.tmpl @@ -22,7 +22,7 @@ {{if .blobBase}}

{{ctx.Locale.Tr "repo.diff.file_before"}}

- +

{{ctx.Locale.Tr "repo.diff.file_image_width"}}: @@ -37,7 +37,7 @@ {{if .blobHead}}

{{ctx.Locale.Tr "repo.diff.file_after"}}

- +

{{ctx.Locale.Tr "repo.diff.file_image_width"}}: @@ -55,9 +55,9 @@

- + {{ctx.Locale.Tr - + {{ctx.Locale.Tr @@ -70,8 +70,8 @@
- - + {{ctx.Locale.Tr + {{ctx.Locale.Tr
diff --git a/templates/repo/diff/new_review.tmpl b/templates/repo/diff/new_review.tmpl index a2eae007a5..13d09babe1 100644 --- a/templates/repo/diff/new_review.tmpl +++ b/templates/repo/diff/new_review.tmpl @@ -1,9 +1,16 @@
- +
+ +
{{if $.IsShowingAllCommits}}
diff --git a/templates/repo/editor/commit_form.tmpl b/templates/repo/editor/commit_form.tmpl index fc04289b70..c42eed69a5 100644 --- a/templates/repo/editor/commit_form.tmpl +++ b/templates/repo/editor/commit_form.tmpl @@ -67,7 +67,7 @@ {{end}}
- + - + {{ctx.Locale.Tr
diff --git a/templates/user/dashboard/feeds.tmpl b/templates/user/dashboard/feeds.tmpl index bd2a3800a2..8ae50bf385 100644 --- a/templates/user/dashboard/feeds.tmpl +++ b/templates/user/dashboard/feeds.tmpl @@ -91,7 +91,7 @@ {{range $push.Commits}} {{$commitLink := printf "%s/commit/%s" $repoLink .Sha1}}
- + {{ShortSha .Sha1}} {{RenderCommitMessage $.Context .Message ($repo.ComposeMetas ctx)}} @@ -103,11 +103,11 @@ {{ctx.Locale.Tr "action.compare_commits" $push.Len}} ยป {{end}} {{else if .GetOpType.InActions "create_issue"}} - {{index .GetIssueInfos 1 | RenderEmoji $.Context | RenderCodeBlock}} + {{RenderIssueTitle ctx (index .GetIssueInfos 1) (.Repo.ComposeMetas ctx)}} {{else if .GetOpType.InActions "create_pull_request"}} - {{index .GetIssueInfos 1 | RenderEmoji $.Context | RenderCodeBlock}} + {{RenderIssueTitle ctx (index .GetIssueInfos 1) (.Repo.ComposeMetas ctx)}} {{else if .GetOpType.InActions "comment_issue" "approve_pull_request" "reject_pull_request" "comment_pull"}} - {{(.GetIssueTitle ctx) | RenderEmoji $.Context | RenderCodeBlock}} + {{RenderIssueTitle ctx (.GetIssueTitle ctx) (.Repo.ComposeMetas ctx)}} {{$comment := index .GetIssueInfos 1}} {{if $comment}}
{{RenderMarkdownToHtml ctx $comment}}
@@ -115,7 +115,7 @@ {{else if .GetOpType.InActions "merge_pull_request"}}
{{index .GetIssueInfos 1}}
{{else if .GetOpType.InActions "close_issue" "reopen_issue" "close_pull_request" "reopen_pull_request"}} - {{(.GetIssueTitle ctx) | RenderEmoji $.Context | RenderCodeBlock}} + {{RenderIssueTitle ctx (.GetIssueTitle ctx) (.Repo.ComposeMetas ctx)}} {{else if .GetOpType.InActions "pull_review_dismissed"}}
{{ctx.Locale.Tr "action.review_dismissed_reason"}}
{{index .GetIssueInfos 2 | RenderEmoji $.Context}}
diff --git a/tests/e2e/repo-home.e2e.ts b/tests/e2e/repo-home.e2e.ts new file mode 100644 index 0000000000..fbcfe17226 --- /dev/null +++ b/tests/e2e/repo-home.e2e.ts @@ -0,0 +1,19 @@ +// @watch start +// web_src/js/features/common-global.js +// web_src/css/repo.css +// @watch end + +import {expect} from '@playwright/test'; +import {save_visual, test} from './utils_e2e.ts'; + +test('Language stats bar', async ({page}) => { + const response = await page.goto('/user2/repo1'); + expect(response?.status()).toBe(200); + + await expect(page.locator('#language-stats-legend')).toBeVisible(); + await save_visual(page); + + await page.click('#language-stats-bar'); + await expect(page.locator('#language-stats-legend')).toBeHidden(); + await save_visual(page); +}); diff --git a/tests/integration/actions_job_test.go b/tests/integration/actions_job_test.go index 5af3519b93..545ab37bf6 100644 --- a/tests/integration/actions_job_test.go +++ b/tests/integration/actions_job_test.go @@ -4,22 +4,28 @@ package integration import ( + "context" "encoding/base64" "fmt" "net/http" "net/url" + "reflect" "testing" "time" actions_model "code.gitea.io/gitea/models/actions" auth_model "code.gitea.io/gitea/models/auth" + repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unittest" user_model "code.gitea.io/gitea/models/user" + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" runnerv1 "code.gitea.io/actions-proto-go/runner/v1" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestJobWithNeeds(t *testing.T) { @@ -354,6 +360,95 @@ jobs: }) } +func TestActionsGiteaContext(t *testing.T) { + if !setting.Database.Type.IsSQLite3() { + t.Skip() + } + + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + user2Session := loginUser(t, user2.Name) + user2Token := getTokenForLoggedInUser(t, user2Session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + + apiBaseRepo := createActionsTestRepo(t, user2Token, "actions-gitea-context", false) + baseRepo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: apiBaseRepo.ID}) + user2APICtx := NewAPITestContext(t, baseRepo.OwnerName, baseRepo.Name, auth_model.AccessTokenScopeWriteRepository) + + runner := newMockRunner() + runner.registerAsRepoRunner(t, baseRepo.OwnerName, baseRepo.Name, "mock-runner", []string{"ubuntu-latest"}) + + // init the workflow + wfTreePath := ".gitea/workflows/pull.yml" + wfFileContent := `name: Pull Request +on: pull_request +jobs: + wf1-job: + runs-on: ubuntu-latest + steps: + - run: echo 'test the pull' +` + opts := getWorkflowCreateFileOptions(user2, baseRepo.DefaultBranch, fmt.Sprintf("create %s", wfTreePath), wfFileContent) + createWorkflowFile(t, user2Token, baseRepo.OwnerName, baseRepo.Name, wfTreePath, opts) + // user2 creates a pull request + doAPICreateFile(user2APICtx, "user2-patch.txt", &api.CreateFileOptions{ + FileOptions: api.FileOptions{ + NewBranchName: "user2/patch-1", + Message: "create user2-patch.txt", + Author: api.Identity{ + Name: user2.Name, + Email: user2.Email, + }, + Committer: api.Identity{ + Name: user2.Name, + Email: user2.Email, + }, + Dates: api.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }, + ContentBase64: base64.StdEncoding.EncodeToString([]byte("user2-fix")), + })(t) + apiPull, err := doAPICreatePullRequest(user2APICtx, baseRepo.OwnerName, baseRepo.Name, baseRepo.DefaultBranch, "user2/patch-1")(t) + require.NoError(t, err) + task := runner.fetchTask(t) + gtCtx := task.Context.GetFields() + actionTask := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionTask{ID: task.Id}) + actionRunJob := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRunJob{ID: actionTask.JobID}) + actionRun := unittest.AssertExistsAndLoadBean(t, &actions_model.ActionRun{ID: actionRunJob.RunID}) + require.NoError(t, actionRun.LoadAttributes(context.Background())) + + assert.Equal(t, user2.Name, gtCtx["actor"].GetStringValue()) + assert.Equal(t, setting.AppURL+"api/v1", gtCtx["api_url"].GetStringValue()) + assert.Equal(t, apiPull.Base.Ref, gtCtx["base_ref"].GetStringValue()) + runEvent := map[string]any{} + require.NoError(t, json.Unmarshal([]byte(actionRun.EventPayload), &runEvent)) + assert.True(t, reflect.DeepEqual(gtCtx["event"].GetStructValue().AsMap(), runEvent)) + assert.Equal(t, actionRun.TriggerEvent, gtCtx["event_name"].GetStringValue()) + assert.Equal(t, apiPull.Head.Ref, gtCtx["head_ref"].GetStringValue()) + assert.Equal(t, actionRunJob.JobID, gtCtx["job"].GetStringValue()) + assert.Equal(t, actionRun.Ref, gtCtx["ref"].GetStringValue()) + assert.Equal(t, (git.RefName(actionRun.Ref)).ShortName(), gtCtx["ref_name"].GetStringValue()) + assert.False(t, gtCtx["ref_protected"].GetBoolValue()) + assert.Equal(t, (git.RefName(actionRun.Ref)).RefType(), gtCtx["ref_type"].GetStringValue()) + assert.Equal(t, actionRun.Repo.OwnerName+"/"+actionRun.Repo.Name, gtCtx["repository"].GetStringValue()) + assert.Equal(t, actionRun.Repo.OwnerName, gtCtx["repository_owner"].GetStringValue()) + assert.Equal(t, actionRun.Repo.HTMLURL(), gtCtx["repositoryUrl"].GetStringValue()) + assert.Equal(t, fmt.Sprint(actionRunJob.RunID), gtCtx["run_id"].GetStringValue()) + assert.Equal(t, fmt.Sprint(actionRun.Index), gtCtx["run_number"].GetStringValue()) + assert.Equal(t, fmt.Sprint(actionRunJob.Attempt), gtCtx["run_attempt"].GetStringValue()) + assert.Equal(t, "Actions", gtCtx["secret_source"].GetStringValue()) + assert.Equal(t, setting.AppURL, gtCtx["server_url"].GetStringValue()) + assert.Equal(t, actionRun.CommitSHA, gtCtx["sha"].GetStringValue()) + assert.Equal(t, actionRun.WorkflowID, gtCtx["workflow"].GetStringValue()) + assert.Equal(t, setting.Actions.DefaultActionsURL.URL(), gtCtx["gitea_default_actions_url"].GetStringValue()) + token := gtCtx["token"].GetStringValue() + assert.Equal(t, actionTask.TokenLastEight, token[len(token)-8:]) + + doAPIDeleteRepository(user2APICtx)(t) + }) +} + func createActionsTestRepo(t *testing.T, authToken, repoName string, isPrivate bool) *api.Repository { req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{ Name: repoName, diff --git a/tests/integration/admin_user_test.go b/tests/integration/admin_user_test.go index 93499e9139..6e0499d949 100644 --- a/tests/integration/admin_user_test.go +++ b/tests/integration/admin_user_test.go @@ -26,13 +26,36 @@ import ( func TestAdminViewUsers(t *testing.T) { defer tests.PrepareTestEnv(t)() - session := loginUser(t, "user1") - req := NewRequest(t, "GET", "/admin/users") - session.MakeRequest(t, req, http.StatusOK) + t.Run("Admin user", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - session = loginUser(t, "user2") - req = NewRequest(t, "GET", "/admin/users") - session.MakeRequest(t, req, http.StatusForbidden) + session := loginUser(t, "user1") + req := NewRequest(t, "GET", "/admin/users") + session.MakeRequest(t, req, http.StatusOK) + + req = NewRequest(t, "GET", "/admin/users?status_filter[is_2fa_enabled]=1") + resp := session.MakeRequest(t, req, http.StatusOK) + htmlDoc := NewHTMLParser(t, resp.Body) + + // 6th column is the 2FA column. + // One user that has TOTP and another user that has WebAuthn. + assert.EqualValues(t, 2, htmlDoc.Find(".admin-setting-content table tbody tr td:nth-child(6) .octicon-check").Length()) + }) + + t.Run("Normal user", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + session := loginUser(t, "user2") + req := NewRequest(t, "GET", "/admin/users") + session.MakeRequest(t, req, http.StatusForbidden) + }) + + t.Run("Anonymous user", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + req := NewRequest(t, "GET", "/admin/users") + MakeRequest(t, req, http.StatusSeeOther) + }) } func TestAdminViewUser(t *testing.T) { diff --git a/tests/integration/explore_org_test.go b/tests/integration/explore_org_test.go new file mode 100644 index 0000000000..e0c48ccf0d --- /dev/null +++ b/tests/integration/explore_org_test.go @@ -0,0 +1,49 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package integration + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" + "code.gitea.io/gitea/tests" + + "github.com/stretchr/testify/assert" +) + +func TestExploreOrg(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // Set the default sort order + defer test.MockVariableValue(&setting.UI.ExploreDefaultSort, "alphabetically")() + + cases := []struct{ sortOrder, expected string }{ + {"", "?sort=" + setting.UI.ExploreDefaultSort + "&q="}, + {"newest", "?sort=newest&q="}, + {"oldest", "?sort=oldest&q="}, + {"alphabetically", "?sort=alphabetically&q="}, + {"reversealphabetically", "?sort=reversealphabetically&q="}, + } + for _, c := range cases { + req := NewRequest(t, "GET", "/explore/organizations?sort="+c.sortOrder) + resp := MakeRequest(t, req, http.StatusOK) + h := NewHTMLParser(t, resp.Body) + href, _ := h.Find(`.ui.dropdown .menu a.active.item[href^="?sort="]`).Attr("href") + assert.Equal(t, c.expected, href) + } + + // these sort orders shouldn't be supported, to avoid leaking user activity + cases404 := []string{ + "/explore/organizations?sort=mostMembers", + "/explore/organizations?sort=leastGroups", + "/explore/organizations?sort=leastupdate", + "/explore/organizations?sort=reverseleastupdate", + } + for _, c := range cases404 { + req := NewRequest(t, "GET", c).SetHeader("Accept", "text/html") + MakeRequest(t, req, http.StatusNotFound) + } +} diff --git a/tests/integration/explore_user_test.go b/tests/integration/explore_user_test.go index 441d89cea5..d1e3fd85af 100644 --- a/tests/integration/explore_user_test.go +++ b/tests/integration/explore_user_test.go @@ -7,6 +7,8 @@ import ( "net/http" "testing" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/tests" "github.com/stretchr/testify/assert" @@ -15,8 +17,11 @@ import ( func TestExploreUser(t *testing.T) { defer tests.PrepareTestEnv(t)() + // Set the default sort order + defer test.MockVariableValue(&setting.UI.ExploreDefaultSort, "reversealphabetically")() + cases := []struct{ sortOrder, expected string }{ - {"", "?sort=newest&q="}, + {"", "?sort=" + setting.UI.ExploreDefaultSort + "&q="}, {"newest", "?sort=newest&q="}, {"oldest", "?sort=oldest&q="}, {"alphabetically", "?sort=alphabetically&q="}, diff --git a/tests/integration/pull_icon_test.go b/tests/integration/pull_icon_test.go index 8fde547ce9..b678550c30 100644 --- a/tests/integration/pull_icon_test.go +++ b/tests/integration/pull_icon_test.go @@ -133,7 +133,7 @@ func testPullRequestListIcon(t *testing.T, doc *HTMLDoc, name, expectedColor, ex } func createOpenPullRequest(ctx context.Context, t *testing.T, user *user_model.User, repo *repo_model.Repository) *issues_model.PullRequest { - pull := createPullRequest(t, user, repo, "open") + pull := createPullRequest(t, user, repo, "branch-open", "open") assert.False(t, pull.Issue.IsClosed) assert.False(t, pull.HasMerged) @@ -143,7 +143,7 @@ func createOpenPullRequest(ctx context.Context, t *testing.T, user *user_model.U } func createOpenWipPullRequest(ctx context.Context, t *testing.T, user *user_model.User, repo *repo_model.Repository) *issues_model.PullRequest { - pull := createPullRequest(t, user, repo, "open-wip") + pull := createPullRequest(t, user, repo, "branch-open-wip", "open-wip") err := issue_service.ChangeTitle(ctx, pull.Issue, user, "WIP: "+pull.Issue.Title) require.NoError(t, err) @@ -156,7 +156,7 @@ func createOpenWipPullRequest(ctx context.Context, t *testing.T, user *user_mode } func createClosedPullRequest(ctx context.Context, t *testing.T, user *user_model.User, repo *repo_model.Repository) *issues_model.PullRequest { - pull := createPullRequest(t, user, repo, "closed") + pull := createPullRequest(t, user, repo, "branch-closed", "closed") err := issue_service.ChangeStatus(ctx, pull.Issue, user, "", true) require.NoError(t, err) @@ -169,7 +169,7 @@ func createClosedPullRequest(ctx context.Context, t *testing.T, user *user_model } func createClosedWipPullRequest(ctx context.Context, t *testing.T, user *user_model.User, repo *repo_model.Repository) *issues_model.PullRequest { - pull := createPullRequest(t, user, repo, "closed-wip") + pull := createPullRequest(t, user, repo, "branch-closed-wip", "closed-wip") err := issue_service.ChangeTitle(ctx, pull.Issue, user, "WIP: "+pull.Issue.Title) require.NoError(t, err) @@ -185,7 +185,7 @@ func createClosedWipPullRequest(ctx context.Context, t *testing.T, user *user_mo } func createMergedPullRequest(ctx context.Context, t *testing.T, user *user_model.User, repo *repo_model.Repository) *issues_model.PullRequest { - pull := createPullRequest(t, user, repo, "merged") + pull := createPullRequest(t, user, repo, "branch-merged", "merged") gitRepo, err := git.OpenRepository(ctx, repo.RepoPath()) defer gitRepo.Close() @@ -202,10 +202,7 @@ func createMergedPullRequest(ctx context.Context, t *testing.T, user *user_model return pull } -func createPullRequest(t *testing.T, user *user_model.User, repo *repo_model.Repository, name string) *issues_model.PullRequest { - branch := "branch-" + name - title := "Testing " + name - +func createPullRequest(t *testing.T, user *user_model.User, repo *repo_model.Repository, branch, title string) *issues_model.PullRequest { _, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, user, &files_service.ChangeRepoFilesOptions{ Files: []*files_service.ChangeRepoFile{ { diff --git a/tests/integration/pull_review_test.go b/tests/integration/pull_review_test.go index 1319db29bf..6dd58102eb 100644 --- a/tests/integration/pull_review_test.go +++ b/tests/integration/pull_review_test.go @@ -1,4 +1,5 @@ // Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2024 The Forgejo Authors. All rights reserved. // SPDX-License-Identifier: MIT package integration @@ -362,6 +363,8 @@ func TestPullView_CodeOwner(t *testing.T) { defer f() t.Run("First Pull Request", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + // create a new branch to prepare for pull request _, err := files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{ NewBranch: "codeowner-basebranch", @@ -409,6 +412,8 @@ func TestPullView_CodeOwner(t *testing.T) { require.NoError(t, err) t.Run("Second Pull Request", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + // create a new branch to prepare for pull request _, err = files_service.ChangeRepoFiles(db.DefaultContext, repo, user2, &files_service.ChangeRepoFilesOptions{ NewBranch: "codeowner-basebranch2", @@ -431,6 +436,8 @@ func TestPullView_CodeOwner(t *testing.T) { }) t.Run("Forked Repo Pull Request", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}) forkedRepo, err := repo_service.ForkRepositoryAndUpdates(db.DefaultContext, user2, user5, repo_service.ForkRepoOptions{ BaseRepo: repo, @@ -483,6 +490,8 @@ func TestPullView_GivenApproveOrRejectReviewOnClosedPR(t *testing.T) { defer baseGitRepo.Close() t.Run("Submit approve/reject review on merged PR", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + // Create a merged PR (made by user1) in the upstream repo1. testEditFile(t, user1Session, "user1", "repo1", "master", "README.md", "Hello, World (Edited)\n") resp := testPullCreate(t, user1Session, "user1", "repo1", false, "master", "master", "This is a pull title") @@ -513,12 +522,14 @@ func TestPullView_GivenApproveOrRejectReviewOnClosedPR(t *testing.T) { }) t.Run("Submit approve/reject review on closed PR", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + // Created a closed PR (made by user1) in the upstream repo1. testEditFileToNewBranch(t, user1Session, "user1", "repo1", "master", "a-test-branch", "README.md", "Hello, World (Edited...again)\n") resp := testPullCreate(t, user1Session, "user1", "repo1", false, "master", "a-test-branch", "This is a pull title") elem := strings.Split(test.RedirectURL(resp), "/") assert.EqualValues(t, "pulls", elem[3]) - testIssueClose(t, user1Session, elem[1], elem[2], elem[4]) + testIssueClose(t, user1Session, elem[1], elem[2], elem[4], true) // Get the commit SHA pr := unittest.AssertExistsAndLoadBean(t, &issues_model.PullRequest{ @@ -544,6 +555,41 @@ func TestPullView_GivenApproveOrRejectReviewOnClosedPR(t *testing.T) { }) } +func TestPullReviewInArchivedRepo(t *testing.T) { + onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { + session := loginUser(t, "user2") + + // Open a PR + testEditFileToNewBranch(t, session, "user2", "repo1", "master", "for-pr", "README.md", "Hi!\n") + resp := testPullCreate(t, session, "user2", "repo1", true, "master", "for-pr", "PR title") + elem := strings.Split(test.RedirectURL(resp), "/") + + t.Run("Review box normally", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // The "Finish review button" must be available + resp = session.MakeRequest(t, NewRequest(t, "GET", path.Join(elem[1], elem[2], "pulls", elem[4], "files")), http.StatusOK) + button := NewHTMLParser(t, resp.Body).Find("#review-box button") + assert.False(t, button.HasClass("disabled")) + }) + + t.Run("Review box in archived repo", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + // Archive the repo + resp = session.MakeRequest(t, NewRequestWithValues(t, "POST", path.Join(elem[1], elem[2], "settings"), map[string]string{ + "_csrf": GetCSRF(t, session, path.Join(elem[1], elem[2], "settings")), + "action": "archive", + }), http.StatusSeeOther) + + // The "Finish review button" must be disabled + resp = session.MakeRequest(t, NewRequest(t, "GET", path.Join(elem[1], elem[2], "pulls", elem[4], "files")), http.StatusOK) + button := NewHTMLParser(t, resp.Body).Find("#review-box button") + assert.True(t, button.HasClass("disabled")) + }) + }) +} + func testNofiticationCount(t *testing.T, session *TestSession, csrf string, expectedSubmitStatus int) *httptest.ResponseRecorder { options := map[string]string{ "_csrf": csrf, @@ -579,8 +625,12 @@ func testSubmitReview(t *testing.T, session *TestSession, csrf, owner, repo, pul return session.MakeRequest(t, req, expectedSubmitStatus) } -func testIssueClose(t *testing.T, session *TestSession, owner, repo, issueNumber string) *httptest.ResponseRecorder { - req := NewRequest(t, "GET", path.Join(owner, repo, "pulls", issueNumber)) +func testIssueClose(t *testing.T, session *TestSession, owner, repo, issueNumber string, isPull bool) *httptest.ResponseRecorder { + issueType := "issues" + if isPull { + issueType = "pulls" + } + req := NewRequest(t, "GET", path.Join(owner, repo, issueType, issueNumber)) resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go index 90fc19c193..01d905895a 100644 --- a/tests/integration/repo_test.go +++ b/tests/integration/repo_test.go @@ -1462,3 +1462,15 @@ func TestRepoSubmoduleView(t *testing.T) { htmlDoc.AssertElement(t, fmt.Sprintf(`tr[data-entryname="repo1"] a[href="%s"]`, u.JoinPath("/user2/repo1").String()), true) }) } + +func TestBlameDirectory(t *testing.T) { + defer tests.PrepareTestEnv(t)() + + // Ensure directory exists. + req := NewRequest(t, "GET", "/user2/repo59/src/branch/master/deep") + MakeRequest(t, req, http.StatusOK) + + // Blame is not allowed + req = NewRequest(t, "GET", "/user2/repo59/blame/branch/master/deep") + MakeRequest(t, req, http.StatusNotFound) +} diff --git a/tests/integration/user_dashboard_test.go b/tests/integration/user_dashboard_test.go index abc3e065d9..0ed5193c48 100644 --- a/tests/integration/user_dashboard_test.go +++ b/tests/integration/user_dashboard_test.go @@ -5,12 +5,21 @@ package integration import ( "net/http" + "net/url" + "strconv" "strings" "testing" + "code.gitea.io/gitea/models/db" + 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/translation" + issue_service "code.gitea.io/gitea/services/issue" + files_service "code.gitea.io/gitea/services/repository/files" + "code.gitea.io/gitea/tests" + "github.com/PuerkitoBio/goquery" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -28,3 +37,45 @@ func TestUserDashboardActionLinks(t *testing.T) { assert.EqualValues(t, locale.TrString("new_migrate.link"), strings.TrimSpace(links.Find("a[href='/repo/migrate']").Text())) assert.EqualValues(t, locale.TrString("new_org.link"), strings.TrimSpace(links.Find("a[href='/org/create']").Text())) } + +func TestDashboardTitleRendering(t *testing.T) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + sess := loginUser(t, user4.Name) + + repo, _, f := tests.CreateDeclarativeRepo(t, user4, "", + []unit_model.Type{unit_model.TypePullRequests, unit_model.TypeIssues}, nil, + []*files_service.ChangeRepoFile{ + { + Operation: "create", + TreePath: "test.txt", + ContentReader: strings.NewReader("Just some text here"), + }, + }, + ) + defer f() + + issue := createIssue(t, user4, repo, "`:exclamation:` not rendered", "Hi there!") + pr := createPullRequest(t, user4, repo, "testing", "`:exclamation:` not rendered") + + _, err := issue_service.CreateIssueComment(db.DefaultContext, user4, repo, issue, "hi", nil) + require.NoError(t, err) + + _, err = issue_service.CreateIssueComment(db.DefaultContext, user4, repo, pr.Issue, "hi", nil) + require.NoError(t, err) + + testIssueClose(t, sess, repo.OwnerName, repo.Name, strconv.Itoa(int(issue.Index)), false) + testIssueClose(t, sess, repo.OwnerName, repo.Name, strconv.Itoa(int(pr.Issue.Index)), true) + + response := sess.MakeRequest(t, NewRequest(t, "GET", "/"), http.StatusOK) + htmlDoc := NewHTMLParser(t, response.Body) + + count := 0 + htmlDoc.doc.Find("#activity-feed .flex-item-main .title").Each(func(i int, s *goquery.Selection) { + count++ + assert.EqualValues(t, ":exclamation: not rendered", s.Text()) + }) + + assert.EqualValues(t, 6, count) + }) +} diff --git a/web_src/css/repo.css b/web_src/css/repo.css index b8cd3c08bb..bdb09eb776 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -2022,13 +2022,14 @@ details.repo-search-result summary::marker { font-weight: var(--font-weight-medium); } -.repository .repository-summary .segment.language-stats { +.repository .repository-summary #language-stats-bar { display: flex; gap: 2px; padding: 0; height: 10px; white-space: nowrap; - border-radius: 0 0 3px 3px !important; + border-top-left-radius: 0 !important; + border-top-right-radius: 0 !important; overflow: hidden; } diff --git a/web_src/js/components/DashboardRepoList.vue b/web_src/js/components/DashboardRepoList.vue index 007891a39f..0d8396b6f3 100644 --- a/web_src/js/components/DashboardRepoList.vue +++ b/web_src/js/components/DashboardRepoList.vue @@ -1,5 +1,5 @@
images/icon-install.png Installation
images/icon-usage.png Usage