// 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) } }