mirror of
https://github.com/go-gitea/gitea
synced 2025-07-22 18:28:37 +00:00
Add file tree to file view page (#32721)
Resolve #29328 This pull request introduces a file tree on the left side when reviewing files of a repository. --------- Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -7,9 +7,13 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
repo_model "code.gitea.io/gitea/models/repo"
|
||||
"code.gitea.io/gitea/modules/git"
|
||||
"code.gitea.io/gitea/modules/log"
|
||||
"code.gitea.io/gitea/modules/setting"
|
||||
api "code.gitea.io/gitea/modules/structs"
|
||||
"code.gitea.io/gitea/modules/util"
|
||||
@@ -118,3 +122,98 @@ func GetTreeBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git
|
||||
}
|
||||
return tree, nil
|
||||
}
|
||||
|
||||
func entryModeString(entryMode git.EntryMode) string {
|
||||
switch entryMode {
|
||||
case git.EntryModeBlob:
|
||||
return "blob"
|
||||
case git.EntryModeExec:
|
||||
return "exec"
|
||||
case git.EntryModeSymlink:
|
||||
return "symlink"
|
||||
case git.EntryModeCommit:
|
||||
return "commit" // submodule
|
||||
case git.EntryModeTree:
|
||||
return "tree"
|
||||
}
|
||||
return "unknown"
|
||||
}
|
||||
|
||||
type TreeViewNode struct {
|
||||
EntryName string `json:"entryName"`
|
||||
EntryMode string `json:"entryMode"`
|
||||
FullPath string `json:"fullPath"`
|
||||
SubmoduleURL string `json:"submoduleUrl,omitempty"`
|
||||
Children []*TreeViewNode `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
func (node *TreeViewNode) sortLevel() int {
|
||||
return util.Iif(node.EntryMode == "tree" || node.EntryMode == "commit", 0, 1)
|
||||
}
|
||||
|
||||
func newTreeViewNodeFromEntry(ctx context.Context, commit *git.Commit, parentDir string, entry *git.TreeEntry) *TreeViewNode {
|
||||
node := &TreeViewNode{
|
||||
EntryName: entry.Name(),
|
||||
EntryMode: entryModeString(entry.Mode()),
|
||||
FullPath: path.Join(parentDir, entry.Name()),
|
||||
}
|
||||
|
||||
if node.EntryMode == "commit" {
|
||||
if subModule, err := commit.GetSubModule(node.FullPath); err != nil {
|
||||
log.Error("GetSubModule: %v", err)
|
||||
} else if subModule != nil {
|
||||
submoduleFile := git.NewCommitSubmoduleFile(subModule.URL, entry.ID.String())
|
||||
webLink := submoduleFile.SubmoduleWebLink(ctx)
|
||||
node.SubmoduleURL = webLink.CommitWebLink
|
||||
}
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
// sortTreeViewNodes list directory first and with alpha sequence
|
||||
func sortTreeViewNodes(nodes []*TreeViewNode) {
|
||||
sort.Slice(nodes, func(i, j int) bool {
|
||||
a, b := nodes[i].sortLevel(), nodes[j].sortLevel()
|
||||
if a != b {
|
||||
return a < b
|
||||
}
|
||||
return nodes[i].EntryName < nodes[j].EntryName
|
||||
})
|
||||
}
|
||||
|
||||
func listTreeNodes(ctx context.Context, commit *git.Commit, tree *git.Tree, treePath, subPath string) ([]*TreeViewNode, error) {
|
||||
entries, err := tree.ListEntries()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
subPathDirName, subPathRemaining, _ := strings.Cut(subPath, "/")
|
||||
nodes := make([]*TreeViewNode, 0, len(entries))
|
||||
for _, entry := range entries {
|
||||
node := newTreeViewNodeFromEntry(ctx, commit, treePath, entry)
|
||||
nodes = append(nodes, node)
|
||||
if entry.IsDir() && subPathDirName == entry.Name() {
|
||||
subTreePath := treePath + "/" + node.EntryName
|
||||
if subTreePath[0] == '/' {
|
||||
subTreePath = subTreePath[1:]
|
||||
}
|
||||
subNodes, err := listTreeNodes(ctx, commit, entry.Tree(), subTreePath, subPathRemaining)
|
||||
if err != nil {
|
||||
log.Error("listTreeNodes: %v", err)
|
||||
} else {
|
||||
node.Children = subNodes
|
||||
}
|
||||
}
|
||||
}
|
||||
sortTreeViewNodes(nodes)
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func GetTreeViewNodes(ctx context.Context, commit *git.Commit, treePath, subPath string) ([]*TreeViewNode, error) {
|
||||
entry, err := commit.GetTreeEntryByPath(treePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return listTreeNodes(ctx, commit, entry.Tree(), treePath, subPath)
|
||||
}
|
||||
|
@@ -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,51 @@ func TestGetTreeBySHA(t *testing.T) {
|
||||
|
||||
assert.EqualValues(t, expectedTree, tree)
|
||||
}
|
||||
|
||||
func TestGetTreeViewNodes(t *testing.T) {
|
||||
unittest.PrepareTestEnv(t)
|
||||
ctx, _ := contexttest.MockContext(t, "user2/repo1")
|
||||
ctx.Repo.RefFullName = git.RefNameFromBranch("sub-home-md-img-check")
|
||||
contexttest.LoadRepo(t, ctx, 1)
|
||||
contexttest.LoadRepoCommit(t, ctx)
|
||||
contexttest.LoadUser(t, ctx, 2)
|
||||
contexttest.LoadGitRepo(t, ctx)
|
||||
defer ctx.Repo.GitRepo.Close()
|
||||
|
||||
treeNodes, err := GetTreeViewNodes(ctx, ctx.Repo.Commit, "", "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []*TreeViewNode{
|
||||
{
|
||||
EntryName: "docs",
|
||||
EntryMode: "tree",
|
||||
FullPath: "docs",
|
||||
},
|
||||
}, treeNodes)
|
||||
|
||||
treeNodes, err = GetTreeViewNodes(ctx, ctx.Repo.Commit, "", "docs/README.md")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []*TreeViewNode{
|
||||
{
|
||||
EntryName: "docs",
|
||||
EntryMode: "tree",
|
||||
FullPath: "docs",
|
||||
Children: []*TreeViewNode{
|
||||
{
|
||||
EntryName: "README.md",
|
||||
EntryMode: "blob",
|
||||
FullPath: "docs/README.md",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, treeNodes)
|
||||
|
||||
treeNodes, err = GetTreeViewNodes(ctx, ctx.Repo.Commit, "docs", "README.md")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []*TreeViewNode{
|
||||
{
|
||||
EntryName: "README.md",
|
||||
EntryMode: "blob",
|
||||
FullPath: "docs/README.md",
|
||||
},
|
||||
}, treeNodes)
|
||||
}
|
||||
|
Reference in New Issue
Block a user