97f81af03f521ebc51c64ec84a8649e28d7fa8a0 / internal/web/commits.go · 3622 bytes · raw
package web
import (
"html/template"
"net/http"
"regexp"
"strings"
"git.josie-c.com/josie/simplegit/internal/git"
"git.josie-c.com/josie/simplegit/internal/render"
)
const commitsPerPage = 50
var shaPattern = regexp.MustCompile(`^[0-9a-fA-F]{4,40}$`)
type commitRow struct {
SHA string // short, 7 chars
Href string
Subject string
Author string
Date string // trimmed ISO timestamp
}
type commitsData struct {
navData
Ref string
Commits []commitRow
}
type commitData struct {
navData
SHA string
Subject string
Body string
Author string
Email string
Date string
Parents []commitRow
TreeHref string
DiffHTML template.HTML
Notice string
}
func (s *Server) handleCommits(w http.ResponseWriter, r *http.Request) {
repo, repoPath, user, ok := s.resolveRepo(w, r)
if !ok {
return
}
owner := r.PathValue("user")
ref := strings.Trim(r.PathValue("ref"), "/")
if ref == "" {
ref = repo.DefaultBranch
}
if !validRef(ref) {
http.NotFound(w, r)
return
}
exists, err := git.RefExists(repoPath, ref)
if err != nil {
s.internalError(w, r, err)
return
}
if !exists {
http.NotFound(w, r)
return
}
base := "/" + owner + "/" + repo.Name
log, err := git.Log(repoPath, ref, commitsPerPage)
if err != nil {
s.internalError(w, r, err)
return
}
data := commitsData{navData: nav(r, repo, user, "code"), Ref: ref}
for _, c := range log {
data.Commits = append(data.Commits, commitRow{
SHA: shortSHA(c.SHA),
Href: base + "/commit/" + c.SHA,
Subject: c.Subject,
Author: c.Author,
Date: trimDate(c.Date),
})
}
s.render(w, "commits.html", http.StatusOK, data)
}
func (s *Server) handleCommit(w http.ResponseWriter, r *http.Request) {
repo, repoPath, user, ok := s.resolveRepo(w, r)
if !ok {
return
}
owner := r.PathValue("user")
sha := r.PathValue("sha")
if !shaPattern.MatchString(sha) {
http.NotFound(w, r)
return
}
base := "/" + owner + "/" + repo.Name
c, err := git.CommitAt(repoPath, sha)
if err != nil {
http.NotFound(w, r)
return
}
data := commitData{
navData: nav(r, repo, user, "code"),
SHA: c.SHA, Subject: c.Subject,
Body: c.Body, Author: c.Author, Email: c.Email, Date: trimDate(c.Date),
TreeHref: base + "/tree/" + c.SHA + "/",
}
for _, p := range c.Parents {
if pc, err := git.CommitAt(repoPath, p); err == nil {
data.Parents = append(data.Parents, commitRow{
SHA: shortSHA(p), Href: base + "/commit/" + p, Subject: pc.Subject,
})
} else {
data.Parents = append(data.Parents, commitRow{SHA: shortSHA(p), Href: base + "/commit/" + p})
}
}
patch, err := git.ShowPatch(repoPath, c.SHA, maxDiffBytes)
if err != nil {
s.internalError(w, r, err)
return
}
if len(patch) > 0 {
data.DiffHTML, data.Notice = renderDiffHTML(patch)
}
s.render(w, "commit.html", http.StatusOK, data)
}
// renderDiffHTML caps a patch at maxDiffBytes and renders it highlighted,
// falling back to an escaped <pre>; the notice is set when truncated.
// Shared by the commit view and the PR diff.
func renderDiffHTML(patch []byte) (template.HTML, string) {
notice := ""
if len(patch) > maxDiffBytes {
patch = patch[:maxDiffBytes]
notice = diffTruncatedNotice
}
if html, handled, err := render.CodeHTML(diffLexeme, string(patch)); err == nil && handled {
return template.HTML(html), notice
}
return template.HTML("<pre>" + template.HTMLEscapeString(string(patch)) + "</pre>"), notice
}
func shortSHA(sha string) string {
return sha[:min(len(sha), 7)]
}
func trimDate(iso string) string {
if len(iso) >= 16 {
return iso[:16]
}
return iso
}