Goit

Simple and lightweight Git web server
git clone http://git.omkov.net/Goit
Log | Tree | Refs | README | Download

Goit/src/repo/commit.go (159 lines, 4.2 KiB) -rw-r--r-- blame download

0123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
// Copyright (C) 2023, Jakob Wakeling
// All rights reserved.

package repo

import (
	"errors"
	"fmt"
	"html/template"
	"log"
	"net/http"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/Jamozed/Goit/src/goit"
	"github.com/buildkite/terminal-to-html/v3"
	"github.com/go-chi/chi/v5"
	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/plumbing"
)

func HandleCommit(w http.ResponseWriter, r *http.Request) {
	auth, user, err := goit.Auth(w, r, true)
	if err != nil {
		log.Println("[/repo/commit]", err.Error())
		goit.HttpError(w, http.StatusInternalServerError)
	}

	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 stat struct {
		Name, Path, Status, Num, Plusses, Minuses string
		IsBinary                                  bool
	}

	data := struct {
		HeaderFields
		Title                       string
		Author, Date, Commit        string
		Parents                     []string
		MessageSubject, MessageBody string
		Stats                       []stat
		Summary                     string
		Diff                        template.HTML
	}{
		Title:        repo.Name + " - Log",
		HeaderFields: GetHeaderFields(auth, user, repo, r.Host),
	}

	gr, err := git.PlainOpen(goit.RepoPath(repo.Name, true))
	if err != nil {
		log.Println("[/repo/commit]", err.Error())
		goit.HttpError(w, http.StatusInternalServerError)
		return
	}

	ref, err := gr.Head()
	if err != nil {
		if !errors.Is(err, plumbing.ErrReferenceNotFound) {
			log.Println("[/repo/log]", err.Error())
			goit.HttpError(w, http.StatusInternalServerError)
			return
		}
	} else {
		if readme, _ := findPattern(gr, ref, readmePattern); readme != "" {
			data.Readme = filepath.Join("/", repo.Name, "file", readme)
		}
		if licence, _ := findPattern(gr, ref, licencePattern); licence != "" {
			data.Licence = filepath.Join("/", repo.Name, "file", licence)
		}
	}

	commit, err := gr.CommitObject(plumbing.NewHash(chi.URLParam(r, "hash")))
	if errors.Is(err, plumbing.ErrObjectNotFound) {
		goit.HttpError(w, http.StatusNotFound)
		return
	} else if err != nil {
		log.Println("[/repo/commit]", err.Error())
		goit.HttpError(w, http.StatusInternalServerError)
		return
	}

	data.Author = commit.Author.String()
	data.Date = commit.Author.When.UTC().Format(time.DateTime)
	data.Commit = commit.Hash.String()

	for _, h := range commit.ParentHashes {
		data.Parents = append(data.Parents, h.String())
	}

	data.MessageSubject, data.MessageBody, _ = strings.Cut(commit.Message, "\n")

	st, err := goit.DiffStats(commit)
	if err != nil {
		log.Println("[/repo/commit]", err.Error())
		goit.HttpError(w, http.StatusInternalServerError)
		return
	}

	var files, additions, deletions int = len(st), 0, 0
	for _, s := range st {
		f := stat{Name: s.Name, Path: s.Name, Status: s.Status}
		f.Num = strconv.FormatInt(int64(s.Addition+s.Deletion), 10)

		if s.Addition+s.Deletion > 80 {
			f.Plusses = strings.Repeat("+", (s.Addition*80)/(s.Addition+s.Deletion))
			f.Minuses = strings.Repeat("-", (s.Deletion*80)/(s.Addition+s.Deletion))
		} else {
			f.Plusses = strings.Repeat("+", s.Addition)
			f.Minuses = strings.Repeat("-", s.Deletion)
		}

		if s.Status == "R" {
			f.Name = s.Prev + " -> " + s.Name
		}
		if s.IsBinary {
			f.IsBinary = true
		}

		data.Stats = append(data.Stats, f)

		additions += s.Addition
		deletions += s.Deletion
	}

	data.Summary = fmt.Sprintf("%d files changed, %d insertions, %d deletions", files, additions, deletions)

	var phash string
	if commit.NumParents() > 0 {
		phash = commit.ParentHashes[0].String()
	} else {
		phash = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"
	}

	c := goit.NewGitCommand("diff", "--color=always", "-p", phash, commit.Hash.String())
	c.Dir = goit.RepoPath(repo.Name, true)
	out, _, err := c.Run(nil, nil)
	if err != nil {
		log.Println("[/repo/commit]", err.Error())
		goit.HttpError(w, http.StatusInternalServerError)
		return
	}

	data.Diff = template.HTML(terminal.Render(out))

	if err := goit.Tmpl.ExecuteTemplate(w, "repo/commit", data); err != nil {
		log.Println("[/repo/commit]", err.Error())
	}
}