|
package main
import (
"bytes"
"html/template"
"io"
"log"
"net/http"
"strings"
"time"
"github.com/libgit2/git2go"
)
func main() {
repo, err := git.OpenRepository("/home/lu/t/libgit2")
if err != nil {
log.Fatal("opening repository: ", err)
}
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
buf := new(bytes.Buffer)
err = repoTmpl.Execute(buf, map[string]interface{}{
"RepoPath": "~/t/libgit2",
"Repo": NewFancyRepo(repo),
"Style": template.CSS(repoStyle),
})
if err != nil {
log.Println("rendering repo: ", err)
http.Error(w, "internal server error", http.StatusInternalServerError)
return
}
io.Copy(w, buf)
})
log.Fatal(http.ListenAndServe("localhost:12345", nil))
}
type FancyRepo struct {
repo *git.Repository
commit *FancyCommit
}
func NewFancyRepo(repo *git.Repository) *FancyRepo {
return &FancyRepo{repo: repo}
}
func (fr *FancyRepo) Commit() (*FancyCommit, error) {
if fr.commit == nil {
head, err := fr.repo.Head()
if err != nil {
return nil, err
}
commit, err := fr.repo.LookupCommit(head.Target())
if err != nil {
return nil, err
}
fr.commit = &FancyCommit{commit: commit}
}
return fr.commit, nil
}
type FancyCommit struct {
commit *git.Commit
files []*FancyFile
}
func (fc *FancyCommit) Author() string {
return fc.commit.Author().Name
}
func (fc *FancyCommit) Date() time.Time {
return fc.commit.Author().When
}
func (fc *FancyCommit) Id(n int) string {
if n == 0 {
return fc.commit.TreeId().String()
}
return fc.commit.TreeId().String()[:n]
}
func (fc *FancyCommit) Summary() string {
return fc.commit.Summary()
}
func (fc *FancyCommit) Description() string {
return fc.commit.Message()
}
func (fc *FancyCommit) Files() ([]*FancyFile, error) {
if fc.files == nil {
tree, err := fc.commit.Tree()
if err != nil {
return nil, err
}
err = tree.Walk(func(s string, entry *git.TreeEntry) int {
fc.files = append(fc.files, &FancyFile{
repo: fc.commit.Owner(),
entry: entry,
})
return 1
})
if err != nil {
return nil, err
}
}
return fc.files, nil
}
type FancyFile struct {
repo *git.Repository
entry *git.TreeEntry
commit *FancyCommit
}
func (fc *FancyFile) Name() string {
return fc.entry.Name
}
func (fc *FancyFile) Commit() (*FancyCommit, error) {
if fc.commit == nil {
commit, err := fc.repo.LookupCommit(fc.entry.Id)
if err != nil {
return nil, err
}
fc.commit = &FancyCommit{commit: commit}
}
return fc.commit, nil
}
func (fc *FancyFile) Type() string {
return strings.ToLower(fc.entry.Type.String())
}
var repoTmpl = template.Must(template.New("").Parse(`<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>{{ .RepoPath }}</title>
<style>{{ .Style }}</style>
</head>
<body>
<section id="commit" class="commit-info">
<div class="commit-author">{{ .Repo.Commit.Author }}</div>
<div class="commit-summary" title="{{ .Repo.Commit.Description }}">{{ .Repo.Commit.Summary }}</div>
<div class="commit-id" data-commit="{{ .Repo.Commit.Id 0 }}"><a href="#">{{ .Repo.Commit.Id 10 }}</a></div>
<time class="commit-date">{{ .Repo.Commit.Date }}</time>
</section>
<table id="files">
{{ range $file := .Repo.Commit.Files }}
<tr class="file">
<td class="file-name file-type-{{ $file.Type }}">{{ $file.Name }}</td>
</tr>
{{ end }}
</body>
</html>
`))
var repoStyle = `
* {
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
}
#commit {
display: flex;
justify-content: space-between;
align-items: baseline;
padding: 0.6em 0.5em;
width: 70vw;
background-color: rgba(200, 0, 255, 0.15);
}
#commit .commit-author {
font-weight: 600;
}
#commit .commit-id {
font-family: monospace;
}
#files td {
border: 1px solid #333;
border-radius: 2px;
margin-bottom: 0;
padding: 0.2em 0.5em;
font-size: 95%;
}
#files tr:hover {
background-color: #efefef;
}
#files {
width: 70vw;
}
.file-name {
color: #00445a;
}
.file-type-blob:before {
content:"🖹";
padding-right: 0.5em;
}
.file-type-tree:before {
content: "🗀";
padding-right: 0.2em;
}
.file-type-tree:after {
content: "/";
}
`
|