From c0f4e5583efb89879dbb7cface88d10f312d97a4 Mon Sep 17 00:00:00 2001 From: Shudhanshu Singh Date: Tue, 21 Jul 2026 09:14:25 +0530 Subject: [PATCH] feat(repo): prioritize well-known READMEs and optimize discovery (#38532) This PR adjusts the repository README discovery logic to mirror GitHub's priority order, while retaining support for Gitea's specific `.gitea/` directory. Previously, Gitea would prioritize a `README.md` at the root of the repository over any READMEs in `.gitea/` or `.github/`. With this change, well-known subdirectories are evaluated first when viewing the repository root. **New Priority Order:** 1. `.gitea/README.md` 2. `.github/README.md` 3. `/README.md` (root directory) 4. `docs/README.md` This allows repository maintainers to use `.github/README.md` (or `.gitea/README.md`) as the repository homepage without having to remove or rename generic root `/README.md` files required by package managers (like NPM, Crates.io, Docker, etc.). --------- Signed-off-by: wxiaoguang Co-authored-by: wxiaoguang --- modules/git/fastimport.go | 42 ++++++ modules/test/utils.go | 2 + routers/web/repo/view.go | 22 +-- routers/web/repo/view_home.go | 10 +- routers/web/repo/view_readme.go | 140 +++++++++--------- routers/web/repo/view_readme_test.go | 71 --------- .../user2/readme-test.git/info/refs | 21 --- .../user2/readme-test.git/objects/.keep | 0 .../12/11481f7314efbfe4e44703170d96c8fac8172b | Bin 169 -> 0 bytes .../17/2343566bf11fc71ba4acf8d2ea70d12bc1d037 | Bin 214 -> 0 bytes .../1a/48cae3f18ccd9c929e6608f67087dbaac3cf9e | Bin 167 -> 0 bytes .../1e/1e08102cf1b1fc01c069c88ee75445974363ab | Bin 83 -> 0 bytes .../21/470f9b3e8ff24e0fa083d2dbc447f4c3401355 | 2 - .../23/65bfe0c5714e2e3f2d53bb302b10d8d5b4fc7d | Bin 175 -> 0 bytes .../38/9d08c6a71d024a91f14089007cd789cd977ca6 | Bin 48 -> 0 bytes .../3a/a8f4e0e1a535f0f9e0ae40e6ec1bce42642bc4 | Bin 140 -> 0 bytes .../3b/23d7f1a9cb904cb46f5f2272bfa5ed5f871fb9 | 1 - .../50/6ff7310f420e878595b4bc8f11688e3f0ae14e | Bin 166 -> 0 bytes .../58/3eb775c596858380273492759d39081d65d029 | Bin 169 -> 0 bytes .../60/ea618ae7d4ecbe9c1962591c7da1b05bb1a5c8 | 3 - .../6a/b05db4c52530726c1856eb558228e9d1949e7f | Bin 169 -> 0 bytes .../71/60a063b5544b5a78131b94f47bfd200046eda2 | Bin 167 -> 0 bytes .../75/6c70c97047d8aeb11ca3c71edd9fb76cefee9c | Bin 28 -> 0 bytes .../7f/2b9f991d99362eb827b67f4ae2f5fbc5fa2271 | Bin 211 -> 0 bytes .../7f/792e709143fb0f021da2371e5f40d1bcc284fd | Bin 166 -> 0 bytes .../82/817856dadc7f6b944633e1b77d5b6e302dde06 | Bin 51 -> 0 bytes .../8b/4149e7dede3cd53ba11c64c88b057c5fe2c200 | Bin 169 -> 0 bytes .../93/54813d81053c14afe878a9f056b937ec42bb48 | Bin 28 -> 0 bytes .../9c/72c10e55e7d6ea21f591aa424e2625e8ad8094 | Bin 136 -> 0 bytes .../a3/cd04bb110e17591ac04e156c7df2c2f5c96fa6 | Bin 82 -> 0 bytes .../b0/e851a5619e2d6cee1da25a15ab67305f0861ec | Bin 76 -> 0 bytes .../b4/4c8eb00bdaf0522de61e591fee5f66851ef4b5 | Bin 112 -> 0 bytes .../b8/eaa80ad86072e1f23d2638842154ce9aceff8d | Bin 77 -> 0 bytes .../d5/34f914944c3c943a6bdb677d869ac54934928d | Bin 31 -> 0 bytes .../e2/f9904cd97b4045feecfffef5a426e9461bee70 | Bin 117 -> 0 bytes .../e3/a6fd8fe49e323ee10017f72b777a53fbd8076f | 3 - .../e7/bf02fcfa7a86f7fe9e8158b55d58ddf9d877ec | Bin 171 -> 0 bytes .../ea/57c91ddb8b4ac705b5ac4c34c7a48f2d0fc180 | Bin 77 -> 0 bytes .../fe/495ea336f079ef2bed68648d0ba9a37cdbd4aa | Bin 197 -> 0 bytes .../readme-test.git/objects/info/commit-graph | Bin 2612 -> 0 bytes .../user2/readme-test.git/objects/info/packs | 2 - ...d634b76f8154310cccb52537a0195e43166.bitmap | Bin 1642 -> 0 bytes ...33bd634b76f8154310cccb52537a0195e43166.idx | Bin 4012 -> 0 bytes ...3bd634b76f8154310cccb52537a0195e43166.pack | Bin 7854 -> 0 bytes .../user2/readme-test.git/packed-refs | 22 --- .../user2/readme-test.git/refs/.keep | 0 .../refs/heads/fallbacks-broken-symlinks | 1 - tests/integration/repo_test.go | 137 ++++++++++++++--- 48 files changed, 248 insertions(+), 231 deletions(-) create mode 100644 modules/git/fastimport.go delete mode 100644 routers/web/repo/view_readme_test.go delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/info/refs create mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/.keep delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/12/11481f7314efbfe4e44703170d96c8fac8172b delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/17/2343566bf11fc71ba4acf8d2ea70d12bc1d037 delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/1a/48cae3f18ccd9c929e6608f67087dbaac3cf9e delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/1e/1e08102cf1b1fc01c069c88ee75445974363ab delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/21/470f9b3e8ff24e0fa083d2dbc447f4c3401355 delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/23/65bfe0c5714e2e3f2d53bb302b10d8d5b4fc7d delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/38/9d08c6a71d024a91f14089007cd789cd977ca6 delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/3a/a8f4e0e1a535f0f9e0ae40e6ec1bce42642bc4 delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/3b/23d7f1a9cb904cb46f5f2272bfa5ed5f871fb9 delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/50/6ff7310f420e878595b4bc8f11688e3f0ae14e delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/58/3eb775c596858380273492759d39081d65d029 delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/60/ea618ae7d4ecbe9c1962591c7da1b05bb1a5c8 delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/6a/b05db4c52530726c1856eb558228e9d1949e7f delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/71/60a063b5544b5a78131b94f47bfd200046eda2 delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/75/6c70c97047d8aeb11ca3c71edd9fb76cefee9c delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/7f/2b9f991d99362eb827b67f4ae2f5fbc5fa2271 delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/7f/792e709143fb0f021da2371e5f40d1bcc284fd delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/82/817856dadc7f6b944633e1b77d5b6e302dde06 delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/8b/4149e7dede3cd53ba11c64c88b057c5fe2c200 delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/93/54813d81053c14afe878a9f056b937ec42bb48 delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/9c/72c10e55e7d6ea21f591aa424e2625e8ad8094 delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/a3/cd04bb110e17591ac04e156c7df2c2f5c96fa6 delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/b0/e851a5619e2d6cee1da25a15ab67305f0861ec delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/b4/4c8eb00bdaf0522de61e591fee5f66851ef4b5 delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/b8/eaa80ad86072e1f23d2638842154ce9aceff8d delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/d5/34f914944c3c943a6bdb677d869ac54934928d delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/e2/f9904cd97b4045feecfffef5a426e9461bee70 delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/e3/a6fd8fe49e323ee10017f72b777a53fbd8076f delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/e7/bf02fcfa7a86f7fe9e8158b55d58ddf9d877ec delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/ea/57c91ddb8b4ac705b5ac4c34c7a48f2d0fc180 delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/fe/495ea336f079ef2bed68648d0ba9a37cdbd4aa delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/info/commit-graph delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/info/packs delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/pack/pack-8933bd634b76f8154310cccb52537a0195e43166.bitmap delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/pack/pack-8933bd634b76f8154310cccb52537a0195e43166.idx delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/objects/pack/pack-8933bd634b76f8154310cccb52537a0195e43166.pack delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/packed-refs create mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/refs/.keep delete mode 100644 tests/gitea-repositories-meta/user2/readme-test.git/refs/heads/fallbacks-broken-symlinks diff --git a/modules/git/fastimport.go b/modules/git/fastimport.go new file mode 100644 index 00000000000..74118774701 --- /dev/null +++ b/modules/git/fastimport.go @@ -0,0 +1,42 @@ +// Copyright 2026 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package git + +import ( + "bytes" + "context" + "fmt" + + "gitea.dev/modules/git/gitcmd" +) + +type FastImportFile struct { + Mode EntryMode + Path string + Content string +} + +type FastImportCommit struct { + Ref string + Message string + Files []FastImportFile +} + +// ForceFastImport is for mainly for testing purpose +func ForceFastImport(ctx context.Context, repo RepositoryFacade, commits []FastImportCommit) error { + var buf bytes.Buffer + for i, c := range commits { + _, _ = fmt.Fprintf(&buf, "reset %s\n", c.Ref) + _, _ = fmt.Fprintf(&buf, "commit %s\nmark :%d\ncommitter Gitea 1500000000 +0000\n", c.Ref, i+1) + _, _ = fmt.Fprintf(&buf, "data %d\n%s\n", len(c.Message), c.Message) + for _, f := range c.Files { + _, _ = fmt.Fprintf(&buf, "M %s inline %s\ndata %d\n%s\n", f.Mode.String(), f.Path, len(f.Content), f.Content) + } + } + buf.WriteString("done\n") + _, _, err := gitcmd.NewCommand("fast-import").AddArguments("--force", "--done"). + WithRepo(repo).WithStdinBytes(buf.Bytes()). + RunStdString(ctx) + return err +} diff --git a/modules/test/utils.go b/modules/test/utils.go index 12b35f42f7a..671af5a3401 100644 --- a/modules/test/utils.go +++ b/modules/test/utils.go @@ -8,6 +8,7 @@ import ( "archive/zip" "bytes" "compress/gzip" + "context" "io" "net/http" "net/http/httptest" @@ -156,6 +157,7 @@ var AllowSkipExternalService = sync.OnceValue(func() bool { }) type TestingT interface { + Context() context.Context Helper() Skipf(format string, args ...any) Errorf(format string, args ...any) diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index fecd55db6c7..e3583fd332d 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -268,11 +268,11 @@ func prepareDirectoryFileIcons(ctx *context.Context, files []git.CommitInfo) { ctx.Data["FileIconPoolHTML"] = renderedIconPool.RenderToHTML() } -func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entries { +func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) (*git.TreeEntry, git.Entries) { tree, err := ctx.Repo.Commit.SubTree(ctx, ctx.Repo.GitRepo, ctx.Repo.TreePath) if err != nil { HandleGitError(ctx, "Repo.Commit.SubTree", err) - return nil + return nil, nil } // TODO: LAST-COMMIT-ASYNC-LOADING: search this keyword to see more details @@ -283,20 +283,20 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx, ctx.Repo.GitRepo, ctx.Repo.TreePath) if err != nil { HandleGitError(ctx, "Repo.Commit.GetTreeEntryByPath", err) - return nil + return nil, nil } if !entry.IsDir() { HandleGitError(ctx, "Repo.Commit.GetTreeEntryByPath", err) - return nil + return nil, nil } - allEntries, err := tree.ListEntries(ctx, ctx.Repo.GitRepo) + subEntries, err := tree.ListEntries(ctx, ctx.Repo.GitRepo) if err != nil { ctx.ServerError("ListEntries", err) - return nil + return nil, nil } - allEntries.CustomSort(base.NaturalSortCompare) + subEntries.CustomSort(base.NaturalSortCompare) commitInfoCtx := gocontext.Context(ctx) if timeout > 0 { @@ -305,10 +305,10 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri defer cancel() } - files, latestCommit, err := allEntries.GetCommitsInfo(commitInfoCtx, ctx.Repo.RepoLink, ctx.Repo.GitRepo, ctx.Repo.Commit, ctx.Repo.TreePath) + files, latestCommit, err := subEntries.GetCommitsInfo(commitInfoCtx, ctx.Repo.RepoLink, ctx.Repo.GitRepo, ctx.Repo.Commit, ctx.Repo.TreePath) if err != nil { ctx.ServerError("GetCommitsInfo", err) - return nil + return nil, nil } { // this block is for testing purpose only @@ -340,9 +340,9 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri } if !loadLatestCommitData(ctx, latestCommit) { - return nil + return nil, nil } - return allEntries + return entry, subEntries } // RenderUserCards render a page show users according the input template diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go index b6fd0cd6c3a..b09dbbeaac9 100644 --- a/routers/web/repo/view_home.go +++ b/routers/web/repo/view_home.go @@ -141,7 +141,7 @@ func prepareHomeSidebarLicenses(ctx *context.Context) { } func prepareToRenderDirectory(ctx *context.Context) { - entries := renderDirectoryFiles(ctx, 1*time.Second) + treeEntry, subEntries := renderDirectoryFiles(ctx, 1*time.Second) if ctx.Written() { return } @@ -151,12 +151,11 @@ func prepareToRenderDirectory(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("repo.file.title", ctx.Repo.Repository.Name+"/"+ctx.Repo.TreePath, ctx.Repo.RefFullName.ShortName()) } - subfolder, readmeFile, err := findReadmeFileInEntries(ctx, ctx.Repo.TreePath, entries, true) + subfolder, readmeFile, err := findReadmeFileInRepoTree(ctx, ctx.Repo.TreePath, treeEntry, subEntries) if err != nil { - ctx.ServerError("findReadmeFileInEntries", err) + ctx.ServerError("findReadmeFileInRepo", err) return } - prepareToRenderReadmeFile(ctx, subfolder, readmeFile) } @@ -356,7 +355,8 @@ func redirectFollowSymlink(ctx *context.Context, treePathEntry *git.TreeEntry) b return false } if treePathEntry.IsLink() { - if res, err := git.EntryFollowLinks(ctx, ctx.Repo.GitRepo, ctx.Repo.Commit, ctx.Repo.TreePath, treePathEntry); err == nil { + res, err := git.EntryFollowLinks(ctx, ctx.Repo.GitRepo, ctx.Repo.Commit, ctx.Repo.TreePath, treePathEntry) + if err == nil { redirect := ctx.Repo.RepoLink + "/src/" + ctx.Repo.RefTypeNameSubURL() + "/" + util.PathEscapeSegments(res.TargetFullPath) + "?" + ctx.Req.URL.RawQuery ctx.Redirect(redirect) return true diff --git a/routers/web/repo/view_readme.go b/routers/web/repo/view_readme.go index 908ff95dd1f..69fa95ef9f7 100644 --- a/routers/web/repo/view_readme.go +++ b/routers/web/repo/view_readme.go @@ -24,95 +24,101 @@ import ( ) // locate a README for a tree in one of the supported paths. -// -// entries is passed to reduce calls to ListEntries(), so -// this has precondition: +// entries are passed to reduce calls to ListEntries(), so this has precondition: // // entries == ctx.Repo.Commit.SubTree(ctx.Repo.TreePath).ListEntries() // -// FIXME: There has to be a more efficient way of doing this -func findReadmeFileInEntries(ctx *context.Context, parentDir string, entries []*git.TreeEntry, tryWellKnownDirs bool) (string, *git.TreeEntry, error) { - docsEntries := make([]*git.TreeEntry, 3) // (one of docs/, .gitea/ or .github/) +// this function is tested by integration test ViewRepoDirectoryReadme +func findReadmeFileInRepoTree(ctx *context.Context, treePath string, tree *git.TreeEntry, rootSubEntries []*git.TreeEntry) (subFolder string, _ *git.TreeEntry, err error) { + gitRepo := ctx.Repo.GitRepo + var dirEntries []*git.TreeEntry + if treePath == "" { + // only try the special sub-folders when visiting the repo root + wellKnownSubDirs := findReadmeWellKnownSubDirs(rootSubEntries) + dirEntries = []*git.TreeEntry{wellKnownSubDirs.entryGitea, wellKnownSubDirs.entryGitHub, tree, wellKnownSubDirs.entryDocs} + } else { + dirEntries = []*git.TreeEntry{tree} + } + + for _, dirEntry := range dirEntries { + if dirEntry == nil { + continue + } + + var dirSubEntries []*git.TreeEntry + if dirEntry == tree { + subFolder, dirSubEntries = "", rootSubEntries + } else { + subFolder = dirEntry.Name() + dirSubEntries, err = dirEntry.Tree(ctx, gitRepo).ListEntries(ctx, gitRepo) + if err != nil { + return "", nil, err + } + } + found := findReadmeFileInEntries(ctx, path.Join(treePath, subFolder), dirSubEntries) + if found != nil { + return subFolder, found, nil + } + } + return "", nil, nil +} + +func findReadmeWellKnownSubDirs(entries []*git.TreeEntry) (ret struct{ entryGitea, entryGitHub, entryDocs *git.TreeEntry }) { for _, entry := range entries { - if tryWellKnownDirs && entry.IsDir() { - // as a special case for the top-level repo introduction README, - // fall back to subfolders, looking for e.g. docs/README.md, .gitea/README.zh-CN.txt, .github/README.txt, ... - // (note that docsEntries is ignored unless we are at the root) - lowerName := strings.ToLower(entry.Name()) - switch lowerName { - case "docs": - if entry.Name() == "docs" || docsEntries[0] == nil { - docsEntries[0] = entry - } - case ".gitea": - if entry.Name() == ".gitea" || docsEntries[1] == nil { - docsEntries[1] = entry - } - case ".github": - if entry.Name() == ".github" || docsEntries[2] == nil { - docsEntries[2] = entry - } + if !entry.IsDir() { + continue + } + lowerName := strings.ToLower(entry.Name()) + switch lowerName { + case ".gitea": + if entry.Name() == ".gitea" || ret.entryGitea == nil { + ret.entryGitea = entry + } + case ".github": + if entry.Name() == ".github" || ret.entryGitHub == nil { + ret.entryGitHub = entry + } + case "docs": + if entry.Name() == "docs" || ret.entryDocs == nil { + ret.entryDocs = entry } } } + return ret +} +func findReadmeFileInEntries(ctx *context.Context, parentPath string, entries []*git.TreeEntry) *git.TreeEntry { // Create a list of extensions in priority order - // 1. Markdown files - with and without localisation - e.g. README.en-us.md or README.md + // 1. Markdown files - with and without localization - e.g. README.en-us.md or README.md // 2. Txt files - e.g. README.txt // 3. No extension - e.g. README exts := append(localizedExtensions(".md", ctx.Locale.Language()), ".txt", "") // sorted by priority extCount := len(exts) - readmeFiles := make([]*git.TreeEntry, extCount+1) + readmeFiles := make([]*git.TreeEntry, extCount+1) // ext weight can be len(exts), so here "+1" + for _, entry := range entries { - if i, ok := util.IsReadmeFileExtension(entry.Name(), exts...); ok { - fullPath := path.Join(parentDir, entry.Name()) - if readmeFiles[i] == nil || base.NaturalSortCompare(readmeFiles[i].Name(), entry.Blob(ctx.Repo.GitRepo).Name()) < 0 { - if entry.IsLink() { - res, err := git.EntryFollowLinks(ctx, ctx.Repo.GitRepo, ctx.Repo.Commit, fullPath, entry) - if err == nil && (res.TargetEntry.IsExecutable() || res.TargetEntry.IsRegular()) { - readmeFiles[i] = entry - } - } else { - readmeFiles[i] = entry + extWeight, ok := util.IsReadmeFileExtension(entry.Name(), exts...) + if !ok { + continue + } + fullPath := path.Join(parentPath, entry.Name()) + if readmeFiles[extWeight] == nil || base.NaturalSortCompare(readmeFiles[extWeight].Name(), entry.Blob(ctx.Repo.GitRepo).Name()) < 0 { + if entry.IsLink() { + res, err := git.EntryFollowLinks(ctx, ctx.Repo.GitRepo, ctx.Repo.Commit, fullPath, entry) + if err == nil && (res.TargetEntry.IsExecutable() || res.TargetEntry.IsRegular()) { + readmeFiles[extWeight] = entry } + } else { + readmeFiles[extWeight] = entry } } } - - var readmeFile *git.TreeEntry for _, f := range readmeFiles { if f != nil { - readmeFile = f - break + return f } } - - if ctx.Repo.TreePath == "" && readmeFile == nil { - for _, subTreeEntry := range docsEntries { - if subTreeEntry == nil { - continue - } - subTree := subTreeEntry.Tree(ctx, ctx.Repo.GitRepo) - if subTree == nil { - // this should be impossible; if subTreeEntry exists so should this. - continue - } - childEntries, err := subTree.ListEntries(ctx, ctx.Repo.GitRepo) - if err != nil { - return "", nil, err - } - - subfolder, readmeFile, err := findReadmeFileInEntries(ctx, path.Join(parentDir, subTreeEntry.Name()), childEntries, false) - if err != nil && !git.IsErrNotExist(err) { - return "", nil, err - } - if readmeFile != nil { - return path.Join(subTreeEntry.Name(), subfolder), readmeFile, nil - } - } - } - - return "", readmeFile, nil + return nil } // localizedExtensions prepends the provided language code with and without a diff --git a/routers/web/repo/view_readme_test.go b/routers/web/repo/view_readme_test.go deleted file mode 100644 index 851b7c88ca1..00000000000 --- a/routers/web/repo/view_readme_test.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2026 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package repo - -import ( - "fmt" - "path" - "testing" - - "gitea.dev/modules/git" - "gitea.dev/modules/git/gitcmd" - "gitea.dev/services/contexttest" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestFindReadmeFileInEntriesWithSymlinkInSubfolder(t *testing.T) { - for _, subdir := range []string{".github", ".gitea", "docs"} { - t.Run(subdir, func(t *testing.T) { - repoPath := t.TempDir() - stdin := fmt.Sprintf(`commit refs/heads/master -author Test 1700000000 +0000 -committer Test 1700000000 +0000 -data <nhm3wW6!5zeCE6x^N&aRSP}_?}*x z$8o5jv^vz42%^@>Q$&n8FdC&YSxKgoOk~(vZzQpfMl4~ase)tIDRS6J-Eu@mC38@% z+YM{XGNz=w<}AWi`+0@ike+becG}Ylr)kgT@yp9Hm-kpplfoIKb07%!ZqC`y+u{{YMSK(^sz);i_ygQEL^6jO{e zZ)T|OdloQidXa+}u3^&C%r0_dLlI5zT9cjVdJPI4;ld-0N)2~^a1pQ=J^Bfq!Z3~o z?QE2G+S%wGd&01gUFN6;e@dzcd}mtW>+ERa1Kvxwm)p~R-s|?W%XT6E0opo;IHEQ1 zrmzpv-&5(-e><$1pB+PcWT6C7fy-^nEkR)NXJg0^Yk3t|OMO7<>Y3fI4E-nwH)(L3 Q1#$sOxp3p*7x&_1XWgo56#xJL diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/1a/48cae3f18ccd9c929e6608f67087dbaac3cf9e b/tests/gitea-repositories-meta/user2/readme-test.git/objects/1a/48cae3f18ccd9c929e6608f67087dbaac3cf9e deleted file mode 100644 index d8522ae6d3319a48188c99be269b1622f89e7030..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 167 zcmV;Y09gNc0iBLJ4#GeT1*!KGz5vMHFGvW{(NS>$uj3^V`6(;~wQSQ#Wl?XVw^_WIdC{=~CF{HkG3G*4BvEG1Qud8i zPT0`8r3PIx{HpD=z_pVHTwbf%>H^1c#ohgf=XvViL+y9MC!m;82sTlGEe1p*-=}r8 V{%@F@J}VUN`^@rqnhyv1PZ>ILQ$7Fy diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/1e/1e08102cf1b1fc01c069c88ee75445974363ab b/tests/gitea-repositories-meta/user2/readme-test.git/objects/1e/1e08102cf1b1fc01c069c88ee75445974363ab deleted file mode 100644 index d4152e535edee4dede53f2b78c5890f6010635bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 83 zcmV-Z0IdIb0V^p=O;s>AWH2-^Ff%bx2y%6F@pWa`@t!|y#UeLtnFArh>nhm3v8SkFzih!6D0#a;)ZwSdIk(g4FWSNYUKu$Lz9@WCDOc?~pa~)axw&jwzZiIq1 zuJ+oU1dS;ror2~UDQriI8Ozu=MS&TJnUm0s>m16&U;JVSO-V6h$?=c d=3@Q7p&qhjh#mKnw6N{t&`+;TeE>rzQOAB&RpI~u diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/38/9d08c6a71d024a91f14089007cd789cd977ca6 b/tests/gitea-repositories-meta/user2/readme-test.git/objects/38/9d08c6a71d024a91f14089007cd789cd977ca6 deleted file mode 100644 index c5929ae89288aa30a1a891326bc8bb9f340640f8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48 zcmV-00MGw;0V^p=O;s>9W-u`T0tLPF%#zeZ2HRsE+r`do2(@WGwr9=%yg1I;B`E+G GZx5ikFcZ80 diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/3a/a8f4e0e1a535f0f9e0ae40e6ec1bce42642bc4 b/tests/gitea-repositories-meta/user2/readme-test.git/objects/3a/a8f4e0e1a535f0f9e0ae40e6ec1bce42642bc4 deleted file mode 100644 index 106393d722517fe5c97ef75e78200b79654ea7ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 140 zcmV;70CWF%0V^p=O;s>7Hf1m|00ITQ^vsgfLg 27Q. (c"onM<[6_^橼ZTU n qO*3{N\ \ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/50/6ff7310f420e878595b4bc8f11688e3f0ae14e b/tests/gitea-repositories-meta/user2/readme-test.git/objects/50/6ff7310f420e878595b4bc8f11688e3f0ae14e deleted file mode 100644 index a7f4501ea2e8b7d2c984dcf2f926497f5b9597b3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 166 zcmV;X09pTd0iBLH4#F@H0IB_o{Q%M0@p{)1LUeRge84!%A#y1=1;58aynu3w8BLys z;ZeYOw<(J%By>uA70(!0I5Gw7qT1ldv)C*w>PWMh)1qSml2$Lwlqm+OAn3UD(J{rY zjVQJY)S-(et>r!~aC+n?9M`Jeb%EVjvAO;5Fi-t^D1DRD9&D&pvcw*?NQfqXPfNA_ UZb3S3j)Us8UW4?((1W7)${?*IS* diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/58/3eb775c596858380273492759d39081d65d029 b/tests/gitea-repositories-meta/user2/readme-test.git/objects/58/3eb775c596858380273492759d39081d65d029 deleted file mode 100644 index a2dadacf8ac5c581336a8fec56846cc07f12f8ac..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 169 zcmV;a09OBa0iBL9Zo@DP06Xgy{6Gv+kwgh7if-95^#c^eG(qe*aOEMtk5S|W8E?45 z={!xB4nFvgzL>#2mxfuBFg5f+3Y{omE{Lp+bJ13qRg$~QVz&+nO%i1>Zo#x#Q7XyD zLds33Vyu-UYId^r^Sr?EqE|Sqn|ihdK5rWizc2j0&*Nk2!քLuUl#qlQ,ꔡlCBn$6XDɹbbҖR0y[/On -i21Ї@epdiޭ殯 !<N \ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/6a/b05db4c52530726c1856eb558228e9d1949e7f b/tests/gitea-repositories-meta/user2/readme-test.git/objects/6a/b05db4c52530726c1856eb558228e9d1949e7f deleted file mode 100644 index 3e4c8247d906f07f3985a58a76b08000a1d661f2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 169 zcmV;a09OBa0V^p=O;s>7H)Sv}00ITQ^vsgfLxl>Fpkh6NdXlWfkP XQ?xj?zbJXp^RpMsW-0*yojgaTO}bCn diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/71/60a063b5544b5a78131b94f47bfd200046eda2 b/tests/gitea-repositories-meta/user2/readme-test.git/objects/71/60a063b5544b5a78131b94f47bfd200046eda2 deleted file mode 100644 index 477d5b1f1f4312265b57e9f6403dc133ddd83c81..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 167 zcmV;Y09gNc0iBLJ4#F@H1*v_Cy#UcVvDZdIh>nhm3)tov5Y9)zDY!im;slga%xKcs z_m4`gX*YES$XOR9!)OuX!4Zru$`1C59MBmbSfhP8}43Q9A$t diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/75/6c70c97047d8aeb11ca3c71edd9fb76cefee9c b/tests/gitea-repositories-meta/user2/readme-test.git/objects/75/6c70c97047d8aeb11ca3c71edd9fb76cefee9c deleted file mode 100644 index 3ef17969867f6a1e4c2138ae8d469601e3c1fb5f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28 kcmbDbEm{|z*nLb?q@?IA8^a9ULSw;^I5j2 zc&p%o+tfuBJoByuMYT+x3YJ!pHK0Znn?-|*!DR{N!HZ56B6Un0C?RF%JjK?dO*X}j zld@1Q5=+~m;aBbF16`jPaYEJa*lGXHNtRod7H)Sv}00ITQ^vsgfLqpAZcI(ND9+HwZ@WUXx$Eao>!0_~^7Hf1m|00ITQ^vsgfL$*TcJIW-;CK0dJk diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/a3/cd04bb110e17591ac04e156c7df2c2f5c96fa6 b/tests/gitea-repositories-meta/user2/readme-test.git/objects/a3/cd04bb110e17591ac04e156c7df2c2f5c96fa6 deleted file mode 100644 index 6d9bc2a7664fe2968b95869b01587b4eaa61a4fe..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 82 zcmV-Y0ImOc0V^p=O;s?nWH2-`00M;|S4S6LSH0X6h6P%IvU?Y)CvG|R|7KUck;K%O or+%1#)#R6Cq!uv*co<)>wEEN6zbbUcs*pz-KP}|_02Heo?n7)OQvd(} diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/b0/e851a5619e2d6cee1da25a15ab67305f0861ec b/tests/gitea-repositories-meta/user2/readme-test.git/objects/b0/e851a5619e2d6cee1da25a15ab67305f0861ec deleted file mode 100644 index 2f88dbd361ae06a6ca6202ea1441b965b36a145a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 76 zcmV-S0JHyi0V^p=O;s>6VK6ZO0tLPF%#zeZ2HRsE+r`do2(@WGwr9=%yg1I;B`FXE iDf!9848easzi??;<(R6yt3=9gw&*v>c|QTPTpYU4?IQ>P diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/b4/4c8eb00bdaf0522de61e591fee5f66851ef4b5 b/tests/gitea-repositories-meta/user2/readme-test.git/objects/b4/4c8eb00bdaf0522de61e591fee5f66851ef4b5 deleted file mode 100644 index 55bb8491afef625eeeb1af14dd0b3cd87974c3f3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 112 zcmV-$0FVE80V^p=O;xb8U@$QN0tLPF%#zeZ2HRsE+r`do2(@WGwr9=%yg1I;B`Jmm z24*HE3PG-pF21e|m#6VK6ZO0tLPF%#zeZ2HRsE+r`do2(@WGwr9=%yg1I;B`FXE jDf!983=1;&CfS@nr)Y6(e^K(H=VvdN%~S#at56)Q)g~dT diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/d5/34f914944c3c943a6bdb677d869ac54934928d b/tests/gitea-repositories-meta/user2/readme-test.git/objects/d5/34f914944c3c943a6bdb677d869ac54934928d deleted file mode 100644 index 7bb434442f72d254b112e2740ebf900a8bcd1e73..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31 ncmb7Fk>(@G5`XFAXi5hUst`{6b8%sw0Rmmf%9Jnzj)s0?^SW} zGS5X5u$toh+*F3eXIXX&@`*=E9q<#)sr_{5>&g6OP%}!35_1dkb29T%8JyT;7#ALA Xn11m|__kh3)c diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/e3/a6fd8fe49e323ee10017f72b777a53fbd8076f b/tests/gitea-repositories-meta/user2/readme-test.git/objects/e3/a6fd8fe49e323ee10017f72b777a53fbd8076f deleted file mode 100644 index 2307ba82dca..00000000000 --- a/tests/gitea-repositories-meta/user2/readme-test.git/objects/e3/a6fd8fe49e323ee10017f72b777a53fbd8076f +++ /dev/null @@ -1,3 +0,0 @@ -xM -1 a=E.m Νwh#"A&L \ No newline at end of file diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/e7/bf02fcfa7a86f7fe9e8158b55d58ddf9d877ec b/tests/gitea-repositories-meta/user2/readme-test.git/objects/e7/bf02fcfa7a86f7fe9e8158b55d58ddf9d877ec deleted file mode 100644 index 9f898ef87387dc17cf15c794fd48d43921ddccbc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 171 zcmV;c095~Y0hNw94#F@H0IB_o{Q!~e_>>T$qod*j>&==(ITf6O-xHyHfO3i%4b9`& z70^bX%E}DM7z&)EObupfwQkrcwozqfo1F_*aW=_{1y`O5C^UhP>#PD%6DjJ*&dHFS zm%%p)k!s431#YFCS9o;P!+qP;mRGn;J65j)Z_7M<#xhidS5VG-YgKImoTZgg(C@VD Z)}IaSwLco-$?pOghan;L;tL^5Pf1exQLg|1 diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/ea/57c91ddb8b4ac705b5ac4c34c7a48f2d0fc180 b/tests/gitea-repositories-meta/user2/readme-test.git/objects/ea/57c91ddb8b4ac705b5ac4c34c7a48f2d0fc180 deleted file mode 100644 index d1eff52868d457865f396c829e7060972f4f3ac8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77 zcmV-T0J8sh0V^p=O;s>6VK6ZO0tLPF%#zeZ2HRsE+r`do2(@WGwr9=%yg1I;B`FXE jDf!9841Xgw?YVe?)2-mtT+c<4vwUKHHtqxfy8Rtw@<$_V diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/fe/495ea336f079ef2bed68648d0ba9a37cdbd4aa b/tests/gitea-repositories-meta/user2/readme-test.git/objects/fe/495ea336f079ef2bed68648d0ba9a37cdbd4aa deleted file mode 100644 index 48c0b5a4d621e0a9c34ca50ef4b53faef90a273a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 197 zcmV;$06PD80iBLJPQ@?~0I7Y7y#P>Y-s!xWb5UIux&=!md zok5Mp23L*FH7h!0+BE5Aa}QWMT04ba7*V02d(J2f}1=advSGfw0*u+~I7_Jy5kkic0~(CW*xaR80zI z2CAnOW&xUw53>U0kue*PkB-@aV(6FyC`JtC1gfJp<^q~YF6IX6r3CW;^`m26pcp>P z2b3ok^8?jV6AJ*%BNqz-^$Ov@!a#9CSOlmB9~K456T)IZHMp=iP!5JAfNV;!B+v{L z%8zK1&YU-HHxe zmCLN&ILp~k?&R0C20IVD^%k(aeRID3R`VS`OW7ZU%(ZB`W^TpNw5)ix&CN}|ipLC+ zgiLHaRnx?|S?i|hFolY)w%C2$#i88MV|Mk^Ln^-7)j__e4NraED?jIq+t;hBjjuDN z=y4nu{a%$(Dvd$k)dGofu z{~W`)IV{5L#E*xuJTE7jJp5R3%ID0-h0{gU+&*1Ts_VHrhreAZY=`Bcopp!wFMHgm z7GVh$-tdyiGjE-9)CHSKORrS^6nn4hF?oO7jTx$Xs{-yE5af94B7XSQj73upm^z1B zZRj{B^PQ8+qE`EvqEnK|vD|3(Lu|H283px9N;w7H5mQUvpLfWb_C)OZy%-Ix>+JAjT$p3G0t~;AduCJ$FT zc6MFf#oSvGrkZawY&Yya`tQ}eALSQsbbU{2x>4C8!6B6Lk>}5E=X1$zK@pyxS4EkZ zZ1^<&4@-&iF^6LpBYmgao?0X(%6VfV^TV$@Qb6gf07w9VK=R+5r3>`Jf4H+%RF)>( ze3%qcA=BQc`UWT`3{|6%{5LnLeuja=E0Kx!N*@H3>B=0svPQS{B2bMOR1Ht^-{_SO z|2}>0xO{VcN8^Xi``aG4$mCzP1FDfg*cGEYL*S{!*Mrg5-p&`wUjD!Ri}d8@u0STgV~gZxzL{V>wLbF3s>=s+ zPiUj55n*(gcSdJv+={1fcv-49yg4VW)G{Bah6&-GMAg#j{3l{5ecTToZ<`nUew|4d z<0DX=$p}@W1N3_#18?qy_WAQJcA4o%&GHpn zyIWbSr!{U{z;+GSyUj^JH9}A|62SP{^X#|TiToP@*~J^H4Ug1MdH&7eQ9i1BykEvl zZ;+RCHd*Y@wc3A$>xwU%ZIAv%RTF4ZzQj|VbIOFK^Nh>xR7^7ca_-9fM?f_^P`hM+ z?okO!7VD0Aw%4H)$pS$WO&#KDIzrPl!1}Tjt_=wDK`ufVL zbfQCZz^$WUu2v7OZYu?A9sQ~94%E7h)ZW#Y8h0jBw!$ramKP6<~!DQfQdH_4Y{>oIPwOMifBL=kGj eWq^E|gV$BAMVrhm)^9AH!JzuP|G}Vl9XW+O1w%QfsjW(UKwxq6eWDbLb%*Otnc% zQR~KHPdOB!rFT(K*IPk7h#m?emH2T`M7^~ljqm?9yGgdwfqftU`R$vxGqWc;&YmJg z5hB`|Z+Uh(kzQ;#8GZfgOwWKSd}+P{ou4(8haHhBm&+0mU1J~OCycx|fBi!m0jcEU zU%hW6B)ZDpXQtBQ@tiv`(;l5VRAJy$O^h%mxn}FXO@mlE!m9(ul#F#^HQPFJi6YAZ z@|>BRrpUhldDuuUGr1Tb+l}NmCVvOWBPMeB5tE++WRHpb3(^UYYfa=+kUBtKGLRV| zd7^GV_Zi56o5g84<~nlsO0rdRwJ6S*$rvZ@I4@4K_0P!RM1`WTq?{Mto!ZKRd$>Lc zK~YRBk;7gFUJa0%1r@SjNiFDB4lp)~)olHmxHKvXsjg4lcwxWUsJQ7 z@UMW>dKV%d-C{LcpOn>QUQwocle<|k?zc@QOZR2Y!kYOd^oxFS4Mh**I;r_W=R*eq=2NAYGG|-jj0Ssv;q{a%WiEK2 uw}0273(tKwB>v_j;V$YM?vyFwlKmG$ksnc)( diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/pack/pack-8933bd634b76f8154310cccb52537a0195e43166.idx b/tests/gitea-repositories-meta/user2/readme-test.git/objects/pack/pack-8933bd634b76f8154310cccb52537a0195e43166.idx deleted file mode 100644 index 561e0f2e9d241a658eca432443f071acb88482ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4012 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 diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/packed-refs b/tests/gitea-repositories-meta/user2/readme-test.git/packed-refs deleted file mode 100644 index 2399a8008dc..00000000000 --- a/tests/gitea-repositories-meta/user2/readme-test.git/packed-refs +++ /dev/null @@ -1,22 +0,0 @@ -# pack-refs with: peeled fully-peeled sorted -ea9ef877d1d88af76682d8798418081264f10cfc refs/heads/fallbacks -0d4c14db927c9ffba01fa7e126cc748b5c02c01e refs/heads/fallbacks2 -c66d5b07c2063d3268707f22226c708b589574ef refs/heads/fallbacks3 -89f8426e9eb5eff35c09b3565836c8f8e15d0ce9 refs/heads/fallbacks4 -b0e902496eae435ad03c92a5d479f916ef2d4893 refs/heads/fallbacks5 -84a5500b5cc040b11daf53fc42c542a99589dc76 refs/heads/fallbacks6 -cf406a96e416d7de5c4c1bbfffdd672300c822bf refs/heads/fallbacks7 -0d6ac644b969e9199915a492da9dba08c179fd23 refs/heads/fallbacks8 -5038febc0c57215beb3748d7ae4091a25a4acc93 refs/heads/fallbacks9 -9134e1f178ca4cccf1a197142646f2d7627e8cd5 refs/heads/i18n -744d2441e55bc0010d6b340d303f0106a627ad29 refs/heads/master -3c492566170b057e962c025515ab38bbd7444077 refs/heads/plain -3882d6373a0882a6739b3cd9b24d21c630621234 refs/heads/sp-ace -bf5ed898252eaa50dcc01108ed4417c3ea98a294 refs/heads/special-subdir-.gitea -c03543573ab088ce1cf7090a387d2be621426234 refs/heads/special-subdir-.github -e75957ad9b7e6ed16dda183529ec283db0bbc5fe refs/heads/special-subdir-docs -46f5d5ab33d701642e08c713fab42af89fdd4fea refs/heads/special-subdir-nested -9c0f872256b839c2b97ec22fd348d87b14045513 refs/heads/subdir -d7a854fff61e45b98234d7aa79ecbcb1619cd3dd refs/heads/symlink -30b9c0ed4b1039dbd99f3fb537b84ca507e0549d refs/heads/symlink-loop -41489b7be5c2244d2b7b524dcb31caf3bd1f9ccc refs/heads/txt diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/refs/.keep b/tests/gitea-repositories-meta/user2/readme-test.git/refs/.keep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/refs/heads/fallbacks-broken-symlinks b/tests/gitea-repositories-meta/user2/readme-test.git/refs/heads/fallbacks-broken-symlinks deleted file mode 100644 index cf36865a712..00000000000 --- a/tests/gitea-repositories-meta/user2/readme-test.git/refs/heads/fallbacks-broken-symlinks +++ /dev/null @@ -1 +0,0 @@ -fe495ea336f079ef2bed68648d0ba9a37cdbd4aa diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go index 462f790585a..d1b1f017e9b 100644 --- a/tests/integration/repo_test.go +++ b/tests/integration/repo_test.go @@ -1,6 +1,7 @@ // Copyright 2017 The Gitea Authors. All rights reserved. // SPDX-License-Identifier: MIT +//nolint:govet // disable "composites: gitea.dev/modules/git.FastImportFile struct literal uses unkeyed fields" package integration import ( @@ -19,6 +20,7 @@ import ( "gitea.dev/models/unit" "gitea.dev/models/unittest" user_model "gitea.dev/models/user" + "gitea.dev/modules/git" "gitea.dev/modules/setting" "gitea.dev/modules/test" "gitea.dev/modules/util" @@ -308,10 +310,92 @@ func testViewRepoDirectory(t *testing.T) { func testViewRepoDirectoryReadme(t *testing.T) { defer tests.PrintCurrentTest(t)() + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"}) + repo56 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user2.ID, Name: "readme-test"}) + + const regular, symlink = git.EntryModeBlob, git.EntryModeSymlink + + allGitea := []git.FastImportFile{ + {regular, ".gitea/README.en.md", "This is .gitea/README.en.md"}, + {regular, ".gitea/README.md", "This is .gitea/README.md"}, + {regular, ".gitea/README", "This is .gitea/README"}, + } + allGithub := []git.FastImportFile{ + {regular, ".github/README.en.md", "This is .github/README.en.md"}, + {regular, ".github/README.md", "This is .github/README.md"}, + {regular, ".github/README", "This is .github/README"}, + } + allRoot := []git.FastImportFile{ + {regular, "README.en.md", "This is README.en.md"}, + {regular, "README.md", "This is README.md"}, + {regular, "README", "This is README"}, + } + allDocs := []git.FastImportFile{ + {regular, "docs/README.en.md", "This is docs/README.en.md"}, + {regular, "docs/README.md", "This is docs/README.md"}, + {regular, "docs/README", "This is docs/README"}, + } + + combineFiles := func(slices ...[]git.FastImportFile) (res []git.FastImportFile) { + for _, s := range slices { + res = append(res, s...) + } + return res + } + + err := git.ForceFastImport(t.Context(), repo56.CodeStorageRepo(), []git.FastImportCommit{ + {Ref: "refs/heads/master", Message: "init master", Files: []git.FastImportFile{{regular, "README.md", "The cake is a lie."}}}, + {Ref: "refs/heads/txt", Message: "init txt", Files: []git.FastImportFile{{regular, "README.txt", "My spoon is too big."}}}, + {Ref: "refs/heads/plain", Message: "init plain", Files: []git.FastImportFile{{regular, "README", "Birken my stocks gee howdy"}}}, + {Ref: "refs/heads/i18n", Message: "init i18n", Files: []git.FastImportFile{{regular, "README.zh.md", "你好世界"}}}, + {Ref: "refs/heads/subdir", Message: "init subdir", Files: []git.FastImportFile{{regular, "libcake/README.md", "Four pints of sugar."}}}, + {Ref: "refs/heads/special-subdir-docs", Message: "init special-subdir-docs", Files: []git.FastImportFile{{regular, "docs/README.md", "This is in docs/"}}}, + {Ref: "refs/heads/special-subdir-.gitea", Message: "init special-subdir-.gitea", Files: []git.FastImportFile{{regular, ".gitea/README.md", "This is in .gitea/"}}}, + {Ref: "refs/heads/special-subdir-.github", Message: "init special-subdir-.github", Files: []git.FastImportFile{{regular, ".github/README.md", "This is in .github/"}}}, + {Ref: "refs/heads/special-subdir-nested", Message: "init special-subdir-nested", Files: []git.FastImportFile{ + {regular, ".gitea/docs/README.md", "This is in docs/"}, + {regular, "subproject/.github/README.md", "This is in .github/"}, + }}, + {Ref: "refs/heads/symlink", Message: "init symlink", Files: []git.FastImportFile{ + {symlink, ".github/README.md", "../some/other/path/awefulcake.txt"}, + {symlink, "some/README.txt", "other/path/awefulcake.txt"}, + {regular, "some/other/path/awefulcake.txt", "This is in some/other/path"}, + {symlink, "trampoline", "up/back/down/down"}, + {symlink, "up/back/down/down/README.md", "../../../../up/down/left/reelmein"}, + {regular, "up/down/left/reelmein", "It's a me, mario"}, + }}, + {Ref: "refs/heads/symlink-loop", Message: "init symlink-loop", Files: []git.FastImportFile{ + {symlink, "README.md", "trampoline"}, + {symlink, "some/README.txt", "other/path/awefulcake.txt"}, + {symlink, "some/other/path/awefulcake.txt", "../../../README.md"}, + {symlink, "trampoline", "README.md"}, + }}, + {Ref: "refs/heads/sp-ace", Message: "init sp-ace", Files: []git.FastImportFile{{regular, "read me", "The cake is a lie."}}}, + {Ref: "refs/heads/fallbacks", Message: "init fallbacks", Files: combineFiles(allGitea, allGithub, allRoot, allDocs)}, + {Ref: "refs/heads/fallbacks2", Message: "init fallbacks2", Files: combineFiles(allGitea[1:], allGithub, allRoot, allDocs)}, + {Ref: "refs/heads/fallbacks3", Message: "init fallbacks3", Files: combineFiles(allGitea[2:], allGithub, allRoot, allDocs)}, + {Ref: "refs/heads/fallbacks4", Message: "init fallbacks4", Files: combineFiles(allGithub, allRoot, allDocs)}, + {Ref: "refs/heads/fallbacks5", Message: "init fallbacks5", Files: combineFiles(allGithub[1:], allRoot, allDocs)}, + {Ref: "refs/heads/fallbacks6", Message: "init fallbacks6", Files: combineFiles(allGithub[2:], allRoot, allDocs)}, + {Ref: "refs/heads/fallbacks7", Message: "init fallbacks7", Files: combineFiles(allRoot, allDocs)}, + {Ref: "refs/heads/fallbacks8", Message: "init fallbacks8", Files: combineFiles(allRoot[1:], allDocs)}, + {Ref: "refs/heads/fallbacks9", Message: "init fallbacks9", Files: combineFiles(allRoot[2:], allDocs)}, + {Ref: "refs/heads/fallbacks10", Message: "init fallbacks10", Files: combineFiles(allDocs)}, + {Ref: "refs/heads/fallbacks11", Message: "init fallbacks11", Files: combineFiles(allDocs[1:])}, + {Ref: "refs/heads/fallbacks12", Message: "init fallbacks12", Files: combineFiles(allDocs[2:])}, + {Ref: "refs/heads/fallbacks-broken-symlinks", Message: "init fallbacks-broken-symlinks", Files: []git.FastImportFile{ + {symlink, ".gitea/README.md", "non-existent-file"}, + {symlink, ".github/README.md", "non-existent-file"}, + {symlink, "README.md", "non-existent-file"}, + {regular, "docs/README", "This is docs/README"}, + }}, + }) + require.NoError(t, err) + // there are many combinations: // - READMEs can be .md, .txt, or have no extension // - READMEs can be tagged with a language and even a country code - // - READMEs can be stored in docs/, .gitea/, or .github/ + // - READMEs can be stored in "docs/", ".gitea/", or ".github/" // - READMEs can be symlinks to other files // - READMEs can be broken symlinks which should not render // @@ -344,7 +428,7 @@ func testViewRepoDirectoryReadme(t *testing.T) { check("md", "/user2/readme-test/src/branch/master/", "README.md", "markdown", "The cake is a lie.") check("txt", "/user2/readme-test/src/branch/txt/", "README.txt", "plain-text", "My spoon is too big.") check("plain", "/user2/readme-test/src/branch/plain/", "README", "plain-text", "Birken my stocks gee howdy") - check("i18n", "/user2/readme-test/src/branch/i18n/", "README.zh.md", "markdown", "蛋糕是一个谎言") + check("i18n", "/user2/readme-test/src/branch/i18n/", "README.zh.md", "markdown", "你好世界") // using HEAD ref check("branch-HEAD", "/user2/readme-test/src/branch/HEAD/", "README.md", "markdown", "The cake is a lie.") @@ -357,33 +441,32 @@ func testViewRepoDirectoryReadme(t *testing.T) { check(".gitea", "/user2/readme-test/src/branch/special-subdir-.gitea/", ".gitea/README.md", "markdown", "This is in .gitea/") check(".github", "/user2/readme-test/src/branch/special-subdir-.github/", ".github/README.md", "markdown", "This is in .github/") - // symlinks // symlinks are subtle: // - they should be able to handle going a reasonable number of times up and down in the tree // - they shouldn't get stuck on link cycles // - they should determine the filetype based on the name of the link, not the target - check("symlink", "/user2/readme-test/src/branch/symlink/", "README.md", "markdown", "This is in some/other/path") + check("symlink", "/user2/readme-test/src/branch/symlink/", ".github/README.md", "markdown", "This is in some/other/path") check("symlink-multiple", "/user2/readme-test/src/branch/symlink/some/", "README.txt", "plain-text", "This is in some/other/path") check("symlink-up-and-down", "/user2/readme-test/src/branch/symlink/up/back/down/down", "README.md", "markdown", "It's a me, mario") // testing fallback rules // READMEs are searched in this order: - // - [README.zh-cn.md, README.zh_cn.md, README.zh.md, README_zh.md, README.md, README.txt, README, - // docs/README.zh-cn.md, docs/README.zh_cn.md, docs/README.zh.md, docs/README_zh.md, docs/README.md, docs/README.txt, docs/README, - // .gitea/README.zh-cn.md, .gitea/README.zh_cn.md, .gitea/README.zh.md, .gitea/README_zh.md, .gitea/README.md, .gitea/README.txt, .gitea/README, - - // .github/README.zh-cn.md, .github/README.zh_cn.md, .github/README.zh.md, .github/README_zh.md, .github/README.md, .github/README.txt, .github/README] + // - directories: .gitea -> .github -> (root) -> docs + // - extensions: longer to shorter, non-English to English, e.g.: ".zh-cn.md" -> ".zh.md" -> "_zh.md" -> "en.md" -> ".md" -> (no-ext) // and a broken/looped symlink counts as not existing at all and should be skipped. // again, this doesn't cover all cases, but it covers a few - check("fallback/top", "/user2/readme-test/src/branch/fallbacks/", "README.en.md", "markdown", "This is README.en.md") - check("fallback/2", "/user2/readme-test/src/branch/fallbacks2/", "README.md", "markdown", "This is README.md") - check("fallback/3", "/user2/readme-test/src/branch/fallbacks3/", "README", "plain-text", "This is README") - check("fallback/4", "/user2/readme-test/src/branch/fallbacks4/", "docs/README.en.md", "markdown", "This is docs/README.en.md") - check("fallback/5", "/user2/readme-test/src/branch/fallbacks5/", "docs/README.md", "markdown", "This is docs/README.md") - check("fallback/6", "/user2/readme-test/src/branch/fallbacks6/", "docs/README", "plain-text", "This is docs/README") - check("fallback/7", "/user2/readme-test/src/branch/fallbacks7/", ".gitea/README.en.md", "markdown", "This is .gitea/README.en.md") - check("fallback/8", "/user2/readme-test/src/branch/fallbacks8/", ".gitea/README.md", "markdown", "This is .gitea/README.md") - check("fallback/9", "/user2/readme-test/src/branch/fallbacks9/", ".gitea/README", "plain-text", "This is .gitea/README") + check("fallback/top", "/user2/readme-test/src/branch/fallbacks/", ".gitea/README.en.md", "markdown", "This is .gitea/README.en.md") + check("fallback/2", "/user2/readme-test/src/branch/fallbacks2/", ".gitea/README.md", "markdown", "This is .gitea/README.md") + check("fallback/3", "/user2/readme-test/src/branch/fallbacks3/", ".gitea/README", "plain-text", "This is .gitea/README") + check("fallback/4", "/user2/readme-test/src/branch/fallbacks4/", ".github/README.en.md", "markdown", "This is .github/README.en.md") + check("fallback/5", "/user2/readme-test/src/branch/fallbacks5/", ".github/README.md", "markdown", "This is .github/README.md") + check("fallback/6", "/user2/readme-test/src/branch/fallbacks6/", ".github/README", "plain-text", "This is .github/README") + check("fallback/7", "/user2/readme-test/src/branch/fallbacks7/", "README.en.md", "markdown", "This is README.en.md") + check("fallback/8", "/user2/readme-test/src/branch/fallbacks8/", "README.md", "markdown", "This is README.md") + check("fallback/9", "/user2/readme-test/src/branch/fallbacks9/", "README", "plain-text", "This is README") + check("fallback/10", "/user2/readme-test/src/branch/fallbacks10/", "docs/README.en.md", "markdown", "This is docs/README.en.md") + check("fallback/11", "/user2/readme-test/src/branch/fallbacks11/", "docs/README.md", "markdown", "This is docs/README.md") + check("fallback/12", "/user2/readme-test/src/branch/fallbacks12/", "docs/README", "plain-text", "This is docs/README") // this case tests that broken symlinks count as missing files, instead of rendering their contents check("fallbacks-broken-symlinks", "/user2/readme-test/src/branch/fallbacks-broken-symlinks/", "docs/README", "plain-text", "This is docs/README") @@ -407,24 +490,34 @@ func testViewRepoDirectoryReadme(t *testing.T) { }) } missing("sp-ace", "/user2/readme-test/src/branch/sp-ace/") - missing("nested-special", "/user2/readme-test/src/branch/special-subdir-nested/subproject") // the special subdirs should only trigger on the repo root + missing("nested-special", "/user2/readme-test/src/branch/special-subdir-nested/subproject") // the special sub-dirs should only trigger on the repo root missing("special-subdir-nested", "/user2/readme-test/src/branch/special-subdir-nested/") missing("symlink-loop", "/user2/readme-test/src/branch/symlink-loop/") } func testViewRepoSymlink(t *testing.T) { session := loginUser(t, "user2") - req := NewRequest(t, "GET", "/user2/readme-test/src/branch/symlink") + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: 2, ID: 1}) + err := git.ForceFastImport(t.Context(), repo.CodeStorageRepo(), []git.FastImportCommit{ + { + Ref: "refs/heads/symlink", Message: "test", Files: []git.FastImportFile{ + {git.EntryModeSymlink, "README.md", "some/other/path/awefulcake.txt"}, + {git.EntryModeBlob, "some/other/path/awefulcake.txt", "text content"}, + }, + }, + }) + require.NoError(t, err) + req := NewRequest(t, "GET", "/user2/repo1/src/branch/symlink") resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) AssertHTMLElement(t, htmlDoc, ".entry-symbol-link", true) followSymbolLinkHref := htmlDoc.Find(".entry-symbol-link").AttrOr("href", "") - require.Equal(t, "/user2/readme-test/src/branch/symlink/README.md?follow_symlink=1", followSymbolLinkHref) + require.Equal(t, "/user2/repo1/src/branch/symlink/README.md?follow_symlink=1", followSymbolLinkHref) req = NewRequest(t, "GET", followSymbolLinkHref) resp = session.MakeRequest(t, req, http.StatusSeeOther) - assert.Equal(t, "/user2/readme-test/src/branch/symlink/some/other/path/awefulcake.txt?follow_symlink=1", resp.Header().Get("Location")) + assert.Equal(t, "/user2/repo1/src/branch/symlink/some/other/path/awefulcake.txt?follow_symlink=1", resp.Header().Get("Location")) } func testMarkDownReadmeImage(t *testing.T) {