From 741b53eb30e8e0ebdcff6bfb157cf61aa7691b8a Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Tue, 1 Apr 2025 02:39:08 +0800 Subject: [PATCH 01/62] [Fix] Resolve the problem of commit_statuses not being loaded at the top - right when switching files from the file tree (#34079) Co-authored-by: wxiaoguang --- templates/repo/commit_statuses.tmpl | 4 ++-- tests/integration/repo_commits_test.go | 4 ++-- web_src/js/features/repo-commit.ts | 16 ++++++++-------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/templates/repo/commit_statuses.tmpl b/templates/repo/commit_statuses.tmpl index a6f75584a3..1bbfb33105 100644 --- a/templates/repo/commit_statuses.tmpl +++ b/templates/repo/commit_statuses.tmpl @@ -1,10 +1,10 @@ {{if .Statuses}} {{if and (eq (len .Statuses) 1) .Status.TargetURL}} - + {{template "repo/commit_status" .Status}} {{else}} - + {{template "repo/commit_status" .Status}} {{end}} diff --git a/tests/integration/repo_commits_test.go b/tests/integration/repo_commits_test.go index 139f08075c..dee0aa6176 100644 --- a/tests/integration/repo_commits_test.go +++ b/tests/integration/repo_commits_test.go @@ -240,7 +240,7 @@ func TestRepoCommitsStatusMultiple(t *testing.T) { resp = session.MakeRequest(t, req, http.StatusOK) doc = NewHTMLParser(t, resp.Body) - // Check that the data-tippy="commit-statuses" (for trigger) and commit-status (svg) are present - sel := doc.doc.Find("#commits-table .message [data-tippy=\"commit-statuses\"] .commit-status") + // Check that the data-global-init="initCommitStatuses" (for trigger) and commit-status (svg) are present + sel := doc.doc.Find(`#commits-table .message [data-global-init="initCommitStatuses"] .commit-status`) assert.Equal(t, 1, sel.Length()) } diff --git a/web_src/js/features/repo-commit.ts b/web_src/js/features/repo-commit.ts index e6d1112778..98ec2328ec 100644 --- a/web_src/js/features/repo-commit.ts +++ b/web_src/js/features/repo-commit.ts @@ -1,6 +1,6 @@ import {createTippy} from '../modules/tippy.ts'; import {toggleElem} from '../utils/dom.ts'; -import {registerGlobalEventFunc} from '../modules/observer.ts'; +import {registerGlobalEventFunc, registerGlobalInitFunc} from '../modules/observer.ts'; export function initRepoEllipsisButton() { registerGlobalEventFunc('click', 'onRepoEllipsisButtonClick', async (el: HTMLInputElement, e: Event) => { @@ -12,15 +12,15 @@ export function initRepoEllipsisButton() { } export function initCommitStatuses() { - for (const element of document.querySelectorAll('[data-tippy="commit-statuses"]')) { - const top = document.querySelector('.repository.file.list') || document.querySelector('.repository.diff'); - - createTippy(element, { - content: element.nextElementSibling, - placement: top ? 'top-start' : 'bottom-start', + registerGlobalInitFunc('initCommitStatuses', (el: HTMLElement) => { + const nextEl = el.nextElementSibling; + if (!nextEl.matches('.tippy-target')) throw new Error('Expected next element to be a tippy target'); + createTippy(el, { + content: nextEl, + placement: 'bottom-start', interactive: true, role: 'dialog', theme: 'box-with-header', }); - } + }); } From 342432e52a13c37575cf443286ba1d01bdd67cce Mon Sep 17 00:00:00 2001 From: Simon Priet <105607989+SimonPistache@users.noreply.github.com> Date: Mon, 31 Mar 2025 21:11:15 +0200 Subject: [PATCH 02/62] fix(#34076):replace assgniee translation key (#34077) Fix the typo on the `filter_assginee_no_assigne` key used in translations. The typo itself doesn't produce a bug (as it's there both on the code and on the locales) Side Note: Github UI is not the best to bulk change this :/ Squashing commits on the PR should be adequate. Closes #34076 . --------- Co-authored-by: Lunny Xiao --- options/locale/locale_en-US.ini | 2 +- templates/projects/view.tmpl | 2 +- templates/repo/issue/filter_list.tmpl | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 8ce469359c..91bfdefb4b 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1551,7 +1551,7 @@ issues.filter_project = Project issues.filter_project_all = All projects issues.filter_project_none = No project issues.filter_assignee = Assignee -issues.filter_assginee_no_assignee = Assigned to nobody +issues.filter_assignee_no_assignee = Assigned to nobody issues.filter_assignee_any_assignee = Assigned to anybody issues.filter_poster = Author issues.filter_user_placeholder = Search users diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl index 79925e69cc..d6a335ef4b 100644 --- a/templates/projects/view.tmpl +++ b/templates/projects/view.tmpl @@ -13,7 +13,7 @@ "UserSearchList" $.Assignees "SelectedUserId" $.AssigneeID "TextFilterTitle" (ctx.Locale.Tr "repo.issues.filter_assignee") - "TextFilterMatchNone" (ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee") + "TextFilterMatchNone" (ctx.Locale.Tr "repo.issues.filter_assignee_no_assignee") "TextFilterMatchAny" (ctx.Locale.Tr "repo.issues.filter_assignee_any_assignee") }} diff --git a/templates/repo/issue/filter_list.tmpl b/templates/repo/issue/filter_list.tmpl index 58ca4a7c00..cd04f1c317 100644 --- a/templates/repo/issue/filter_list.tmpl +++ b/templates/repo/issue/filter_list.tmpl @@ -94,7 +94,7 @@ "UserSearchList" $.Assignees "SelectedUserId" $.AssigneeID "TextFilterTitle" (ctx.Locale.Tr "repo.issues.filter_assignee") - "TextFilterMatchNone" (ctx.Locale.Tr "repo.issues.filter_assginee_no_assignee") + "TextFilterMatchNone" (ctx.Locale.Tr "repo.issues.filter_assignee_no_assignee") "TextFilterMatchAny" (ctx.Locale.Tr "repo.issues.filter_assignee_any_assignee") }} From a2e8a289b2dbd7d28aa91fbf67eee4439ad9d854 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 31 Mar 2025 12:54:31 -0700 Subject: [PATCH 03/62] Improve pull request list api (#34052) The pull request list API is slow, for every pull request, it needs to open a git repository. Assume it has 30 records, there will be 30 sub processes back because every repository will open a git cat-file --batch sub process. This PR use base git repository to get the head commit id rather than read it from head repository to avoid open any head git repository. --- models/git/branch.go | 12 ++++++ services/convert/pull.go | 83 +++++++++++++--------------------------- 2 files changed, 39 insertions(+), 56 deletions(-) diff --git a/models/git/branch.go b/models/git/branch.go index d1caa35947..9ac6c45578 100644 --- a/models/git/branch.go +++ b/models/git/branch.go @@ -173,6 +173,18 @@ func GetBranch(ctx context.Context, repoID int64, branchName string) (*Branch, e return &branch, nil } +// IsBranchExist returns true if the branch exists in the repository. +func IsBranchExist(ctx context.Context, repoID int64, branchName string) (bool, error) { + var branch Branch + has, err := db.GetEngine(ctx).Where("repo_id=?", repoID).And("name=?", branchName).Get(&branch) + if err != nil { + return false, err + } else if !has { + return false, nil + } + return !branch.IsDeleted, nil +} + func GetBranches(ctx context.Context, repoID int64, branchNames []string, includeDeleted bool) ([]*Branch, error) { branches := make([]*Branch, 0, len(branchNames)) diff --git a/services/convert/pull.go b/services/convert/pull.go index 928534ce5e..c22b5282c8 100644 --- a/services/convert/pull.go +++ b/services/convert/pull.go @@ -389,7 +389,7 @@ func ToAPIPullRequests(ctx context.Context, baseRepo *repo_model.Repository, prs }, Head: &api.PRBranchInfo{ Name: pr.HeadBranch, - Ref: fmt.Sprintf("%s%d/head", git.PullPrefix, pr.Index), + Ref: pr.GetGitRefName(), RepoID: -1, }, } @@ -422,72 +422,43 @@ func ToAPIPullRequests(ctx context.Context, baseRepo *repo_model.Repository, prs return nil, err } } - if baseBranch != nil { apiPullRequest.Base.Sha = baseBranch.CommitID } - if pr.Flow == issues_model.PullRequestFlowAGit { - apiPullRequest.Head.Sha, err = gitRepo.GetRefCommitID(pr.GetGitRefName()) + // pull request head branch, both repository and branch could not exist + if pr.HeadRepo != nil { + apiPullRequest.Head.RepoID = pr.HeadRepo.ID + exist, err := git_model.IsBranchExist(ctx, pr.HeadRepo.ID, pr.HeadBranch) if err != nil { - log.Error("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err) + log.Error("IsBranchExist[%d]: %v", pr.HeadRepo.ID, err) return nil, err } - apiPullRequest.Head.RepoID = pr.BaseRepoID - apiPullRequest.Head.Repository = apiPullRequest.Base.Repository - apiPullRequest.Head.Name = "" + if exist { + apiPullRequest.Head.Ref = pr.HeadBranch + } + } + if apiPullRequest.Head.Ref == "" { + apiPullRequest.Head.Ref = pr.GetGitRefName() } - var headGitRepo *git.Repository - if pr.HeadRepo != nil && pr.Flow == issues_model.PullRequestFlowGithub { - if pr.HeadRepoID == pr.BaseRepoID { - apiPullRequest.Head.RepoID = pr.HeadRepo.ID - apiPullRequest.Head.Repository = apiRepo - headGitRepo = gitRepo - } else { - p, err := access_model.GetUserRepoPermission(ctx, pr.HeadRepo, doer) - if err != nil { - log.Error("GetUserRepoPermission[%d]: %v", pr.HeadRepoID, err) - p.AccessMode = perm.AccessModeNone - } - - apiPullRequest.Head.RepoID = pr.HeadRepo.ID - apiPullRequest.Head.Repository = ToRepo(ctx, pr.HeadRepo, p) - - headGitRepo, err = gitrepo.OpenRepository(ctx, pr.HeadRepo) - if err != nil { - log.Error("OpenRepository[%s]: %v", pr.HeadRepo.RepoPath(), err) - return nil, err - } - defer headGitRepo.Close() + if pr.HeadRepoID == pr.BaseRepoID { + apiPullRequest.Head.Repository = apiPullRequest.Base.Repository + } else { + p, err := access_model.GetUserRepoPermission(ctx, pr.HeadRepo, doer) + if err != nil { + log.Error("GetUserRepoPermission[%d]: %v", pr.HeadRepoID, err) + p.AccessMode = perm.AccessModeNone } + apiPullRequest.Head.Repository = ToRepo(ctx, pr.HeadRepo, p) + } - headBranch, err := headGitRepo.GetBranch(pr.HeadBranch) - if err != nil && !git.IsErrBranchNotExist(err) { - log.Error("GetBranch[%s]: %v", pr.HeadBranch, err) - return nil, err - } - - if git.IsErrBranchNotExist(err) { - headCommitID, err := headGitRepo.GetRefCommitID(apiPullRequest.Head.Ref) - if err != nil && !git.IsErrNotExist(err) { - log.Error("GetCommit[%s]: %v", pr.HeadBranch, err) - return nil, err - } - if err == nil { - apiPullRequest.Head.Sha = headCommitID - } - } else { - commit, err := headBranch.GetCommit() - if err != nil && !git.IsErrNotExist(err) { - log.Error("GetCommit[%s]: %v", headBranch.Name, err) - return nil, err - } - if err == nil { - apiPullRequest.Head.Ref = pr.HeadBranch - apiPullRequest.Head.Sha = commit.ID.String() - } - } + if pr.Flow == issues_model.PullRequestFlowAGit { + apiPullRequest.Head.Name = "" + } + apiPullRequest.Head.Sha, err = gitRepo.GetRefCommitID(pr.GetGitRefName()) + if err != nil { + log.Error("GetRefCommitID[%s]: %v", pr.GetGitRefName(), err) } if len(apiPullRequest.Head.Sha) == 0 && len(apiPullRequest.Head.Ref) != 0 { From 4d2323183d05f73c00f9f3be3171b3f755f551c4 Mon Sep 17 00:00:00 2001 From: TheFox0x7 Date: Mon, 31 Mar 2025 22:19:32 +0200 Subject: [PATCH 04/62] fix users being able bypass limits with repo transfers (#34031) prevent user from being able to transfer repo to user who cannot have more repositories --- models/fixtures/repo_transfer.yml | 8 ++++++ routers/api/v1/repo/transfer.go | 23 ++++++++------- routers/web/repo/repo.go | 10 +++++-- routers/web/repo/setting/setting.go | 3 ++ services/repository/transfer.go | 22 +++++++++++++++ services/repository/transfer_test.go | 42 +++++++++++++++++++++++++++- 6 files changed, 92 insertions(+), 16 deletions(-) diff --git a/models/fixtures/repo_transfer.yml b/models/fixtures/repo_transfer.yml index db92c95248..b12e6b207f 100644 --- a/models/fixtures/repo_transfer.yml +++ b/models/fixtures/repo_transfer.yml @@ -21,3 +21,11 @@ repo_id: 32 created_unix: 1553610671 updated_unix: 1553610671 + +- + id: 4 + doer_id: 3 + recipient_id: 1 + repo_id: 5 + created_unix: 1553610671 + updated_unix: 1553610671 diff --git a/routers/api/v1/repo/transfer.go b/routers/api/v1/repo/transfer.go index 7b890c9e5c..8643d0c2ca 100644 --- a/routers/api/v1/repo/transfer.go +++ b/routers/api/v1/repo/transfer.go @@ -108,22 +108,19 @@ func Transfer(ctx *context.APIContext) { oldFullname := ctx.Repo.Repository.FullName() if err := repo_service.StartRepositoryTransfer(ctx, ctx.Doer, newOwner, ctx.Repo.Repository, teams); err != nil { - if repo_model.IsErrRepoTransferInProgress(err) { + switch { + case repo_model.IsErrRepoTransferInProgress(err): ctx.APIError(http.StatusConflict, err) - return - } - - if repo_model.IsErrRepoAlreadyExist(err) { + case repo_model.IsErrRepoAlreadyExist(err): ctx.APIError(http.StatusUnprocessableEntity, err) + case repo_service.IsRepositoryLimitReached(err): + ctx.APIError(http.StatusForbidden, err) + case errors.Is(err, user_model.ErrBlockedUser): + ctx.APIError(http.StatusForbidden, err) + default: + ctx.APIErrorInternal(err) return } - - if errors.Is(err, user_model.ErrBlockedUser) { - ctx.APIError(http.StatusForbidden, err) - } else { - ctx.APIErrorInternal(err) - } - return } if ctx.Repo.Repository.Status == repo_model.RepositoryPendingTransfer { @@ -169,6 +166,8 @@ func AcceptTransfer(ctx *context.APIContext) { ctx.APIError(http.StatusNotFound, err) case errors.Is(err, util.ErrPermissionDenied): ctx.APIError(http.StatusForbidden, err) + case repo_service.IsRepositoryLimitReached(err): + ctx.APIError(http.StatusForbidden, err) default: ctx.APIErrorInternal(err) } diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 54b7448a89..e260ea36dd 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -305,11 +305,15 @@ func CreatePost(ctx *context.Context) { } func handleActionError(ctx *context.Context, err error) { - if errors.Is(err, user_model.ErrBlockedUser) { + switch { + case errors.Is(err, user_model.ErrBlockedUser): ctx.Flash.Error(ctx.Tr("repo.action.blocked_user")) - } else if errors.Is(err, util.ErrPermissionDenied) { + case repo_service.IsRepositoryLimitReached(err): + limit := err.(repo_service.LimitReachedError).Limit + ctx.Flash.Error(ctx.TrN(limit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", limit)) + case errors.Is(err, util.ErrPermissionDenied): ctx.HTTPError(http.StatusNotFound) - } else { + default: ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.PathParam("action")), err) } } diff --git a/routers/web/repo/setting/setting.go b/routers/web/repo/setting/setting.go index a0edb1e11a..e30986e86e 100644 --- a/routers/web/repo/setting/setting.go +++ b/routers/web/repo/setting/setting.go @@ -848,6 +848,9 @@ func handleSettingsPostTransfer(ctx *context.Context) { ctx.RenderWithErr(ctx.Tr("repo.settings.new_owner_has_same_repo"), tplSettingsOptions, nil) } else if repo_model.IsErrRepoTransferInProgress(err) { ctx.RenderWithErr(ctx.Tr("repo.settings.transfer_in_progress"), tplSettingsOptions, nil) + } else if repo_service.IsRepositoryLimitReached(err) { + limit := err.(repo_service.LimitReachedError).Limit + ctx.RenderWithErr(ctx.TrN(limit, "repo.form.reach_limit_of_creation_1", "repo.form.reach_limit_of_creation_n", limit), tplSettingsOptions, nil) } else if errors.Is(err, user_model.ErrBlockedUser) { ctx.RenderWithErr(ctx.Tr("repo.settings.transfer.blocked_user"), tplSettingsOptions, nil) } else { diff --git a/services/repository/transfer.go b/services/repository/transfer.go index a589bc469d..4e44b90df2 100644 --- a/services/repository/transfer.go +++ b/services/repository/transfer.go @@ -20,10 +20,22 @@ import ( "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/globallock" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" notify_service "code.gitea.io/gitea/services/notify" ) +type LimitReachedError struct{ Limit int } + +func (LimitReachedError) Error() string { + return "Repository limit has been reached" +} + +func IsRepositoryLimitReached(err error) bool { + _, ok := err.(LimitReachedError) + return ok +} + func getRepoWorkingLockKey(repoID int64) string { return fmt.Sprintf("repo_working_%d", repoID) } @@ -49,6 +61,11 @@ func AcceptTransferOwnership(ctx context.Context, repo *repo_model.Repository, d return err } + if !doer.IsAdmin && !repoTransfer.Recipient.CanCreateRepo() { + limit := util.Iif(repoTransfer.Recipient.MaxRepoCreation >= 0, repoTransfer.Recipient.MaxRepoCreation, setting.Repository.MaxCreationLimit) + return LimitReachedError{Limit: limit} + } + if !repoTransfer.CanUserAcceptOrRejectTransfer(ctx, doer) { return util.ErrPermissionDenied } @@ -399,6 +416,11 @@ func StartRepositoryTransfer(ctx context.Context, doer, newOwner *user_model.Use return err } + if !doer.IsAdmin && !newOwner.CanCreateRepo() { + limit := util.Iif(newOwner.MaxRepoCreation >= 0, newOwner.MaxRepoCreation, setting.Repository.MaxCreationLimit) + return LimitReachedError{Limit: limit} + } + var isDirectTransfer bool oldOwnerName := repo.OwnerName diff --git a/services/repository/transfer_test.go b/services/repository/transfer_test.go index 16a8fb6e1e..bf71c7ca2e 100644 --- a/services/repository/transfer_test.go +++ b/services/repository/transfer_test.go @@ -1,4 +1,4 @@ -// Copyright 2019 The Gitea Authors. All rights reserved. +// Copyright 2025 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT package repository @@ -14,11 +14,14 @@ import ( 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/setting" + "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/services/feed" notify_service "code.gitea.io/gitea/services/notify" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var notifySync sync.Once @@ -125,3 +128,40 @@ func TestRepositoryTransfer(t *testing.T) { err = RejectRepositoryTransfer(db.DefaultContext, repo2, doer) assert.True(t, repo_model.IsErrNoPendingTransfer(err)) } + +// Test transfer rejections +func TestRepositoryTransferRejection(t *testing.T) { + require.NoError(t, unittest.PrepareTestDatabase()) + // Set limit to 0 repositories so no repositories can be transferred + defer test.MockVariableValue(&setting.Repository.MaxCreationLimit, 0)() + + // Admin case + doerAdmin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 5}) + + transfer, err := repo_model.GetPendingRepositoryTransfer(db.DefaultContext, repo) + require.NoError(t, err) + require.NotNil(t, transfer) + require.NoError(t, transfer.LoadRecipient(db.DefaultContext)) + + require.True(t, transfer.Recipient.CanCreateRepo()) // admin is not subject to limits + + // Administrator should not be affected by the limits so transfer should be successful + assert.NoError(t, AcceptTransferOwnership(db.DefaultContext, repo, doerAdmin)) + + // Non admin user case + doer := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10}) + repo = unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 21}) + + transfer, err = repo_model.GetPendingRepositoryTransfer(db.DefaultContext, repo) + require.NoError(t, err) + require.NotNil(t, transfer) + require.NoError(t, transfer.LoadRecipient(db.DefaultContext)) + + require.False(t, transfer.Recipient.CanCreateRepo()) // regular user is subject to limits + + // Cannot accept because of the limit + err = AcceptTransferOwnership(db.DefaultContext, repo, doer) + assert.Error(t, err) + assert.True(t, IsRepositoryLimitReached(err)) +} From d54418a7d3611465332d4b86ed3bd5579bda3766 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Tue, 1 Apr 2025 00:39:56 +0000 Subject: [PATCH 05/62] [skip ci] Updated translations via Crowdin --- options/locale/locale_cs-CZ.ini | 1 - options/locale/locale_de-DE.ini | 1 - options/locale/locale_el-GR.ini | 1 - options/locale/locale_es-ES.ini | 1 - options/locale/locale_fa-IR.ini | 1 - options/locale/locale_ga-IE.ini | 11 ++++++++++- options/locale/locale_it-IT.ini | 1 - options/locale/locale_ja-JP.ini | 1 - options/locale/locale_ko-KR.ini | 1 - options/locale/locale_lv-LV.ini | 1 - options/locale/locale_nl-NL.ini | 1 - options/locale/locale_pl-PL.ini | 1 - options/locale/locale_pt-PT.ini | 1 - options/locale/locale_ru-RU.ini | 1 - options/locale/locale_si-LK.ini | 1 - options/locale/locale_sv-SE.ini | 1 - options/locale/locale_tr-TR.ini | 1 - options/locale/locale_uk-UA.ini | 1 - options/locale/locale_zh-HK.ini | 1 - 19 files changed, 10 insertions(+), 19 deletions(-) diff --git a/options/locale/locale_cs-CZ.ini b/options/locale/locale_cs-CZ.ini index a021837002..efbfef984f 100644 --- a/options/locale/locale_cs-CZ.ini +++ b/options/locale/locale_cs-CZ.ini @@ -1529,7 +1529,6 @@ issues.filter_project=Projekt issues.filter_project_all=Všechny projekty issues.filter_project_none=Žádný projekt issues.filter_assignee=Zpracovatel -issues.filter_assginee_no_assignee=Bez zpracovatele issues.filter_poster=Autor issues.filter_user_placeholder=Hledat uživatele issues.filter_user_no_select=Všichni uživatelé diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index 0b0544b89c..1840cd312d 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -1530,7 +1530,6 @@ issues.filter_project=Projekt issues.filter_project_all=Alle Projekte issues.filter_project_none=Kein Projekt issues.filter_assignee=Zuständig -issues.filter_assginee_no_assignee=Niemand zuständig issues.filter_poster=Autor issues.filter_user_placeholder=Benutzer suchen issues.filter_user_no_select=Alle Benutzer diff --git a/options/locale/locale_el-GR.ini b/options/locale/locale_el-GR.ini index f430c0506c..960e3b1581 100644 --- a/options/locale/locale_el-GR.ini +++ b/options/locale/locale_el-GR.ini @@ -1377,7 +1377,6 @@ issues.filter_project=Έργο issues.filter_project_all=Όλα τα έργα issues.filter_project_none=Χωρίς έργα issues.filter_assignee=Αποδέκτης -issues.filter_assginee_no_assignee=Κανένας Αποδέκτης issues.filter_poster=Συγγραφέας issues.filter_type=Τύπος issues.filter_type.all_issues=Όλα τα ζητήματα diff --git a/options/locale/locale_es-ES.ini b/options/locale/locale_es-ES.ini index debf9a6f5d..280c735c79 100644 --- a/options/locale/locale_es-ES.ini +++ b/options/locale/locale_es-ES.ini @@ -1367,7 +1367,6 @@ issues.filter_project=Proyecto issues.filter_project_all=Todos los proyectos issues.filter_project_none=Ningún proyecto issues.filter_assignee=Asignada a -issues.filter_assginee_no_assignee=Sin asignado issues.filter_poster=Autor issues.filter_type=Tipo issues.filter_type.all_issues=Todas las incidencias diff --git a/options/locale/locale_fa-IR.ini b/options/locale/locale_fa-IR.ini index 5c96cea8f6..c7bef6e6dc 100644 --- a/options/locale/locale_fa-IR.ini +++ b/options/locale/locale_fa-IR.ini @@ -1059,7 +1059,6 @@ issues.filter_label_no_select=تمامی برچسب‎ها issues.filter_milestone=نقطه عطف issues.filter_project_none=هیچ پروژه ثبت نشده issues.filter_assignee=مسئول رسیدگی -issues.filter_assginee_no_assignee=بدون مسئول رسیدگی issues.filter_type=نوع issues.filter_type.all_issues=همه مسائل issues.filter_type.assigned_to_you=به شما محول شده diff --git a/options/locale/locale_ga-IE.ini b/options/locale/locale_ga-IE.ini index bb2bf6f15f..a63c097cd0 100644 --- a/options/locale/locale_ga-IE.ini +++ b/options/locale/locale_ga-IE.ini @@ -926,6 +926,9 @@ permission_not_set=Níl leagtha permission_no_access=Gan rochtain permission_read=Léigh permission_write=Léigh agus Scríobh +permission_anonymous_read=Léamh gan Ainm +permission_everyone_read=Léigh gach duine +permission_everyone_write=Scríobh gach duine access_token_desc=Ní chuireann ceadchomharthaí roghnaithe ach teorainn leis an údarú do na bealaí API comhfhreagracha. Léigh doiciméadúchán chun tuilleadh eolais a fháil. at_least_one_permission=Ní mór duit cead amháin ar a laghad a roghnú chun comhartha a chruthú permissions_list=Ceadanna: @@ -1138,6 +1141,7 @@ transfer.no_permission_to_reject=Níl cead agat an aistriú seo a dhiúltú. desc.private=Príobháideach desc.public=Poiblí +desc.public_access=Rochtain Phoiblí desc.template=Teimpléad desc.internal=Inmheánach desc.archived=Cartlannaithe @@ -1546,7 +1550,6 @@ issues.filter_project=Tionscadal issues.filter_project_all=Gach tionscadal issues.filter_project_none=Gan aon tionscadal issues.filter_assignee=Sannaitheoir -issues.filter_assginee_no_assignee=Sannta do dhuine ar bith issues.filter_assignee_any_assignee=Sannta do dhuine ar bith issues.filter_poster=Údar issues.filter_user_placeholder=Cuardaigh úsáideoirí @@ -2132,6 +2135,12 @@ contributors.contribution_type.deletions=Scriosadh settings=Socruithe settings.desc=Is é socruithe an áit ar féidir leat na socruithe don stóras a bhainistiú settings.options=Stóras +settings.public_access=Rochtain Phoiblí +settings.public_access_desc=Cumraigh ceadanna rochtana an chuairteora phoiblí chun réamhshocruithe an stóras seo a shárú. +settings.public_access.docs.not_set=Gan Socrú: níl cead rochtana poiblí breise ar bith. Leanann cead an chuairteora infheictheacht an stór agus ceadanna na mball. +settings.public_access.docs.anonymous_read=Léamh gan Ainm: is féidir le húsáideoirí nach bhfuil logáilte isteach rochtain a fháil ar an aonad le cead léite. +settings.public_access.docs.everyone_read=Léamh ag Gach Duine: is féidir le gach úsáideoir logáilte isteach rochtain a fháil ar an aonad le cead léite. Ciallaíonn cead léite na n-aonad eisiúna/iarrataí tarraingthe freisin gur féidir le húsáideoirí saincheisteanna nua/iarratais tarraingthe a chruthú. +settings.public_access.docs.everyone_write=Scríobh Gach Duine: tá cead scríofa ag gach úsáideoir logáilte isteach don aonad. Ní thacaíonn ach aonad Vicí leis an gcead seo. settings.collaboration=Comhoibritheoirí settings.collaboration.admin=Riarthóir settings.collaboration.write=Scríobh diff --git a/options/locale/locale_it-IT.ini b/options/locale/locale_it-IT.ini index 3ce5a6770f..810f1040f5 100644 --- a/options/locale/locale_it-IT.ini +++ b/options/locale/locale_it-IT.ini @@ -1143,7 +1143,6 @@ issues.filter_milestone=Traguardo issues.filter_project=Progetto issues.filter_project_none=Nessun progetto issues.filter_assignee=Assegnatario -issues.filter_assginee_no_assignee=Nessun assegnatario issues.filter_poster=Autore issues.filter_type=Tipo issues.filter_type.all_issues=Tutti i problemi diff --git a/options/locale/locale_ja-JP.ini b/options/locale/locale_ja-JP.ini index 7fe0f4332d..df987fa3b9 100644 --- a/options/locale/locale_ja-JP.ini +++ b/options/locale/locale_ja-JP.ini @@ -1546,7 +1546,6 @@ issues.filter_project=プロジェクト issues.filter_project_all=すべてのプロジェクト issues.filter_project_none=プロジェクトなし issues.filter_assignee=担当者 -issues.filter_assginee_no_assignee=担当者なし issues.filter_assignee_any_assignee=担当者あり issues.filter_poster=作成者 issues.filter_user_placeholder=ユーザーを検索 diff --git a/options/locale/locale_ko-KR.ini b/options/locale/locale_ko-KR.ini index 697371c2c9..ddb80dde1d 100644 --- a/options/locale/locale_ko-KR.ini +++ b/options/locale/locale_ko-KR.ini @@ -707,7 +707,6 @@ issues.filter_label=레이블 issues.filter_label_no_select=모든 레이블 issues.filter_milestone=마일스톤 issues.filter_assignee=담당자 -issues.filter_assginee_no_assignee=담당자 없음 issues.filter_type=유형 issues.filter_type.all_issues=모든 이슈 issues.filter_type.assigned_to_you=나에게 할당됨 diff --git a/options/locale/locale_lv-LV.ini b/options/locale/locale_lv-LV.ini index 4439537fd7..42c12def23 100644 --- a/options/locale/locale_lv-LV.ini +++ b/options/locale/locale_lv-LV.ini @@ -1383,7 +1383,6 @@ issues.filter_project=Projekts issues.filter_project_all=Visi projekti issues.filter_project_none=Nav projekta issues.filter_assignee=Atbildīgais -issues.filter_assginee_no_assignee=Nav atbildīgā issues.filter_poster=Autors issues.filter_type=Veids issues.filter_type.all_issues=Visas problēmas diff --git a/options/locale/locale_nl-NL.ini b/options/locale/locale_nl-NL.ini index 77bf2d2d59..0ad14807d6 100644 --- a/options/locale/locale_nl-NL.ini +++ b/options/locale/locale_nl-NL.ini @@ -1141,7 +1141,6 @@ issues.filter_milestone=Mijlpaal issues.filter_project=Project issues.filter_project_none=Geen project issues.filter_assignee=Aangewezene -issues.filter_assginee_no_assignee=Geen verantwoordelijke issues.filter_poster=Auteur issues.filter_type=Type issues.filter_type.all_issues=Alle kwesties diff --git a/options/locale/locale_pl-PL.ini b/options/locale/locale_pl-PL.ini index f64b8771ac..9673db2a71 100644 --- a/options/locale/locale_pl-PL.ini +++ b/options/locale/locale_pl-PL.ini @@ -1054,7 +1054,6 @@ issues.filter_label_no_select=Wszystkie etykiety issues.filter_milestone=Kamień milowy issues.filter_project_none=Brak projektu issues.filter_assignee=Przypisany -issues.filter_assginee_no_assignee=Brak przypisania issues.filter_type=Typ issues.filter_type.all_issues=Wszystkie zgłoszenia issues.filter_type.assigned_to_you=Przypisane do Ciebie diff --git a/options/locale/locale_pt-PT.ini b/options/locale/locale_pt-PT.ini index 0b0ddbee56..fdb7389b51 100644 --- a/options/locale/locale_pt-PT.ini +++ b/options/locale/locale_pt-PT.ini @@ -1550,7 +1550,6 @@ issues.filter_project=Planeamento issues.filter_project_all=Todos os planeamentos issues.filter_project_none=Nenhum planeamento issues.filter_assignee=Encarregado -issues.filter_assginee_no_assignee=Sem encarregado issues.filter_assignee_any_assignee=Atribuído a qualquer pessoa issues.filter_poster=Autor(a) issues.filter_user_placeholder=Procurar utilizadores diff --git a/options/locale/locale_ru-RU.ini b/options/locale/locale_ru-RU.ini index fe52165837..68246ff751 100644 --- a/options/locale/locale_ru-RU.ini +++ b/options/locale/locale_ru-RU.ini @@ -1355,7 +1355,6 @@ issues.filter_project=Проект issues.filter_project_all=Все проекты issues.filter_project_none=Нет проекта issues.filter_assignee=Назначено -issues.filter_assginee_no_assignee=Нет ответственного issues.filter_poster=Автор issues.filter_type=Тип issues.filter_type.all_issues=Все задачи diff --git a/options/locale/locale_si-LK.ini b/options/locale/locale_si-LK.ini index 2cd7fb29b9..7c95b254e8 100644 --- a/options/locale/locale_si-LK.ini +++ b/options/locale/locale_si-LK.ini @@ -1025,7 +1025,6 @@ issues.filter_label_no_select=සියලු ලේබල issues.filter_milestone=සන්ධිස්ථානය issues.filter_project_none=ව්‍යාපෘති නැත issues.filter_assignee=අස්ගිනී -issues.filter_assginee_no_assignee=කිසිදු අස්වැද්දුමක් issues.filter_type=වර්ගය issues.filter_type.all_issues=සියලු ගැටළු issues.filter_type.assigned_to_you=ඔබට පවරා ඇත diff --git a/options/locale/locale_sv-SE.ini b/options/locale/locale_sv-SE.ini index dbe53aaadf..92917c103a 100644 --- a/options/locale/locale_sv-SE.ini +++ b/options/locale/locale_sv-SE.ini @@ -873,7 +873,6 @@ issues.filter_label_no_select=Alla etiketter issues.filter_milestone=Milsten issues.filter_project_none=Inget projekt issues.filter_assignee=Förvärvare -issues.filter_assginee_no_assignee=Ingen tilldelad issues.filter_type=Typ issues.filter_type.all_issues=Alla ärenden issues.filter_type.assigned_to_you=Tilldelad dig diff --git a/options/locale/locale_tr-TR.ini b/options/locale/locale_tr-TR.ini index 1a044fca83..faf442431b 100644 --- a/options/locale/locale_tr-TR.ini +++ b/options/locale/locale_tr-TR.ini @@ -1484,7 +1484,6 @@ issues.filter_project=Proje issues.filter_project_all=Tüm projeler issues.filter_project_none=Proje yok issues.filter_assignee=Atanan -issues.filter_assginee_no_assignee=Atanan yok issues.filter_poster=Yazar issues.filter_type=Tür issues.filter_type.all_issues=Tüm konular diff --git a/options/locale/locale_uk-UA.ini b/options/locale/locale_uk-UA.ini index 8c8911ceb6..995de50b61 100644 --- a/options/locale/locale_uk-UA.ini +++ b/options/locale/locale_uk-UA.ini @@ -1071,7 +1071,6 @@ issues.filter_milestone=Етап issues.filter_project=Проєкт issues.filter_project_none=Проєкт відсутній issues.filter_assignee=Виконавець -issues.filter_assginee_no_assignee=Немає виконавця issues.filter_type=Тип issues.filter_type.all_issues=Всі задачі issues.filter_type.assigned_to_you=Призначене вам diff --git a/options/locale/locale_zh-HK.ini b/options/locale/locale_zh-HK.ini index b924faba09..ae6c6c3552 100644 --- a/options/locale/locale_zh-HK.ini +++ b/options/locale/locale_zh-HK.ini @@ -416,7 +416,6 @@ issues.delete_branch_at=`刪除分支 %s %s` issues.filter_label=標籤篩選 issues.filter_milestone=里程碑篩選 issues.filter_assignee=指派人篩選 -issues.filter_assginee_no_assignee=無負責人 issues.filter_type=類型篩選 issues.filter_type.all_issues=所有問題 issues.filter_type.assigned_to_you=指派給您的 From 86c1a33369e0458748d30d0084d3199e7f736855 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 1 Apr 2025 15:02:30 +0800 Subject: [PATCH 06/62] Fix some UI bugs and clean up unused tests (#34088) 1. Make the material icon falls back to basic theme correctly 2. Remove `TestAttributeReader`, the problem has been resolved. 3. Fix `toggleElem` bug and add tests --- modules/fileicon/material.go | 12 +++--- modules/git/repo_attribute_test.go | 60 ------------------------------ web_src/js/utils/dom.test.ts | 18 ++++++++- web_src/js/utils/dom.ts | 2 +- 4 files changed, 23 insertions(+), 69 deletions(-) diff --git a/modules/fileicon/material.go b/modules/fileicon/material.go index aa31cd8d7c..cbdb962ee3 100644 --- a/modules/fileicon/material.go +++ b/modules/fileicon/material.go @@ -99,12 +99,9 @@ func (m *MaterialIconProvider) FileIcon(ctx reqctx.RequestContext, entry *git.Tr } name := m.findIconNameByGit(entry) - if name == "folder" { - // the material icon pack's "folder" icon doesn't look good, so use our built-in one - // keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work - return svg.RenderHTML("material-folder-generic", 16, "octicon-file-directory-fill") - } - if iconSVG, ok := m.svgs[name]; ok && iconSVG != "" { + // the material icon pack's "folder" icon doesn't look good, so use our built-in one + // keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work + if iconSVG, ok := m.svgs[name]; ok && name != "folder" && iconSVG != "" { // keep the old "octicon-xxx" class name to make some "theme plugin selector" could still work extraClass := "octicon-file" switch { @@ -115,7 +112,8 @@ func (m *MaterialIconProvider) FileIcon(ctx reqctx.RequestContext, entry *git.Tr } return m.renderFileIconSVG(ctx, name, iconSVG, extraClass) } - return svg.RenderHTML("octicon-file") + // TODO: use an interface or wrapper for git.Entry to make the code testable. + return BasicThemeIcon(entry) } func (m *MaterialIconProvider) findIconNameWithLangID(s string) string { diff --git a/modules/git/repo_attribute_test.go b/modules/git/repo_attribute_test.go index c4f5bac46d..d8fd9f0e8d 100644 --- a/modules/git/repo_attribute_test.go +++ b/modules/git/repo_attribute_test.go @@ -4,16 +4,10 @@ package git import ( - "context" - mathRand "math/rand/v2" - "path/filepath" - "slices" - "sync" "testing" "time" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) { @@ -101,57 +95,3 @@ func Test_nulSeparatedAttributeWriter_ReadAttribute(t *testing.T) { Value: "unspecified", }, attr) } - -func TestAttributeReader(t *testing.T) { - t.Skip() // for debug purpose only, do not run in CI - - ctx := t.Context() - - timeout := 1 * time.Second - repoPath := filepath.Join(testReposDir, "language_stats_repo") - commitRef := "HEAD" - - oneRound := func(t *testing.T, roundIdx int) { - ctx, cancel := context.WithTimeout(ctx, timeout) - _ = cancel - gitRepo, err := OpenRepository(ctx, repoPath) - require.NoError(t, err) - defer gitRepo.Close() - - commit, err := gitRepo.GetCommit(commitRef) - require.NoError(t, err) - - files, err := gitRepo.LsFiles() - require.NoError(t, err) - - randomFiles := slices.Clone(files) - randomFiles = append(randomFiles, "any-file-1", "any-file-2") - - t.Logf("Round %v with %d files", roundIdx, len(randomFiles)) - - attrReader, deferrable := gitRepo.CheckAttributeReader(commit.ID.String()) - defer deferrable() - - wg := sync.WaitGroup{} - wg.Add(1) - - go func() { - for { - file := randomFiles[mathRand.IntN(len(randomFiles))] - _, err := attrReader.CheckPath(file) - if err != nil { - for i := 0; i < 10; i++ { - _, _ = attrReader.CheckPath(file) - } - break - } - } - wg.Done() - }() - wg.Wait() - } - - for i := 0; i < 100; i++ { - oneRound(t, i) - } -} diff --git a/web_src/js/utils/dom.test.ts b/web_src/js/utils/dom.test.ts index 6e71596850..6a3af91556 100644 --- a/web_src/js/utils/dom.test.ts +++ b/web_src/js/utils/dom.test.ts @@ -1,4 +1,10 @@ -import {createElementFromAttrs, createElementFromHTML, queryElemChildren, querySingleVisibleElem} from './dom.ts'; +import { + createElementFromAttrs, + createElementFromHTML, + queryElemChildren, + querySingleVisibleElem, + toggleElem, +} from './dom.ts'; test('createElementFromHTML', () => { expect(createElementFromHTML('foobar').outerHTML).toEqual('foobar'); @@ -32,3 +38,13 @@ test('queryElemChildren', () => { const children = queryElemChildren(el, '.a'); expect(children.length).toEqual(1); }); + +test('toggleElem', () => { + const el = createElementFromHTML('

a
b

'); + toggleElem(el.children); + expect(el.outerHTML).toEqual('

a
b

'); + toggleElem(el.children, false); + expect(el.outerHTML).toEqual('

a
b

'); + toggleElem(el.children, true); + expect(el.outerHTML).toEqual('

a
b

'); +}); diff --git a/web_src/js/utils/dom.ts b/web_src/js/utils/dom.ts index 6d38ffa8cd..b3debfde9e 100644 --- a/web_src/js/utils/dom.ts +++ b/web_src/js/utils/dom.ts @@ -44,7 +44,7 @@ export function toggleClass(el: ElementArg, className: string, force?: boolean) * @param force force=true to show or force=false to hide, undefined to toggle */ export function toggleElem(el: ElementArg, force?: boolean) { - toggleClass(el, 'tw-hidden', !force); + toggleClass(el, 'tw-hidden', force === undefined ? force : !force); } export function showElem(el: ElementArg) { From 56e42be36dd6daf86073732188da43316ccad608 Mon Sep 17 00:00:00 2001 From: bytedream <63594396+bytedream@users.noreply.github.com> Date: Tue, 1 Apr 2025 11:42:10 +0200 Subject: [PATCH 07/62] Add flat-square action badge style (#34062) Adds the `flat-square` style to action badges. Styles can be selected by adding `?style=${svg}`; - - const mermaidBlock = document.createElement('div'); - mermaidBlock.classList.add('mermaid-block', 'is-loading', 'tw-hidden'); - mermaidBlock.append(iframe); - - const btn = makeCodeCopyButton(); - btn.setAttribute('data-clipboard-text', source); - mermaidBlock.append(btn); - - const updateIframeHeight = () => { - const body = iframe.contentWindow?.document?.body; - if (body) { - iframe.style.height = `${body.clientHeight}px`; - } - }; - - iframe.addEventListener('load', () => { - pre.replaceWith(mermaidBlock); - mermaidBlock.classList.remove('tw-hidden'); - updateIframeHeight(); - setTimeout(() => { // avoid flash of iframe background - mermaidBlock.classList.remove('is-loading'); - iframe.classList.remove('tw-invisible'); - }, 0); - - // update height when element's visibility state changes, for example when the diagram is inside - // a
+ block and the
block becomes visible upon user interaction, it - // would initially set a incorrect height and the correct height is set during this callback. - (new IntersectionObserver(() => { - updateIframeHeight(); - }, {root: document.documentElement})).observe(iframe); + mermaid.initialize({ + startOnLoad: false, + theme: isDarkTheme() ? 'dark' : 'neutral', + securityLevel: 'strict', + suppressErrorRendering: true, }); - document.body.append(mermaidBlock); - } catch (err) { - displayError(pre, err); - } + const pre = el.closest('pre'); + if (pre.hasAttribute('data-render-done')) return; + + const source = el.textContent; + if (mermaidMaxSourceCharacters >= 0 && source.length > mermaidMaxSourceCharacters) { + displayError(pre, new Error(`Mermaid source of ${source.length} characters exceeds the maximum allowed length of ${mermaidMaxSourceCharacters}.`)); + return; + } + + try { + await mermaid.parse(source); + } catch (err) { + displayError(pre, err); + return; + } + + try { + // can't use bindFunctions here because we can't cross the iframe boundary. This + // means js-based interactions won't work but they aren't intended to work either + const {svg} = await mermaid.render('mermaid', source); + + const iframe = document.createElement('iframe'); + iframe.classList.add('markup-content-iframe', 'tw-invisible'); + iframe.srcdoc = `${svg}`; + + const mermaidBlock = document.createElement('div'); + mermaidBlock.classList.add('mermaid-block', 'is-loading', 'tw-hidden'); + mermaidBlock.append(iframe); + + const btn = makeCodeCopyButton(); + btn.setAttribute('data-clipboard-text', source); + mermaidBlock.append(btn); + + const updateIframeHeight = () => { + const body = iframe.contentWindow?.document?.body; + if (body) { + iframe.style.height = `${body.clientHeight}px`; + } + }; + + iframe.addEventListener('load', () => { + pre.replaceWith(mermaidBlock); + mermaidBlock.classList.remove('tw-hidden'); + updateIframeHeight(); + setTimeout(() => { // avoid flash of iframe background + mermaidBlock.classList.remove('is-loading'); + iframe.classList.remove('tw-invisible'); + }, 0); + + // update height when element's visibility state changes, for example when the diagram is inside + // a
+ block and the
block becomes visible upon user interaction, it + // would initially set a incorrect height and the correct height is set during this callback. + (new IntersectionObserver(() => { + updateIframeHeight(); + }, {root: document.documentElement})).observe(iframe); + }); + + document.body.append(mermaidBlock); + } catch (err) { + displayError(pre, err); + } + }); } From a62ed19da6093c94401cade5701b44684820fcf6 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 6 Apr 2025 17:13:02 +0800 Subject: [PATCH 29/62] Use `overflow-wrap: anywhere` to replace `word-break: break-all` (#34126) --- templates/status/500.tmpl | 2 +- web_src/css/base.css | 1 - web_src/css/markup/content.css | 3 +-- web_src/css/repo.css | 3 +-- web_src/js/components/RepoActionView.vue | 1 - 5 files changed, 3 insertions(+), 7 deletions(-) diff --git a/templates/status/500.tmpl b/templates/status/500.tmpl index 198f1ea898..6dfa2d8a8c 100644 --- a/templates/status/500.tmpl +++ b/templates/status/500.tmpl @@ -38,7 +38,7 @@ {{if .ErrorMsg}}

{{ctx.Locale.Tr "error.occurred"}}:

-
{{.ErrorMsg}}
+
{{.ErrorMsg}}
{{end}}
diff --git a/web_src/css/base.css b/web_src/css/base.css index f276ced596..de656c0d95 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -878,7 +878,6 @@ overflow-menu .ui.label { .code-inner { font: 12px var(--fonts-monospace); white-space: pre-wrap; - word-break: break-all; overflow-wrap: anywhere; line-height: inherit; /* needed for inline code preview in markup */ } diff --git a/web_src/css/markup/content.css b/web_src/css/markup/content.css index 7368a1fb00..937224a9d7 100644 --- a/web_src/css/markup/content.css +++ b/web_src/css/markup/content.css @@ -447,8 +447,7 @@ margin: 0; font-size: 100%; white-space: pre-wrap; - word-break: break-all; - overflow-wrap: break-word; + overflow-wrap: anywhere; background: transparent; border: 0; } diff --git a/web_src/css/repo.css b/web_src/css/repo.css index 87af299dad..db44e2a778 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -1724,8 +1724,7 @@ tbody.commit-list { line-height: 18px; margin: 1em; white-space: pre-wrap; - word-break: break-all; - overflow-wrap: break-word; + overflow-wrap: anywhere; } .content-history-detail-dialog .header .avatar { diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue index 2ef528620d..640ad8341f 100644 --- a/web_src/js/components/RepoActionView.vue +++ b/web_src/js/components/RepoActionView.vue @@ -955,7 +955,6 @@ export default defineComponent({ .job-step-logs .job-log-line .log-msg { flex: 1; - word-break: break-all; white-space: break-spaces; margin-left: 10px; overflow-wrap: anywhere; From e94f8d56f113077b2fc8972b6ba31a7ad366cfb9 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 6 Apr 2025 17:38:08 +0800 Subject: [PATCH 30/62] Correctly handle submodule view and avoid throwing 500 error (#34121) Auto-redirect for in-site links, and show 404 for external links (to avoid open redirect or phishing) --- routers/web/repo/view_home.go | 27 +++++++++++++++++++++++++ routers/web/repo/view_home_test.go | 32 ++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 routers/web/repo/view_home_test.go diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go index c6f462bccf..3b053821ee 100644 --- a/routers/web/repo/view_home.go +++ b/routers/web/repo/view_home.go @@ -20,6 +20,8 @@ import ( unit_model "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" + giturl "code.gitea.io/gitea/modules/git/url" + "code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" @@ -302,8 +304,33 @@ func handleRepoEmptyOrBroken(ctx *context.Context) { ctx.Redirect(link) } +func handleRepoViewSubmodule(ctx *context.Context, submodule *git.SubModule) { + submoduleRepoURL, err := giturl.ParseRepositoryURL(ctx, submodule.URL) + if err != nil { + HandleGitError(ctx, "prepareToRenderDirOrFile: ParseRepositoryURL", err) + return + } + submoduleURL := giturl.MakeRepositoryWebLink(submoduleRepoURL) + if httplib.IsCurrentGiteaSiteURL(ctx, submoduleURL) { + ctx.RedirectToCurrentSite(submoduleURL) + } else { + // don't auto-redirect to external URL, to avoid open redirect or phishing + ctx.Data["NotFoundPrompt"] = submoduleURL + ctx.NotFound(nil) + } +} + func prepareToRenderDirOrFile(entry *git.TreeEntry) func(ctx *context.Context) { return func(ctx *context.Context) { + if entry.IsSubModule() { + submodule, err := ctx.Repo.Commit.GetSubModule(entry.Name()) + if err != nil { + HandleGitError(ctx, "prepareToRenderDirOrFile: GetSubModule", err) + return + } + handleRepoViewSubmodule(ctx, submodule) + return + } if entry.IsDir() { prepareToRenderDirectory(ctx) } else { diff --git a/routers/web/repo/view_home_test.go b/routers/web/repo/view_home_test.go new file mode 100644 index 0000000000..6264dba71c --- /dev/null +++ b/routers/web/repo/view_home_test.go @@ -0,0 +1,32 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/models/unittest" + git_module "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/contexttest" + + "github.com/stretchr/testify/assert" +) + +func TestViewHomeSubmoduleRedirect(t *testing.T) { + unittest.PrepareTestEnv(t) + + ctx, _ := contexttest.MockContext(t, "/user2/repo1/src/branch/master/test-submodule") + submodule := &git_module.SubModule{Path: "test-submodule", URL: setting.AppURL + "user2/repo-other.git"} + handleRepoViewSubmodule(ctx, submodule) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, "/user2/repo-other", ctx.Resp.Header().Get("Location")) + + ctx, _ = contexttest.MockContext(t, "/user2/repo1/src/branch/master/test-submodule") + submodule = &git_module.SubModule{Path: "test-submodule", URL: "https://other/user2/repo-other.git"} + handleRepoViewSubmodule(ctx, submodule) + // do not auto-redirect for external URLs, to avoid open redirect or phishing + assert.Equal(t, http.StatusNotFound, ctx.Resp.WrittenStatus()) +} From 3fe082a5a33011f3467eb2ca7dfaf445d283c6db Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 7 Apr 2025 01:08:10 +0800 Subject: [PATCH 31/62] Remove dead code: RepoRef (#34131) The RepoRef is a no-op since Refactor ref type (#33242) (Jan 14) --- routers/web/web.go | 28 ++++++++++++++-------------- services/context/repo.go | 6 ------ 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/routers/web/web.go b/routers/web/web.go index 7948c5f5ff..fcddcad1b1 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1211,7 +1211,7 @@ func registerWebRoutes(m *web.Router) { m.Get("/comments/{id}/attachments", repo.GetCommentAttachments) m.Get("/labels", repo.RetrieveLabelsForList, repo.Labels) m.Get("/milestones", repo.Milestones) - m.Get("/milestone/{id}", context.RepoRef(), repo.MilestoneIssuesAndPulls) + m.Get("/milestone/{id}", repo.MilestoneIssuesAndPulls) m.Get("/issues/suggestions", repo.IssueSuggestions) }, optSignIn, context.RepoAssignment, reqRepoIssuesOrPullsReader) // issue/pull attachments, labels, milestones // end "/{username}/{reponame}": view milestone, label, issue, pull, etc @@ -1225,9 +1225,9 @@ func registerWebRoutes(m *web.Router) { m.Group("/{username}/{reponame}", func() { // edit issues, pulls, labels, milestones, etc m.Group("/issues", func() { m.Group("/new", func() { - m.Combo("").Get(context.RepoRef(), repo.NewIssue). + m.Combo("").Get(repo.NewIssue). Post(web.Bind(forms.CreateIssueForm{}), repo.NewIssuePost) - m.Get("/choose", context.RepoRef(), repo.NewIssueChooseTemplate) + m.Get("/choose", repo.NewIssueChooseTemplate) }) m.Get("/search", repo.SearchRepoIssuesJSON) }, reqUnitIssuesReader) @@ -1290,7 +1290,7 @@ func registerWebRoutes(m *web.Router) { m.Post("/edit", web.Bind(forms.CreateLabelForm{}), repo.UpdateLabel) m.Post("/delete", repo.DeleteLabel) m.Post("/initialize", web.Bind(forms.InitializeLabelsForm{}), repo.InitializeLabels) - }, reqRepoIssuesOrPullsWriter, context.RepoRef()) + }, reqRepoIssuesOrPullsWriter) m.Group("/milestones", func() { m.Combo("/new").Get(repo.NewMilestone). @@ -1299,7 +1299,7 @@ func registerWebRoutes(m *web.Router) { m.Post("/{id}/edit", web.Bind(forms.CreateMilestoneForm{}), repo.EditMilestonePost) m.Post("/{id}/{action}", repo.ChangeMilestoneStatus) m.Post("/delete", repo.DeleteMilestone) - }, reqRepoIssuesOrPullsWriter, context.RepoRef()) + }, reqRepoIssuesOrPullsWriter) // FIXME: many "pulls" requests are sent to "issues" endpoints incorrectly, need to move these routes to the proper place m.Group("/issues", func() { @@ -1377,7 +1377,7 @@ func registerWebRoutes(m *web.Router) { m.Post("/delete", repo.DeleteRelease) m.Post("/attachments", repo.UploadReleaseAttachment) m.Post("/attachments/remove", repo.DeleteAttachment) - }, reqSignIn, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, context.RepoRef()) + }, reqSignIn, context.RepoMustNotBeArchived(), reqRepoReleaseWriter) m.Group("/releases", func() { m.Get("/edit/*", repo.EditRelease) m.Post("/edit/*", web.Bind(forms.EditReleaseForm{}), repo.EditReleasePost) @@ -1506,19 +1506,19 @@ func registerWebRoutes(m *web.Router) { m.Get(".diff", repo.DownloadPullDiff) m.Get(".patch", repo.DownloadPullPatch) m.Group("/commits", func() { - m.Get("", context.RepoRef(), repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits) - m.Get("/list", context.RepoRef(), repo.GetPullCommits) - m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit) + m.Get("", repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits) + m.Get("/list", repo.GetPullCommits) + m.Get("/{sha:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit) }) m.Post("/merge", context.RepoMustNotBeArchived(), web.Bind(forms.MergePullRequestForm{}), repo.MergePullRequest) m.Post("/cancel_auto_merge", context.RepoMustNotBeArchived(), repo.CancelAutoMergePullRequest) m.Post("/update", repo.UpdatePullRequest) m.Post("/set_allow_maintainer_edit", web.Bind(forms.UpdateAllowEditsForm{}), repo.SetAllowEdits) - m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest) + m.Post("/cleanup", context.RepoMustNotBeArchived(), repo.CleanUpPullRequest) m.Group("/files", func() { - m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForAllCommitsOfPr) - m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesStartingFromCommit) - m.Get("/{shaFrom:[a-f0-9]{7,40}}..{shaTo:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange) + m.Get("", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForAllCommitsOfPr) + m.Get("/{sha:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesStartingFromCommit) + m.Get("/{shaFrom:[a-f0-9]{7,40}}..{shaTo:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange) m.Group("/reviews", func() { m.Get("/new_comment", repo.RenderNewCodeCommentForm) m.Post("/comments", web.Bind(forms.CodeCommentForm{}), repo.SetShowOutdatedComments, repo.CreateCodeComment) @@ -1605,7 +1605,7 @@ func registerWebRoutes(m *web.Router) { m.Get("/tree/*", repo.RedirectRepoTreeToSrc) // redirect "/owner/repo/tree/*" requests to "/owner/repo/src/*" m.Get("/blob/*", repo.RedirectRepoBlobToCommit) // redirect "/owner/repo/blob/*" requests to "/owner/repo/src/commit/*" - m.Get("/forks", context.RepoRef(), repo.Forks) + m.Get("/forks", repo.Forks) m.Get("/commit/{sha:([a-f0-9]{7,64})}.{ext:patch|diff}", repo.MustBeNotEmpty, repo.RawDiff) m.Post("/lastcommit/*", context.RepoRefByType(git.RefTypeCommit), repo.LastCommit) }, optSignIn, context.RepoAssignment, reqUnitCodeReader) diff --git a/services/context/repo.go b/services/context/repo.go index 4e91e53e7d..6f5c772f5e 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -669,12 +669,6 @@ func RepoAssignment(ctx *Context) { const headRefName = "HEAD" -func RepoRef() func(*Context) { - // old code does: return RepoRefByType(git.RefTypeBranch) - // in most cases, it is an abuse, so we just disable it completely and fix the abuses one by one (if there is anything wrong) - return nil -} - func getRefNameFromPath(repo *Repository, path string, isExist func(string) bool) string { refName := "" parts := strings.Split(path, "/") From bcc38eb35fdd2b46fbefea0a52fbba5e934fee3f Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 7 Apr 2025 01:34:59 +0800 Subject: [PATCH 32/62] Make markdown render match GitHub's behavior (#34129) For #2246 --- modules/markup/html.go | 3 ++- modules/markup/html_test.go | 12 +++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/modules/markup/html.go b/modules/markup/html.go index 05701eebde..0e074cbcfa 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -85,7 +85,8 @@ var globalVars = sync.OnceValue(func() *globalVarsType { // codePreviewPattern matches "http://domain/.../{owner}/{repo}/src/commit/{commit}/{filepath}#L10-L20" v.codePreviewPattern = regexp.MustCompile(`https?://\S+/([^\s/]+)/([^\s/]+)/src/commit/([0-9a-f]{7,64})(/\S+)#(L\d+(-L\d+)?)`) - v.tagCleaner = regexp.MustCompile(`<((?:/?\w+/\w+)|(?:/[\w ]+/)|(/?[hH][tT][mM][lL]\b)|(/?[hH][eE][aA][dD]\b))`) + // cleans: "go-gitea/gitea#12345`) - // Test that other post processing still works. + // Test that other post-processing still works. test( ":gitea:", `:gitea:`) @@ -499,6 +499,12 @@ func TestPostProcess_RenderDocument(t *testing.T) { `Some text with 😄 in the middle`) test("http://localhost:3000/person/repo/issues/4#issuecomment-1234", `person/repo#4 (comment)`) + + // special tags, GitHub's behavior, and for unclosed tags, output as text content as much as possible + test("", `<script>a</script>`) + test("", `<style>a</STYLE>`) } func TestIssue16020(t *testing.T) { From 8c9d2bdee34ba31bbb8c69b45b746c597505fb1b Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Mon, 7 Apr 2025 03:35:08 +0800 Subject: [PATCH 33/62] Keep file tree view icons consistent with icon theme (#33921) Fix #33914 before: ![3000-gogitea-gitea-y4ulxr46c4k ws-us118 gitpod io_test_test gitea_src_branch_main_ gitmodules](https://github.com/user-attachments/assets/ca50eeff-cc44-4041-b01f-c0c5bdd3b6aa) after: ![3000-gogitea-gitea-y4ulxr46c4k ws-us118 gitpod io_test_test gitea_src_branch_main_README md](https://github.com/user-attachments/assets/3b87fdbd-81d0-4831-8a74-4dbfcd5b6d91) --------- Co-authored-by: wxiaoguang --- modules/fileicon/material.go | 22 +++------ modules/fileicon/render.go | 52 ++++++++++++++++++++++ modules/git/error.go | 12 ++--- modules/git/tree_entry.go | 42 ++++++++--------- modules/git/tree_entry_mode.go | 20 +++------ modules/templates/util_render.go | 9 ---- routers/web/repo/treelist.go | 6 ++- routers/web/repo/view.go | 12 +++++ routers/web/repo/view_readme.go | 2 +- services/repository/files/tree.go | 38 ++++++++++++---- services/repository/files/tree_test.go | 19 ++++++-- templates/repo/view_list.tmpl | 3 +- web_src/js/components/ViewFileTree.vue | 10 +++++ web_src/js/components/ViewFileTreeItem.vue | 9 +++- 14 files changed, 170 insertions(+), 86 deletions(-) create mode 100644 modules/fileicon/render.go diff --git a/modules/fileicon/material.go b/modules/fileicon/material.go index cbdb962ee3..557f7ca9e4 100644 --- a/modules/fileicon/material.go +++ b/modules/fileicon/material.go @@ -13,7 +13,6 @@ import ( "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/options" - "code.gitea.io/gitea/modules/reqctx" "code.gitea.io/gitea/modules/svg" ) @@ -62,13 +61,7 @@ func (m *MaterialIconProvider) loadData() { log.Debug("Loaded material icon rules and SVG images") } -func (m *MaterialIconProvider) renderFileIconSVG(ctx reqctx.RequestContext, name, svg, extraClass string) template.HTML { - data := ctx.GetData() - renderedSVGs, _ := data["_RenderedSVGs"].(map[string]bool) - if renderedSVGs == nil { - renderedSVGs = make(map[string]bool) - data["_RenderedSVGs"] = renderedSVGs - } +func (m *MaterialIconProvider) renderFileIconSVG(p *RenderedIconPool, name, svg, extraClass string) template.HTML { // This part is a bit hacky, but it works really well. It should be safe to do so because all SVG icons are generated by us. // Will try to refactor this in the future. if !strings.HasPrefix(svg, "
{{end}} - {{if and .Repository.IsFork .Repository.Owner.CanCreateRepo}} + {{if .CanConvertFork}}
{{ctx.Locale.Tr "repo.settings.convert_fork"}}
@@ -916,7 +916,7 @@
{{end}} - {{if and .Repository.IsFork .Repository.Owner.CanCreateRepo}} + {{if .CanConvertFork}}