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 <g 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 a7f4501ea2e..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/50/6ff7310f420e878595b4bc8f11688e3f0ae14e and /dev/null differ 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 a2dadacf8ac..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/58/3eb775c596858380273492759d39081d65d029 and /dev/null differ 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 3e4c8247d90..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/6a/b05db4c52530726c1856eb558228e9d1949e7f and /dev/null differ 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 477d5b1f1f4..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/71/60a063b5544b5a78131b94f47bfd200046eda2 and /dev/null differ 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 3ef17969867..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/75/6c70c97047d8aeb11ca3c71edd9fb76cefee9c and /dev/null differ diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/7f/2b9f991d99362eb827b67f4ae2f5fbc5fa2271 b/tests/gitea-repositories-meta/user2/readme-test.git/objects/7f/2b9f991d99362eb827b67f4ae2f5fbc5fa2271 deleted file mode 100644 index 4e39c0306ea..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/7f/2b9f991d99362eb827b67f4ae2f5fbc5fa2271 and /dev/null differ 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 90fae23886c..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/7f/792e709143fb0f021da2371e5f40d1bcc284fd and /dev/null differ diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/82/817856dadc7f6b944633e1b77d5b6e302dde06 b/tests/gitea-repositories-meta/user2/readme-test.git/objects/82/817856dadc7f6b944633e1b77d5b6e302dde06 deleted file mode 100644 index 0428af472a4..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/82/817856dadc7f6b944633e1b77d5b6e302dde06 and /dev/null differ diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/8b/4149e7dede3cd53ba11c64c88b057c5fe2c200 b/tests/gitea-repositories-meta/user2/readme-test.git/objects/8b/4149e7dede3cd53ba11c64c88b057c5fe2c200 deleted file mode 100644 index 64542d37589..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/8b/4149e7dede3cd53ba11c64c88b057c5fe2c200 and /dev/null differ 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 8542b45c75f..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/93/54813d81053c14afe878a9f056b937ec42bb48 and /dev/null differ diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/9c/72c10e55e7d6ea21f591aa424e2625e8ad8094 b/tests/gitea-repositories-meta/user2/readme-test.git/objects/9c/72c10e55e7d6ea21f591aa424e2625e8ad8094 deleted file mode 100644 index b53d42aee9e..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/9c/72c10e55e7d6ea21f591aa424e2625e8ad8094 and /dev/null differ 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 6d9bc2a7664..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/a3/cd04bb110e17591ac04e156c7df2c2f5c96fa6 and /dev/null differ 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 2f88dbd361a..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/b0/e851a5619e2d6cee1da25a15ab67305f0861ec and /dev/null differ 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 55bb8491afe..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/b4/4c8eb00bdaf0522de61e591fee5f66851ef4b5 and /dev/null differ 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 84ab5684a00..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/b8/eaa80ad86072e1f23d2638842154ce9aceff8d and /dev/null differ 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 7bb434442f7..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/d5/34f914944c3c943a6bdb677d869ac54934928d and /dev/null differ diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/e2/f9904cd97b4045feecfffef5a426e9461bee70 b/tests/gitea-repositories-meta/user2/readme-test.git/objects/e2/f9904cd97b4045feecfffef5a426e9461bee70 deleted file mode 100644 index 9654f877b8c..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/e2/f9904cd97b4045feecfffef5a426e9461bee70 and /dev/null differ 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 9f898ef8738..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/e7/bf02fcfa7a86f7fe9e8158b55d58ddf9d877ec and /dev/null differ 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 d1eff52868d..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/ea/57c91ddb8b4ac705b5ac4c34c7a48f2d0fc180 and /dev/null differ 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 48c0b5a4d62..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/fe/495ea336f079ef2bed68648d0ba9a37cdbd4aa and /dev/null differ 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 9bb0976fdc3..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/info/commit-graph and /dev/null differ diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/info/packs b/tests/gitea-repositories-meta/user2/readme-test.git/objects/info/packs deleted file mode 100644 index aad10862f14..00000000000 --- a/tests/gitea-repositories-meta/user2/readme-test.git/objects/info/packs +++ /dev/null @@ -1,2 +0,0 @@ -P pack-8933bd634b76f8154310cccb52537a0195e43166.pack - diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/pack/pack-8933bd634b76f8154310cccb52537a0195e43166.bitmap b/tests/gitea-repositories-meta/user2/readme-test.git/objects/pack/pack-8933bd634b76f8154310cccb52537a0195e43166.bitmap deleted file mode 100644 index db39955a253..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/pack/pack-8933bd634b76f8154310cccb52537a0195e43166.bitmap and /dev/null differ 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 561e0f2e9d2..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/pack/pack-8933bd634b76f8154310cccb52537a0195e43166.idx and /dev/null differ diff --git a/tests/gitea-repositories-meta/user2/readme-test.git/objects/pack/pack-8933bd634b76f8154310cccb52537a0195e43166.pack b/tests/gitea-repositories-meta/user2/readme-test.git/objects/pack/pack-8933bd634b76f8154310cccb52537a0195e43166.pack deleted file mode 100644 index 6f5bf1f044b..00000000000 Binary files a/tests/gitea-repositories-meta/user2/readme-test.git/objects/pack/pack-8933bd634b76f8154310cccb52537a0195e43166.pack and /dev/null differ 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) {