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 <?Bs~X{vOTU&9esUW+!Zqw1)ZQfIbCG_Vk)$p3G_n+bPFNbXpQ?6M(ec!z3ccVyfk4b*o zig(pw23b`TneYFZryw#TYYH5gxF^**y_DjvDc(WJ$(Hp;}P>c@9ooXU#`3N`rGEyWs3h9Z&|fl zEHeGDYy(-FeGDh=>3eWoyck%>xc;%K;7?5**6r#}a~g`4m@ihpY)~S) zrhN9U6-~+a#MiGla4Fol0vB_o2Ieo`9=T^`<;9Ims;~EO8$NRiQ3*^tAM~v^ z)bGZHT}#gen1;?9|4+IhmiQ?saA=LqMEXPIBzgr66_ zF_!3V-_zAFLxd?Pr#YYFq}s$8L6cHu_Dpv0Ggp$kdT6zeB+JQS*~s79o=)14*V1IR z?%S=oEWMhlbDJKOzk4S7uD0gj+t@39VHWF@r9a+^{+WWP_|vf=IH3;yeUYZ;u?1CIj$epOZC 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 zcmb}<|9dRX-Bi_N5YOjd F006l#6TAQb 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 zcmbZg 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 zcmbRTT7+Ht48#^M1+MA>zhTl9-P>GLr3S8$L5)r7bP=J3e!I`MU!o%!iybWFB%?Q z*Q>FO`^v&@wY5*}bPKAUsLNbmb+5GXZp6l&e?J)b|E<=joqSm?XGQ3yBXZk3I@Id@ Zy|@0$pRsu#S4{Bz_@KI3{EoZ4lL6VoQ11W$ 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 zcmb}MwhhN(G zPUo}x(Z6rTLA&W~k&_Kfzc_Nu)kx6mSkqg4F>FTYq}`{Jw)1iN3Z)w69o!NXsCDwV z(<$E-No%I6WR-3D_Ib~NkBh=B*37Qj#%h-@&9`6w&%X1IlV3ev@QHQGjN~N`gx?q_ dS^c$tJNs+h;lDTH+b8!Vi|*Z}&Jy7J8~}YMQSJZ$ diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/60/ea618ae7d4ecbe9c1962591c7da1b05bb1a5c8 b/tests/gitea-repositories-meta/user2/readme-test.git/objects/60/ea618ae7d4ecbe9c1962591c7da1b05bb1a5c8 deleted file mode 100644 index 4367c41e285..00000000000 --- a/tests/gitea-repositories-meta/user2/readme-test.git/objects/60/ea618ae7d4ecbe9c1962591c7da1b05bb1a5c8 +++ /dev/null @@ -1,3 +0,0 @@ -x;0 @s -_*N뤕bccЊTҁS!Nd>!ք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 zcmb0*DR9pHpym!+!AC4pg3n}Jy79f= zYi*`^Dcgxian-{af{zah_UN7B`_?6MK~g?qaj&Ou;<12}o1VO}I(u^q&-*=hZ)PNY zip>5f7yFDMcfp~c_m6#^yH{PxjJ#@{c8g!zY`eVN@{N0bN6ix9TXNJgd+UY^({wj` b-M_pm>c9CF*7aVNQ-qwWR`CbC_LBtw6oXMa 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 kcmby%1uxvTaP7Uu zJgYKb@7uMU=Qb&S;hvewx2kygk)i|Pi$ve0J1ffrvU3cd%18aVA7Xal>Wat0@ diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/7f/792e709143fb0f021da2371e5f40d1bcc284fd b/tests/gitea-repositories-meta/user2/readme-test.git/objects/7f/792e709143fb0f021da2371e5f40d1bcc284fd deleted file mode 100644 index 90fae23886ca83c4be056c5dd3c8125047bcf0b9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 166 zcmbE}UjfSi1J?cOPJ0@Px+HtgJg!*% zpy~D%S5ceK>sVa3`pn~+7BH=rIqNy|orhm;UA0$l!~C$Eb@n!P&mP)dTPSGP(5opJ`JQ8&M~B6`jPaYEJa*lGXHNtRodyMdprz zxQR>7@jVS+p01I#Z1E~)EvxdKpKcYg{d_8CUs>_~`FR_Q;;MOCM{G5Jo;=FKZS+I! c<{bOw8HG9Zfu9yVPuHJuHiU`6+Rn`x0P`G8t^fc4 diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/93/54813d81053c14afe878a9f056b937ec42bb48 b/tests/gitea-repositories-meta/user2/readme-test.git/objects/93/54813d81053c14afe878a9f056b937ec42bb48 deleted file mode 100644 index 8542b45c75ff86619f87b916e8ee3df903bd9e8e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28 kcmbWUXx$Eao>!0_~^g_jk|63dd2dSOX5UCt9EMgOm>#am5$7Hxwd~r#81VX th3zibQyg!F46h6D$|15C*<++oq8Qm>SWdYSbKCu7* 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 zcmb)DVqjv#z|^V`?k*l5zW0tu8*5bKrfTt(>Uoym{-=fSop|KR^yPIk p8m>N>p0Y&DkflN*e#4eum#^>FDmlJtg1eNx?vuX^BCBQJ0RTg6BvSwY 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 zcmb}<|9dRX-Bi^SVAcG2 j@+i;2db|AvnUPOttlGQU( UEqda5{(PLl?3FAG;)^vK0o)!l3IG5A diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/b8/eaa80ad86072e1f23d2638842154ce9aceff8d b/tests/gitea-repositories-meta/user2/readme-test.git/objects/b8/eaa80ad86072e1f23d2638842154ce9aceff8d deleted file mode 100644 index 84ab5684a0093099072185262a4a43517cb15030..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 77 zcmb}<|9dRX-Bi^SVAcG2 j@+c3t(GRtobL^LA6z0?iep>WAU4O>e5GIDz0Wxa<)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 ncmb&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 zcmbD=&&k}HR6Z(p4jzQe~lY&~=SX`7m3MjuaCGDz5P7_Qu}<|9dRX-Bi^SVAcG2 k@+eQevf1066^)nIKDZKd-gVOEnxs0jcPtFM{>r8R0P;s8YybcN 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 zcmb2lyE-KKXms zIVQ2klS|S(EN|tQRG;6-qL#hNzwJciakGf?9;~HBuNIt}%@F*k%TO#>VC6A^3t~N- za~*q+v4!`ZGu5_Ay=7f85}81LAcGT{zk90cVAk)Nq@}vnayaT!?J^~*tveh GF#-Txu3$X? diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/info/commit-graph b/tests/gitea-repositories-meta/user2/readme-test.git/objects/info/commit-graph deleted file mode 100644 index 9bb0976fdc30e99f66fae5f74525635980f9d928..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2612 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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) {