mirror of
https://github.com/hanzoai/forge.git
synced 2026-10-30 15:00:39 +00:00
Source files keep their SPDX-License-Identifier and Hanzo copyright line; the Gitea, Gogs and LFS Test Server copyrights they used to repeat now live once in NOTICE, alongside LICENSE. The header lint accepts any copyright line above the SPDX line, so it no longer demands an upstream one. Co-authored-by: Hanzo Dev <[email protected]>
154 lines
4.5 KiB
Go
154 lines
4.5 KiB
Go
// Copyright 2026 Hanzo AI, Inc. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package repo
|
|
|
|
import (
|
|
"html/template"
|
|
"net/http"
|
|
"path"
|
|
"strings"
|
|
|
|
pull_model "github.com/hanzoai/git/models/pull"
|
|
"github.com/hanzoai/git/modules/base"
|
|
"github.com/hanzoai/git/modules/fileicon"
|
|
"github.com/hanzoai/git/modules/git"
|
|
"github.com/hanzoai/git/services/context"
|
|
"github.com/hanzoai/git/services/gitdiff"
|
|
files_service "github.com/hanzoai/git/services/repository/files"
|
|
|
|
"github.com/go-enry/go-enry/v2"
|
|
)
|
|
|
|
// TreeList get all files' entries of a repository
|
|
func TreeList(ctx *context.Context) {
|
|
tree, err := ctx.Repo.Commit.SubTree(ctx, ctx.Repo.GitRepo, "/")
|
|
if err != nil {
|
|
ctx.ServerError("Repo.Commit.SubTree", err)
|
|
return
|
|
}
|
|
|
|
entries, err := tree.ListEntriesRecursiveFast(ctx, ctx.Repo.GitRepo)
|
|
if err != nil {
|
|
ctx.ServerError("ListEntriesRecursiveFast", err)
|
|
return
|
|
}
|
|
entries.CustomSort(base.NaturalSortCompare)
|
|
|
|
files := make([]string, 0, len(entries))
|
|
for _, entry := range entries {
|
|
if !isExcludedEntry(entry) {
|
|
files = append(files, entry.Name())
|
|
}
|
|
}
|
|
ctx.JSON(http.StatusOK, files)
|
|
}
|
|
|
|
func isExcludedEntry(entry *git.TreeEntry) bool {
|
|
if entry.IsDir() {
|
|
return true
|
|
}
|
|
|
|
if entry.IsSubModule() {
|
|
return true
|
|
}
|
|
|
|
if enry.IsVendor(entry.Name()) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// WebDiffFileItem is used by frontend, check the field names in frontend before changing
|
|
type WebDiffFileItem struct {
|
|
FullName string
|
|
DisplayName string
|
|
NameHash string
|
|
DiffStatus string
|
|
EntryMode string
|
|
IsViewed bool
|
|
Children []*WebDiffFileItem
|
|
FileIcon template.HTML
|
|
}
|
|
|
|
// WebDiffFileTree is used by frontend, check the field names in frontend before changing
|
|
type WebDiffFileTree struct {
|
|
TreeRoot WebDiffFileItem
|
|
}
|
|
|
|
// transformDiffTreeForWeb transforms a gitdiff.DiffTree into a WebDiffFileTree for Web UI rendering
|
|
// it also takes a map of file names to their viewed state, which is used to mark files as viewed
|
|
func transformDiffTreeForWeb(renderedIconPool *fileicon.RenderedIconPool, diffTree *gitdiff.DiffTree, filesViewedState map[string]pull_model.ViewedState) (dft WebDiffFileTree) {
|
|
dirNodes := map[string]*WebDiffFileItem{"": &dft.TreeRoot}
|
|
addItem := func(item *WebDiffFileItem) {
|
|
var parentPath string
|
|
pos := strings.LastIndexByte(item.FullName, '/')
|
|
if pos == -1 {
|
|
item.DisplayName = item.FullName
|
|
} else {
|
|
parentPath = item.FullName[:pos]
|
|
item.DisplayName = item.FullName[pos+1:]
|
|
}
|
|
parentNode, parentExists := dirNodes[parentPath]
|
|
if !parentExists {
|
|
parentNode = &dft.TreeRoot
|
|
fields := strings.Split(parentPath, "/")
|
|
for idx, field := range fields {
|
|
nodePath := strings.Join(fields[:idx+1], "/")
|
|
node, ok := dirNodes[nodePath]
|
|
if !ok {
|
|
node = &WebDiffFileItem{EntryMode: "tree", DisplayName: field, FullName: nodePath}
|
|
dirNodes[nodePath] = node
|
|
parentNode.Children = append(parentNode.Children, node)
|
|
}
|
|
parentNode = node
|
|
}
|
|
}
|
|
parentNode.Children = append(parentNode.Children, item)
|
|
}
|
|
|
|
for _, file := range diffTree.Files {
|
|
item := &WebDiffFileItem{FullName: file.HeadPath, DiffStatus: file.Status}
|
|
item.IsViewed = filesViewedState[item.FullName] == pull_model.Viewed
|
|
item.NameHash = git.HashFilePathForWebUI(item.FullName)
|
|
item.FileIcon = fileicon.RenderEntryIconHTML(renderedIconPool, &fileicon.EntryInfo{BaseName: path.Base(file.HeadPath), EntryMode: file.HeadMode})
|
|
|
|
switch file.HeadMode {
|
|
case git.EntryModeTree:
|
|
item.EntryMode = "tree"
|
|
case git.EntryModeCommit:
|
|
item.EntryMode = "commit" // submodule
|
|
default:
|
|
// default to empty, and will be treated as "blob" file because there is no "symlink" support yet
|
|
}
|
|
addItem(item)
|
|
}
|
|
|
|
var mergeSingleDir func(node *WebDiffFileItem)
|
|
mergeSingleDir = func(node *WebDiffFileItem) {
|
|
if len(node.Children) == 1 {
|
|
if child := node.Children[0]; child.EntryMode == "tree" {
|
|
node.FullName = child.FullName
|
|
node.DisplayName = node.DisplayName + "/" + child.DisplayName
|
|
node.Children = child.Children
|
|
mergeSingleDir(node)
|
|
}
|
|
}
|
|
}
|
|
for _, node := range dft.TreeRoot.Children {
|
|
mergeSingleDir(node)
|
|
}
|
|
return dft
|
|
}
|
|
|
|
func TreeViewNodes(ctx *context.Context) {
|
|
renderedIconPool := fileicon.NewRenderedIconPool()
|
|
results, err := files_service.GetTreeViewNodes(ctx, ctx.Repo.RepoLink, renderedIconPool, ctx.Repo.GitRepo, ctx.Repo.Commit, ctx.Repo.TreePath, ctx.FormString("sub_path"))
|
|
if err != nil {
|
|
ctx.ServerError("GetTreeViewNodes", err)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, map[string]any{"fileTreeNodes": results, "renderedIconPool": renderedIconPool.IconSVGs})
|
|
}
|