package main
import (
"bytes"
"html/template"
"io"
"log"
"net/http"
"strings"
"time"
"github.com/libgit2/git2go"
"github.com/russross/blackfriday"
)
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
commitCount int
branches []*git.Branch
contributors []*git.Signature
}
func NewFancyRepo(repo *git.Repository) *FancyRepo {
return &FancyRepo{repo: repo, commitCount: -1}
}
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 = NewFancyCommit(commit)
}
return fr.commit, nil
}
func (fr *FancyRepo) CommitCount() (int, error) {
if fr.commitCount < 0 {
walk, err := fr.repo.Walk()
if err != nil {
return -1, err
}
head, err := fr.repo.Head()
if err != nil {
return -1, err
}
err = walk.Push(head.Target())
if err != nil {
return -1, err
}
c := 0
err = walk.Iterate(func(commit *git.Commit) bool {
c += 1
return true
})
if err != nil {
return -1, err
}
fr.commitCount = c
}
return fr.commitCount, nil
}
func (fr *FancyRepo) Contributors() ([]*git.Signature, error) {
if fr.contributors == nil {
walk, err := fr.repo.Walk()
if err != nil {
return nil, err
}
head, err := fr.repo.Head()
if err != nil {
return nil, err
}
err = walk.Push(head.Target())
if err != nil {
return nil, err
}
authors := make(map[string]*git.Signature)
err = walk.Iterate(func(commit *git.Commit) bool {
authors[commit.Author().Name] = commit.Author()
return true
})
if err != nil {
return nil, err
}
for _, author := range authors {
fr.contributors = append(fr.contributors, author)
}
}
return fr.contributors, nil
}
func (fr *FancyRepo) Branches() ([]*git.Branch, error) {
if fr.branches == nil {
it, err := fr.repo.NewBranchIterator(git.BranchRemote)
if err != nil {
return nil, err
}
err = it.ForEach(func(b *git.Branch, bt git.BranchType) error {
fr.branches = append(fr.branches, b)
return nil
})
if err != nil {
return nil, err
}
}
return fr.branches, nil
}
func (fr *FancyRepo) Tags() ([]string, error) {
return fr.repo.Tags.List()
}
type FancyCommit struct {
commit *git.Commit
files []*FancyFile
filesCache map[string]*FancyFile
}
func NewFancyCommit(commit *git.Commit) *FancyCommit {
return &FancyCommit{
commit: commit,
filesCache: make(map[string]*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, NewFancyFile(fc.commit.Owner(), entry))
return 1
})
if err != nil {
return nil, err
}
}
return fc.files, nil
}
var readmeNames = []string{"README.md", "readme.md", "README.rst", "readme.rst", "README.txt", "readme.txt", "README"}
func (fc *FancyCommit) Readme() (*FancyFile, error) {
readme := fc.filesCache["|||README|||"]
if readme == nil {
tree, err := fc.commit.Tree()
if err != nil {
return nil, err
}
var entry *git.TreeEntry
for _, n := range readmeNames {
entry, err = tree.EntryByPath(n)
if err == nil {
break
}
}
if entry == nil {
return nil, nil
}
readme = NewFancyFile(fc.commit.Owner(), entry)
fc.filesCache["|||README|||"] = readme
}
return readme, nil
}
type FancyFile struct {
repo *git.Repository
entry *git.TreeEntry
commit *FancyCommit
}
func NewFancyFile(repo *git.Repository, entry *git.TreeEntry) *FancyFile {
return &FancyFile{repo: repo, entry: entry}
}
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 = NewFancyCommit(commit)
}
return fc.commit, nil
}
func (fc *FancyFile) Type() string {
return strings.ToLower(fc.entry.Type.String())
}
func (fc *FancyFile) RawContents() (string, error) {
blob, err := fc.repo.LookupBlob(fc.entry.Id)
if err != nil {
return "", err
}
return string(blob.Contents()), nil
}
func (fc *FancyFile) Contents() (template.HTML, error) {
blob, err := fc.repo.LookupBlob(fc.entry.Id)
if err != nil {
return "", err
}
buf := new(bytes.Buffer)
contents := blob.Contents()
if strings.HasSuffix(fc.entry.Name, ".md") {
buf.Write(blackfriday.MarkdownCommon(contents))
} else {
buf.WriteString("")
}
return template.HTML(buf.String()), nil
}
var repoTmpl = template.Must(template.New("").Parse(`
")
template.HTMLEscape(buf, contents)
buf.WriteString("
| {{ $file.Name }} |