From b60ee86a2406c03f94993f3899e8508f93a59ccb Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 16 Dec 2024 09:36:07 -0800 Subject: [PATCH 1/2] Add tree --- routers/web/repo/{treelist.go => tree.go} | 26 +++++++++++++++++++++++ routers/web/repo/tree_test.go | 10 +++++++++ routers/web/web.go | 8 +++---- 3 files changed, 40 insertions(+), 4 deletions(-) rename routers/web/repo/{treelist.go => tree.go} (63%) create mode 100644 routers/web/repo/tree_test.go diff --git a/routers/web/repo/treelist.go b/routers/web/repo/tree.go similarity index 63% rename from routers/web/repo/treelist.go rename to routers/web/repo/tree.go index d11af4669f..13e6cea53f 100644 --- a/routers/web/repo/treelist.go +++ b/routers/web/repo/tree.go @@ -5,6 +5,8 @@ package repo import ( "net/http" + "path" + "strings" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/git" @@ -52,3 +54,27 @@ func isExcludedEntry(entry *git.TreeEntry) bool { return false } + +func getPossibleBranches(dir string) []string { + cnt := strings.Count(dir, "/") + branches := make([]string, cnt, cnt) + for i := 0; i < cnt; i++ { + branches[i] = dir + dir = path.Dir(dir) + } + return branches +} + +func guessRefInfoAndDir(ctx *context.Context, dir string) (git.RefName, string, error) { + branches := getPossibleBranches(dir) +} + +func Tree(ctx *context.Context) { + pathParam := ctx.PathParam("*") + dir := path.Dir(pathParam) + refName, realDir, err := guessRefInfoAndDir(ctx, dir) + if err != nil { + ctx.ServerError("guessRefInfoAndDir", err) + return + } +} diff --git a/routers/web/repo/tree_test.go b/routers/web/repo/tree_test.go new file mode 100644 index 0000000000..96663170f3 --- /dev/null +++ b/routers/web/repo/tree_test.go @@ -0,0 +1,10 @@ +// Copyright 2024 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import "testing" + +func Test_getPossibleBranches(t *testing.T) { + getPossibleBranches("") +} diff --git a/routers/web/web.go b/routers/web/web.go index bf8c4306bf..341c87e4da 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1157,14 +1157,14 @@ func registerRoutes(m *web.Router) { m.Group("/{username}/{reponame}", func() { m.Get("/find/*", repo.FindFiles) - m.Group("/tree-list", func() { + m.Group("/tree-list", func() { // for find files m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.TreeList) m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.TreeList) m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.TreeList) }) - m.Group("/contents", func() { - m.Get("", repo.GetContentsList) - m.Get("/*", repo.GetContents) + m.Group("/tree", func() { + m.Get("", repo.Tree) + m.Get("/*", repo.Tree) }) m.Get("/compare", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.CompareDiff) m.Combo("/compare/*", repo.MustBeNotEmpty, repo.SetEditorconfigIfExists). From a28b65d69b024725c7a95f6e5af1dca4dd90f411 Mon Sep 17 00:00:00 2001 From: Lunny Xiao Date: Mon, 23 Dec 2024 20:54:17 -0800 Subject: [PATCH 2/2] complete first version --- routers/web/repo/tree.go | 28 +++----- routers/web/repo/tree_test.go | 10 --- services/repository/files/tree.go | 92 ++++++++++++++++++++++++++ services/repository/files/tree_test.go | 48 ++++++++++++++ 4 files changed, 149 insertions(+), 29 deletions(-) delete mode 100644 routers/web/repo/tree_test.go diff --git a/routers/web/repo/tree.go b/routers/web/repo/tree.go index 13e6cea53f..9388d65480 100644 --- a/routers/web/repo/tree.go +++ b/routers/web/repo/tree.go @@ -5,12 +5,11 @@ package repo import ( "net/http" - "path" - "strings" "code.gitea.io/gitea/modules/base" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/services/context" + files_service "code.gitea.io/gitea/services/repository/files" "github.com/go-enry/go-enry/v2" ) @@ -55,26 +54,17 @@ func isExcludedEntry(entry *git.TreeEntry) bool { return false } -func getPossibleBranches(dir string) []string { - cnt := strings.Count(dir, "/") - branches := make([]string, cnt, cnt) - for i := 0; i < cnt; i++ { - branches[i] = dir - dir = path.Dir(dir) - } - return branches -} - -func guessRefInfoAndDir(ctx *context.Context, dir string) (git.RefName, string, error) { - branches := getPossibleBranches(dir) -} - func Tree(ctx *context.Context) { - pathParam := ctx.PathParam("*") - dir := path.Dir(pathParam) - refName, realDir, err := guessRefInfoAndDir(ctx, dir) + dir := ctx.PathParam("*") + ref := ctx.FormTrim("ref") + recursive := ctx.FormBool("recursive") + + // TODO: Only support branch for now + results, err := files_service.GetTreeList(ctx, ctx.Repo.Repository, dir, git.RefNameFromBranch(ref), recursive) if err != nil { ctx.ServerError("guessRefInfoAndDir", err) return } + + ctx.JSON(http.StatusOK, results) } diff --git a/routers/web/repo/tree_test.go b/routers/web/repo/tree_test.go deleted file mode 100644 index 96663170f3..0000000000 --- a/routers/web/repo/tree_test.go +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2024 The Gitea Authors. All rights reserved. -// SPDX-License-Identifier: MIT - -package repo - -import "testing" - -func Test_getPossibleBranches(t *testing.T) { - getPossibleBranches("") -} diff --git a/services/repository/files/tree.go b/services/repository/files/tree.go index e3a7f3b8b0..c16272ee36 100644 --- a/services/repository/files/tree.go +++ b/services/repository/files/tree.go @@ -7,10 +7,13 @@ import ( "context" "fmt" "net/url" + "path" + "strings" "code.gitea.io/gitea/models" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/gitrepo" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" ) @@ -99,3 +102,92 @@ func GetTreeBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git } return tree, nil } + +type TreeEntry struct { + Name string `json:"name"` + IsFile bool `json:"isFile"` + Path string `json:"path"` + Children []*TreeEntry `json:"children"` +} + +func GetTreeList(ctx context.Context, repo *repo_model.Repository, treePath string, ref git.RefName, recursive bool) ([]*TreeEntry, error) { + if repo.IsEmpty { + return nil, nil + } + if ref == "" { + ref = git.RefNameFromBranch(repo.DefaultBranch) + } + + // Check that the path given in opts.treePath is valid (not a git path) + cleanTreePath := CleanUploadFileName(treePath) + if cleanTreePath == "" && treePath != "" { + return nil, models.ErrFilenameInvalid{ + Path: treePath, + } + } + treePath = cleanTreePath + + gitRepo, closer, err := gitrepo.RepositoryFromContextOrOpen(ctx, repo) + if err != nil { + return nil, err + } + defer closer.Close() + + // Get the commit object for the ref + commit, err := gitRepo.GetCommit(ref.String()) + if err != nil { + return nil, err + } + + entry, err := commit.GetTreeEntryByPath(treePath) + if err != nil { + return nil, err + } + + // If the entry is a file, we return a FileContentResponse object + if entry.Type() != "tree" { + return nil, fmt.Errorf("%s is not a tree", treePath) + } + + gitTree, err := commit.SubTree(treePath) + if err != nil { + return nil, err + } + var entries git.Entries + if recursive { + entries, err = gitTree.ListEntriesRecursiveFast() + } else { + entries, err = gitTree.ListEntries() + } + if err != nil { + return nil, err + } + + var treeList []*TreeEntry + mapTree := make(map[string][]*TreeEntry) + for _, e := range entries { + subTreePath := path.Join(treePath, e.Name()) + + if strings.Contains(e.Name(), "/") { + mapTree[path.Dir(e.Name())] = append(mapTree[path.Dir(e.Name())], &TreeEntry{ + Name: path.Base(e.Name()), + IsFile: e.Mode() != git.EntryModeTree, + Path: subTreePath, + }) + } else { + treeList = append(treeList, &TreeEntry{ + Name: e.Name(), + IsFile: e.Mode() != git.EntryModeTree, + Path: subTreePath, + }) + } + } + + for _, tree := range treeList { + if !tree.IsFile { + tree.Children = mapTree[tree.Path] + } + } + + return treeList, nil +} diff --git a/services/repository/files/tree_test.go b/services/repository/files/tree_test.go index 786bc15857..a329a39405 100644 --- a/services/repository/files/tree_test.go +++ b/services/repository/files/tree_test.go @@ -7,6 +7,7 @@ import ( "testing" "code.gitea.io/gitea/models/unittest" + "code.gitea.io/gitea/modules/git" api "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/services/contexttest" @@ -50,3 +51,50 @@ func TestGetTreeBySHA(t *testing.T) { assert.EqualValues(t, expectedTree, tree) } + +func Test_GetTreeList(t *testing.T) { + unittest.PrepareTestEnv(t) + ctx1, _ := contexttest.MockContext(t, "user2/repo1") + contexttest.LoadRepo(t, ctx1, 1) + contexttest.LoadRepoCommit(t, ctx1) + contexttest.LoadUser(t, ctx1, 2) + contexttest.LoadGitRepo(t, ctx1) + defer ctx1.Repo.GitRepo.Close() + + refName := git.RefNameFromBranch(ctx1.Repo.Repository.DefaultBranch) + + treeList, err := GetTreeList(ctx1, ctx1.Repo.Repository, "", refName, true) + assert.NoError(t, err) + assert.Len(t, treeList, 1) + assert.EqualValues(t, "README.md", treeList[0].Name) + assert.EqualValues(t, "README.md", treeList[0].Path) + assert.True(t, treeList[0].IsFile) + assert.Empty(t, treeList[0].Children) + + ctx2, _ := contexttest.MockContext(t, "org3/repo3") + contexttest.LoadRepo(t, ctx2, 3) + contexttest.LoadRepoCommit(t, ctx2) + contexttest.LoadUser(t, ctx2, 2) + contexttest.LoadGitRepo(t, ctx2) + defer ctx2.Repo.GitRepo.Close() + + refName = git.RefNameFromBranch(ctx2.Repo.Repository.DefaultBranch) + + treeList, err = GetTreeList(ctx2, ctx2.Repo.Repository, "", refName, true) + assert.NoError(t, err) + assert.Len(t, treeList, 2) + assert.EqualValues(t, "README.md", treeList[0].Name) + assert.EqualValues(t, "README.md", treeList[0].Path) + assert.True(t, treeList[0].IsFile) + assert.Empty(t, treeList[0].Children) + + assert.EqualValues(t, "doc", treeList[1].Name) + assert.EqualValues(t, "doc", treeList[1].Path) + assert.False(t, treeList[1].IsFile) + assert.Len(t, treeList[1].Children, 1) + + assert.EqualValues(t, "doc.md", treeList[1].Children[0].Name) + assert.EqualValues(t, "doc/doc.md", treeList[1].Children[0].Path) + assert.True(t, treeList[1].Children[0].IsFile) + assert.Empty(t, treeList[1].Children[0].Children) +}