From 257c34e0039670b89deb248ac8ddbbbc3e823938 Mon Sep 17 00:00:00 2001 From: Hello-Liar <80651389+Hello-Liar@users.noreply.github.com> Date: Sun, 25 Oct 2026 14:41:06 -0700 Subject: [PATCH] fix(repo): use the viewed ref's editorconfig in file view, blame and editor (#39973) The file view, blame and web editor read `.editorconfig` from the default branch, so branch-specific settings like tab width were ignored. The file view's middleware ran before the ref was resolved, and blame never loaded it. - `GetEditorconfig` reads the current commit, falling back to the default branch only where no ref is resolved - `Home` and `RefBlame` load the editorconfig after ref resolution Fixes: https://github.com/go-gitea/gitea/issues/27413 Signed-off-by: silverwind Co-authored-by: silverwind --- routers/api/v1/repo/file.go | 2 +- routers/web/repo/blame.go | 1 + routers/web/repo/view_file.go | 2 +- routers/web/repo/view_home.go | 1 + routers/web/web.go | 4 ++-- services/context/repo.go | 12 ++++-------- tests/e2e/codeeditor.test.ts | 23 +++++++++++++---------- tests/integration/editor_test.go | 7 +++++++ 8 files changed, 30 insertions(+), 22 deletions(-) diff --git a/routers/api/v1/repo/file.go b/routers/api/v1/repo/file.go index 1900ebaef14..6a97b5dc591 100644 --- a/routers/api/v1/repo/file.go +++ b/routers/api/v1/repo/file.go @@ -299,7 +299,7 @@ func GetEditorconfig(ctx *context.APIContext) { // "404": // "$ref": "#/responses/notFound" - ec, _, err := ctx.Repo.GetEditorconfig(ctx, ctx.Repo.Commit) + ec, _, err := ctx.Repo.GetEditorconfig(ctx) if err != nil { ctx.APIErrorAuto(err) return diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go index 06805b7b952..40bb7c7a978 100644 --- a/routers/web/repo/blame.go +++ b/routers/web/repo/blame.go @@ -94,6 +94,7 @@ func RefBlame(ctx *context.Context) { } renderBlame(ctx, result.Parts, commitNames) + SetEditorconfigIfExists(ctx) ctx.HTML(http.StatusOK, tplName) } diff --git a/routers/web/repo/view_file.go b/routers/web/repo/view_file.go index 4e2db924030..d003d11102d 100644 --- a/routers/web/repo/view_file.go +++ b/routers/web/repo/view_file.go @@ -169,7 +169,7 @@ func prepareFileView(ctx *context.Context, entry *git.TreeEntry) { ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/raw/" + ctx.Repo.RefTypeNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) if ctx.Repo.TreePath == ".editorconfig" { - _, editorconfigWarning, editorconfigErr := ctx.Repo.GetEditorconfig(ctx, ctx.Repo.Commit) + _, editorconfigWarning, editorconfigErr := ctx.Repo.GetEditorconfig(ctx) if editorconfigWarning != nil { ctx.Data["FileWarning"] = strings.TrimSpace(editorconfigWarning.Error()) } diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go index 91f208bfb1a..2772b5d348b 100644 --- a/routers/web/repo/view_home.go +++ b/routers/web/repo/view_home.go @@ -477,6 +477,7 @@ func Home(ctx *context.Context) { prepareFuncs := []func(*context.Context){ prepareClonePanel, prepareHomeSidebarRepoTopics, + SetEditorconfigIfExists, prepareToRenderDirOrFile(entry), prepareRecentlyPushedNewBranches, } diff --git a/routers/web/web.go b/routers/web/web.go index a28e35eef81..d04815fd98c 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1298,7 +1298,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) { // end "/{username}/{reponame}/settings" // user/org home, including rss feeds like "/{username}/{reponame}.rss" - m.Get("/{username}/{reponame}", optSignIn, webAuth.AllowBasic, context.RepoAssignment, context.RepoRefByType(git.RefTypeBranch), repo.SetEditorconfigIfExists, repo.Home) + m.Get("/{username}/{reponame}", optSignIn, webAuth.AllowBasic, context.RepoAssignment, context.RepoRefByType(git.RefTypeBranch), repo.Home) m.Post("/{username}/{reponame}/markup", optSignIn, context.RepoAssignment, reqUnitsWithMarkdown, web.Bind[*structs.MarkupOption](), misc.Markup) @@ -1761,7 +1761,7 @@ func registerWebRoutes(m *web.Router, webAuth *AuthMiddleware) { m.Get("/tag/*", context.RepoRefByType(git.RefTypeTag), repo.Home) m.Get("/commit/*", context.RepoRefByType(git.RefTypeCommit), repo.Home) m.Get("/*", context.RepoRefByType(""), repo.Home) // "/*" route is deprecated, and kept for backward compatibility - }, repo.SetEditorconfigIfExists) + }) 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/*" diff --git a/services/context/repo.go b/services/context/repo.go index aac5b0617a3..a7f3982850f 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -307,18 +307,14 @@ func (r *Repository) RefTypeNameSubURL() string { return r.RefFullName.RefWebLinkPath() } -// GetEditorconfig returns the .editorconfig definition if found in the -// HEAD of the default repo branch. -func (r *Repository) GetEditorconfig(ctx context.Context, optCommit ...*git.Commit) (cfg *editorconfig.Editorconfig, warning, err error) { +// GetEditorconfig returns the .editorconfig of the current commit, falling back to the default branch +func (r *Repository) GetEditorconfig(ctx context.Context) (cfg *editorconfig.Editorconfig, warning, err error) { if r.GitRepo == nil { return nil, nil, nil } - var commit *git.Commit - - if len(optCommit) != 0 { - commit = optCommit[0] - } else { + commit := r.Commit + if commit == nil { commit, err = r.GitRepo.GetBranchCommit(ctx, r.Repository.DefaultBranch) if err != nil { return nil, nil, err diff --git a/tests/e2e/codeeditor.test.ts b/tests/e2e/codeeditor.test.ts index 21038cacd02..46109a63989 100644 --- a/tests/e2e/codeeditor.test.ts +++ b/tests/e2e/codeeditor.test.ts @@ -1,16 +1,19 @@ import {env} from 'node:process'; import {expect, test} from '@playwright/test'; -import {login, apiCreateRepo, randomString} from './utils.ts'; +import {login, apiCreateFiles, apiCreateRepo, randomString} from './utils.ts'; test('codeeditor textarea updates correctly', async ({page, request}) => { const repoName = `e2e-codeeditor-${randomString(8)}`; - await Promise.all([apiCreateRepo(request, {name: repoName}), login(page)]); - await page.goto(`/${env.GITEA_TEST_E2E_USER}/${repoName}/_new/main`); - await page.getByPlaceholder('Name your file…').fill('test.js'); - await expect(page.locator('[data-tab="write"] .editor-loading')).toBeHidden(); - const editor = page.locator('.cm-content[role="textbox"]'); - await expect(editor).toBeVisible(); - await editor.click(); - await page.keyboard.type('const hello = "world";'); - await expect(page.locator('textarea[name="content"]')).toHaveValue('const hello = "world";'); + const owner = env.GITEA_TEST_E2E_USER; + await Promise.all([ + (async () => { + await apiCreateRepo(request, {name: repoName}); + await apiCreateFiles(request, owner, repoName, [{path: '.editorconfig', content: '[*]\nindent_size = 2\n'}], {newBranch: 'feature'}); + })(), + login(page), + ]); + await page.goto(`/${owner}/${repoName}/_new/feature`); + await page.locator('.cm-content[role="textbox"]').click(); + await page.keyboard.press('Tab'); + await expect(page.locator('textarea[name="content"]')).toHaveValue(' '); }); diff --git a/tests/integration/editor_test.go b/tests/integration/editor_test.go index e9adc0a86eb..a6c4e4957f7 100644 --- a/tests/integration/editor_test.go +++ b/tests/integration/editor_test.go @@ -39,6 +39,13 @@ func TestEditor(t *testing.T) { testEditFile(t, sessionUser2, "user2", "repo1", "master", "README.md", "Hello, World (direct)\n") testEditFileToNewBranch(t, sessionUser2, "user2", "repo1", "master", "feature/test", "README.md", "Hello, World (commit-to-new-branch)\n") }) + t.Run("EditorconfigOfBranch", func(t *testing.T) { + testCreateFile(t, sessionUser2, "user2", "repo1", "master", "editorconfig", ".editorconfig", "[*]\nindent_size = 2\n") + for _, link := range []string{"/user2/repo1/src/branch/editorconfig/README.md", "/user2/repo1/blame/branch/editorconfig/README.md"} { + resp := sessionUser2.MakeRequest(t, NewRequest(t, "GET", link), http.StatusOK) + AssertHTMLElement(t, NewHTMLParser(t, resp.Body), ".tab-size-2", true) + } + }) t.Run("PatchFile", testEditorPatchFile) t.Run("DeleteFile", func(t *testing.T) { viewLink := "/user2/repo1/src/branch/branch2/README.md"