diff --git a/routers/web/repo/treelist.go b/routers/web/repo/tree.go similarity index 69% rename from routers/web/repo/treelist.go rename to routers/web/repo/tree.go index d11af4669f..9388d65480 100644 --- a/routers/web/repo/treelist.go +++ b/routers/web/repo/tree.go @@ -9,6 +9,7 @@ import ( "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" ) @@ -52,3 +53,18 @@ func isExcludedEntry(entry *git.TreeEntry) bool { return false } + +func Tree(ctx *context.Context) { + 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/web.go b/routers/web/web.go index 5e5ed25c6c..355871bd99 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). diff --git a/services/repository/files/tree.go b/services/repository/files/tree.go index 6775186afd..3b31275bde 100644 --- a/services/repository/files/tree.go +++ b/services/repository/files/tree.go @@ -7,9 +7,12 @@ import ( "context" "fmt" "net/url" + "path" + "strings" 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" "code.gitea.io/gitea/modules/util" @@ -118,3 +121,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) +}