// Copyright (C) 2023, Jakob Wakeling
// All rights reserved.
package repo
import (
"errors"
"html/template"
"log"
"net/http"
"path"
"sort"
"strings"
"github.com/Jamozed/Goit/src/goit"
"github.com/Jamozed/Goit/src/util"
"github.com/dustin/go-humanize"
"github.com/go-chi/chi/v5"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
)
func HandleTree(w http.ResponseWriter, r *http.Request) {
auth, user, err := goit.Auth(w, r, true)
if err != nil {
log.Println("[admin]", err.Error())
goit.HttpError(w, http.StatusInternalServerError)
}
tpath := chi.URLParam(r, "*")
repo, err := goit.GetRepoByName(chi.URLParam(r, "repo"))
if err != nil {
goit.HttpError(w, http.StatusInternalServerError)
return
} else if repo == nil || !goit.IsVisible(repo, auth, user) {
goit.HttpError(w, http.StatusNotFound)
return
}
type row struct {
Mode, Name, Path, RawPath, Size string
IsFile, IsText, B bool
}
data := struct {
HeaderFields
Title, Path, Size string
Files []row
HtmlPath template.HTML
}{
Title: repo.Name + " - Tree",
HeaderFields: GetHeaderFields(auth, user, repo, r.Host),
}
parts := strings.Split(tpath, "/")
htmlPath := "" + repo.Name + "/"
dirPath := ""
for i := 0; i < len(parts)-1; i += 1 {
dirPath = path.Join(dirPath, parts[i])
htmlPath += "" + parts[i] + "/"
}
htmlPath += parts[len(parts)-1]
data.Path = tpath
data.HtmlPath = template.HTML(htmlPath)
gr, err := git.PlainOpen(goit.RepoPath(repo.Name, true))
if err != nil {
log.Println("[/repo/tree]", err.Error())
goit.HttpError(w, http.StatusInternalServerError)
return
}
if ref, err := gr.Head(); err != nil {
if !errors.Is(err, plumbing.ErrReferenceNotFound) {
log.Println("[/repo/tree]", err.Error())
goit.HttpError(w, http.StatusInternalServerError)
return
}
} else {
if readme, _ := findPattern(gr, ref, readmePattern); readme != "" {
data.Readme = path.Join("/", repo.Name, "file", readme)
}
if licence, _ := findPattern(gr, ref, licencePattern); licence != "" {
data.Licence = path.Join("/", repo.Name, "file", licence)
}
commit, err := gr.CommitObject(ref.Hash())
if err != nil {
log.Println("[/repo/tree]", err.Error())
goit.HttpError(w, http.StatusInternalServerError)
return
}
tree, err := commit.Tree()
if err != nil {
log.Println("[/repo/tree]", err.Error())
goit.HttpError(w, http.StatusInternalServerError)
return
}
if tpath != "" {
data.Files = append(data.Files, row{
Mode: "d---------", Name: "..", Path: path.Join("tree", path.Dir(tpath)),
})
tree, err = tree.Tree(tpath)
if errors.Is(err, object.ErrDirectoryNotFound) {
goit.HttpError(w, http.StatusNotFound)
return
} else if err != nil {
log.Println("[/repo/tree]", err.Error())
goit.HttpError(w, http.StatusInternalServerError)
return
}
}
sort.SliceStable(tree.Entries, func(i, j int) bool {
if tree.Entries[i].Mode&0o40000 != 0 && tree.Entries[j].Mode&0o40000 == 0 {
return true
}
return tree.Entries[i].Name < tree.Entries[j].Name
})
var totalSize uint64
for _, v := range tree.Entries {
var fpath, rpath, size string
var isFile, isText bool
if v.Mode&0o40000 == 0 {
fpath = path.Join("file", tpath, v.Name)
rpath = path.Join(tpath, v.Name)
isFile = true
goit.SizesLock.RLock()
sz, ok := goit.Sizes[v.Hash]
goit.SizesLock.RUnlock()
if !ok {
file, err := tree.File(v.Name)
if err != nil {
log.Println("[/repo/tree]", err.Error())
goit.HttpError(w, http.StatusInternalServerError)
return
}
sz = uint64(file.Size)
goit.SizesLock.Lock()
goit.Sizes[v.Hash] = sz
goit.SizesLock.Unlock()
}
size = humanize.IBytes(sz)
totalSize += sz
file, err := tree.File(v.Name)
if err == nil {
ftype, err := goit.GetFileType(file)
if err == nil {
if strings.HasPrefix(ftype, "text") {
isText = true
}
}
}
} else {
fpath = path.Join("tree", tpath, v.Name)
rpath = path.Join(tpath, v.Name)
goit.SizesLock.RLock()
sz, ok := goit.Sizes[v.Hash]
goit.SizesLock.RUnlock()
if !ok {
dirt, err := tree.Tree(v.Name)
if err != nil {
log.Println("[/repo/tree]", err.Error())
goit.HttpError(w, http.StatusInternalServerError)
return
}
var dirSize uint64
if err := dirt.Files().ForEach(func(f *object.File) error {
dirSize += uint64(f.Size)
return nil
}); err != nil {
log.Println("[/repo/tree]", err.Error())
goit.HttpError(w, http.StatusInternalServerError)
return
}
sz = dirSize
goit.SizesLock.Lock()
goit.Sizes[v.Hash] = sz
goit.SizesLock.Unlock()
}
size = humanize.IBytes(sz)
totalSize += sz
}
data.Files = append(data.Files, row{
Mode: util.ModeString(uint32(v.Mode)), Name: v.Name, Path: fpath, RawPath: rpath, Size: size,
IsFile: isFile, IsText: isText, B: util.If(strings.HasSuffix(size, " B"), true, false),
})
}
data.Size = humanize.IBytes(totalSize)
}
if err := goit.Tmpl.ExecuteTemplate(w, "repo/tree", data); err != nil {
log.Println("[/repo/tree]", err.Error())
}
}