dehub/cmd/git-http-server/main.go

160 lines
3.9 KiB
Go
Raw Normal View History

2020-03-27 02:25:54 +00:00
package main
import (
"bytes"
"errors"
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"path/filepath"
"strings"
"text/template"
"time"
"gitlab.com/golang-commonmark/markdown"
"gopkg.in/src-d/go-git.v4"
"gopkg.in/src-d/go-git.v4/plumbing"
"gopkg.in/src-d/go-git.v4/plumbing/object"
)
type handler struct {
repo *git.Repository
branch plumbing.ReferenceName
tpl *template.Template
}
func (h handler) getTree(r *http.Request) (*object.Tree, int, error) {
rev := plumbing.Revision(r.FormValue("rev"))
if rev == "" {
rev = plumbing.Revision(h.branch)
}
hashPtr, err := h.repo.ResolveRevision(rev)
if err != nil {
return nil, 404, fmt.Errorf("resolving revision %q: %w", rev, err)
}
hash := *hashPtr // I don't know why ResolveRevision returns a pointer
commit, err := h.repo.CommitObject(hash)
if err != nil {
return nil, 404, fmt.Errorf("retrieving commit for revision %q (%q): %w",
rev, hash, err)
}
tree, err := h.repo.TreeObject(commit.TreeHash)
if err != nil {
return nil, 500, fmt.Errorf("fetching tree %q of commit %q: %v",
commit.TreeHash, hash, err)
}
return tree, 0, nil
}
2020-03-27 02:25:54 +00:00
func (h handler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
path := r.URL.Path
var mdPath string
if strings.HasSuffix(path, "/") {
mdPath = filepath.Join(path, "README.md") // do before modifying path
path = filepath.Join(path, "index.html")
} else if strings.HasSuffix(path, "/index.html") {
mdPath = filepath.Join(filepath.Dir(path), "README.md")
} else if filepath.Ext(path) == ".html" {
mdPath = strings.TrimSuffix(path, ".html") + ".md"
}
path = strings.TrimPrefix(path, "/")
mdPath = strings.TrimPrefix(mdPath, "/")
tree, errStatusCode, err := h.getTree(r)
2020-03-27 02:25:54 +00:00
if err != nil {
http.Error(rw, err.Error(), errStatusCode)
2020-03-27 02:25:54 +00:00
return
}
var usingMD bool
f, err := tree.File(path)
if errors.Is(err, object.ErrFileNotFound) {
usingMD = true
f, err = tree.File(mdPath)
}
if errors.Is(err, object.ErrFileNotFound) {
http.Error(rw, fmt.Sprintf("%q not found", path), 404)
return
} else if err != nil {
log.Printf("fetching file %q / %q: %v", path, mdPath, err)
http.Error(rw, "internal error", 500)
return
}
fr, err := f.Blob.Reader()
if err != nil {
log.Printf("getting reader of file %q: %v", f.Name, err)
http.Error(rw, "internal error", 500)
return
}
defer fr.Close()
b, err := ioutil.ReadAll(fr)
if err != nil {
log.Printf("reading in contents of file %q: %v", f.Name, err)
http.Error(rw, "internal error", 500)
return
}
if !usingMD {
http.ServeContent(rw, r, filepath.Base(path), time.Now(), bytes.NewReader(b))
return
}
mdBuf := new(bytes.Buffer)
if err := markdown.New().Render(mdBuf, b); err != nil {
log.Printf("rendering file %q to markdown: %v", f.Name, err)
http.Error(rw, "internal error", 500)
return
}
if h.tpl == nil {
http.ServeContent(rw, r, filepath.Base(path), time.Now(), bytes.NewReader(mdBuf.Bytes()))
return
}
h.tpl.Execute(rw, struct {
Body string
}{mdBuf.String()})
}
func main() {
addr := flag.String("addr", ":8000", "Address to listen for http requests on")
branchName := flag.String("branch", "master", "git branch to serve the HEAD of")
repoPath := flag.String("repo-path", ".", "Path to the git repository to server")
tplPath := flag.String("tpl-path", "", "Path to an optional template file which can be used when rendering markdown")
flag.Parse()
repo, err := git.PlainOpen(*repoPath)
if err != nil {
log.Fatalf("opening git repo at path %q: %v", *repoPath, err)
}
branch := plumbing.NewBranchReferenceName(*branchName)
// do an initial check for the branch, for funsies
if _, err := repo.Reference(branch, true); err != nil {
log.Fatalf("resolving reference %q: %v", branch, err)
}
h := &handler{
repo: repo,
branch: branch,
}
if *tplPath != "" {
h.tpl = template.Must(template.ParseFiles(*tplPath))
}
log.Printf("listening on %q", *addr)
http.ListenAndServe(*addr, h)
}