2390197ae3
--- type: change message: |- implement basic git-http-server This isn't really part of dehub proper, but it's a useful utility that dehub projects are likely to want to use, so it's worth leaving it in, at least until it grows into its own thing. The utility starts a simple http server which serves files out of the HEAD of a specified branch. If an html file is requested, and there's a markdown version of that file, then the markdown will be compiled to html (optionally using an html template). README.md is used for index.html, if available. change_hash: AAonhlHUltpS+x8w8HHZRQb8e+RHJZpjoDtN2JTVGFBq credentials: - type: pgp_signature pub_key_id: 95C46FA6A41148AC body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl59ZC0ACgkQlcRvpqQRSKxWjQ/+P8CeaBipcM2CWYU2rJgUxNaHiFwpWNcKfJCdp0ZoypnBww4NyHYSM1578NoGukq2l1stSqqK6oWvN1SbYUd+v1YVmIRhCMbMthPPx1f6kPzStDichqYiPkAaIfIw0PUXpY24fkbHhe0tXC+UApEfgLvgEovHVhBjzbgsPhoMACG8Bgfl8mdwqmkXzGuHxxjpZnZLvL3B3q6nCfpGxUYpdQX5hYucV3+vv2obLnYNpPyaYwdLFcQF7a9xgin5eyWNPayyQ1LPDbt3V9ez0mAtCdqHm3KY3fhqz0YR0bU1rVY9eEMSeNaK8fPPmzDO3vlsE76G+tYuwTLaiZgIDCLy4Qm+VRsw1l35tOwybipWciLvKhCkeq1ohgKEcFZbl5Al4ZhUhj4TKN23gsKQzkt9TJlxAxSaNjkKsb4ZNxMhiKnq/THV2JfLAxCRJ+IK2hyu3wi3te8Q1baTYa9ZXxFN9m5V8r1JNJPUM7BASvCUEzWegNFfrvidCrMHzb7o5GhOETSGWkKejR7/KMn6n1/tvvrt9F2wbPYUJrPKjB8Y33b9PjLgvrQ/2nNOoj6UAeXWMv8N1oaAdjOJB4A8+/vhFTPpXlw3FPRn953LKspkHXck1ooUu1ea1rcOV+872ZVh2HsUn8qD42/IZZQ1K2+NhPaKuAtQnIQzOcZLb94lwA0= account: mediocregopher
153 lines
3.7 KiB
Go
153 lines
3.7 KiB
Go
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) 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, "/")
|
|
|
|
ref, err := h.repo.Reference(h.branch, true)
|
|
if errors.Is(err, plumbing.ErrReferenceNotFound) {
|
|
http.Error(rw, "branch does not exist", 404)
|
|
return
|
|
} else if err != nil {
|
|
log.Printf("resolving reference %q: %v", h.branch, err)
|
|
http.Error(rw, "internal error", 500)
|
|
return
|
|
}
|
|
|
|
hash := ref.Hash()
|
|
commit, err := h.repo.CommitObject(hash)
|
|
if err != nil {
|
|
log.Printf("fetching commit %q: %v", hash, err)
|
|
http.Error(rw, "internal error", 500)
|
|
return
|
|
}
|
|
|
|
tree, err := h.repo.TreeObject(commit.TreeHash)
|
|
if err != nil {
|
|
log.Printf("fetching tree %q of commit %q: %v", commit.TreeHash, hash, err)
|
|
http.Error(rw, "internal error", 500)
|
|
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)
|
|
}
|