a1579dcc96
--- type: change description: Switch markdown generator in git-http-server fingerprint: AFeZtdlslmnWYQjm/K45Ag9lby8A/QnudmYzQY53Fwd3 credentials: - type: pgp_signature pub_key_id: 95C46FA6A41148AC body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl7CB2YACgkQlcRvpqQRSKxm8A//dkhxvWYNtIgD67CpSA2Uu1n79P2uSfFyJN2GJLmuwBWLOH2yeD4FcmxzMacArQgcM1CEErT1IpfCaE3xLJQRAYrP5hWFZbsFaLyD+pm809cLtfXARMTEMFXfgOZJHoJe9kuWTx78cIawZxyS1lLh6JmpbPQSath4g7ZOKwt31LujsoI6c3I7rN29G15MAFPGsmuwnojuYRaDVrpUJksSBI7LTljcm/K/hmeG0KMsUNyn4VVJ7BvX8/iqaTyHrMRdgr/sBGac4zLAsaBMpwKFHgdNwoj8L4hCBJFG/m3IyMzXE4XtceBvLoWYevolusl5AAUSJVw2Y7Aaf8TzsZxeFRsyuZXmqyUE2xAD5sPxuwqP8D2c2a0z3gNhonTxcEUE2JB7qwe5tWjhYVw9MOLcJxfQFGnnvC0oIUY01uc5LGawPiwMgTYd7jGbwoMH+V/S/kLOo9UW2+xVxIVrZQKIVN++1+8bJA56+VnPPxRY41Nkys9xwIvhgo6//PkSzMRuaFccK2OcvzjXJ6LcyVwf9p+PXMagjNcQxvM+ByY1wVUuVZnCsTc5OdECSj4zVlFM6USkvN6s1p1JBEoQs45ESx42nJ//gmTCpRbOvj28l+JjrlC+82gcITXNGoulrgeTNwCym1iYXzCUIggUldFuWTx1UfCMkoePzTgHBMNZF7g= account: mediocregopher
155 lines
3.7 KiB
Go
155 lines
3.7 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"path/filepath"
|
|
"strings"
|
|
"text/template"
|
|
"time"
|
|
|
|
"github.com/gomarkdown/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
|
|
}
|
|
|
|
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)
|
|
if err != nil {
|
|
http.Error(rw, err.Error(), errStatusCode)
|
|
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
|
|
}
|
|
|
|
mdHTML := markdown.ToHTML(b, nil, nil)
|
|
|
|
if h.tpl == nil {
|
|
http.ServeContent(rw, r, filepath.Base(path), time.Now(), bytes.NewReader(mdHTML))
|
|
return
|
|
}
|
|
|
|
h.tpl.Execute(rw, struct {
|
|
Body string
|
|
}{string(mdHTML)})
|
|
}
|
|
|
|
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)
|
|
}
|