// Copyright (C) 2023, Jakob Wakeling
// All rights reserved.
package repo
import (
"errors"
"fmt"
"html/template"
"net/http"
"path"
"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 HandleBlame(w http.ResponseWriter, r *http.Request) {
auth, user, err := goit.Auth(w, r, true)
if err != nil {
util.PrintFuncError(err)
goit.HttpError(w, http.StatusInternalServerError)
return
}
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 Bline struct {
Hash, ShortHash, Author, Date, Line string
LineHTML template.HTML
}
data := struct {
HeaderFields
Title, Path, LineC, Size, Mode string
Blines []Bline
PathHTML template.HTML
}{
Title: repo.Name + " - " + tpath,
HeaderFields: GetHeaderFields(auth, user, repo, r.Host),
}
gr, err := git.PlainOpen(goit.RepoPath(repo.Name, true))
if err != nil {
util.PrintFuncError(err)
goit.HttpError(w, http.StatusInternalServerError)
return
}
ref, err := gr.Head()
if errors.Is(err, plumbing.ErrReferenceNotFound) {
goit.HttpError(w, http.StatusNotFound)
return
} else if err != nil {
util.PrintFuncError(err)
goit.HttpError(w, http.StatusInternalServerError)
return
}
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 {
util.PrintFuncError(err)
goit.HttpError(w, http.StatusInternalServerError)
return
}
file, err := commit.File(tpath)
if errors.Is(err, object.ErrFileNotFound) {
goit.HttpError(w, http.StatusNotFound)
return
} else if err != nil {
util.PrintFuncError(err)
goit.HttpError(w, http.StatusInternalServerError)
return
}
data.Mode = util.ModeString(uint32(file.Mode))
data.Path = file.Name
data.Size = humanize.IBytes(uint64(file.Size))
parts := strings.Split(file.Name, "/")
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.PathHTML = template.HTML(htmlPath)
ftype, err := goit.GetFileType(file)
if err != nil {
util.PrintFuncError(err)
goit.HttpError(w, http.StatusInternalServerError)
return
}
/* Only populate blines for text files */
if strings.HasPrefix(ftype, "text") {
rc, err := file.Blob.Reader()
if err != nil {
util.PrintFuncError(err)
goit.HttpError(w, http.StatusInternalServerError)
return
}
defer rc.Close()
buf := make([]byte, min(file.Size, (10*1024*1024)))
if _, err := rc.Read(buf); err != nil {
util.PrintFuncError(err)
goit.HttpError(w, http.StatusInternalServerError)
return
}
body, _, err := Highlight(file.Name, string(buf), true)
if err != nil {
util.PrintFuncError(err)
goit.HttpError(w, http.StatusInternalServerError)
return
}
blame, err := git.Blame(commit, tpath)
if err != nil {
util.PrintFuncError(err)
goit.HttpError(w, http.StatusInternalServerError)
return
}
htmlLines := strings.Split(body, "\n")
for i, bline := range blame.Lines {
data.Blines = append(data.Blines, Bline{
Hash: bline.Hash.String(),
ShortHash: bline.Hash.String()[:7],
Author: bline.AuthorName,
Date: bline.Date.Format("2006-01-02 15:04:05"),
Line: bline.Text,
})
if i < len(htmlLines) {
htmlLines[i] = strings.TrimPrefix(htmlLines[i], "")
htmlLines[i] += ""
data.Blines[i].LineHTML = template.HTML(htmlLines[i])
}
}
data.Blines = append(data.Blines, Bline{})
}
data.LineC = fmt.Sprint(len(data.Blines), " lines")
if err := goit.Tmpl.ExecuteTemplate(w, "repo/blame", data); err != nil {
util.PrintFuncError(err)
}
}