summaryrefslogtreecommitdiff
path: root/src/edit
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@sbcglobal.net>2016-11-01 14:46:15 -0400
committerLuke Shumaker <lukeshu@sbcglobal.net>2016-11-01 14:46:15 -0400
commitc7eea383aeaf6748daf994e9e28e4d0c25350736 (patch)
treea0979cbd4e6be06385fd0340e050147fdacd6e35 /src/edit
initial commit
Diffstat (limited to 'src/edit')
-rw-r--r--src/edit/dir.go101
-rw-r--r--src/edit/git.go146
-rw-r--r--src/edit/main.go24
-rw-r--r--src/edit/util.go7
-rw-r--r--src/edit/views.go25
5 files changed, 303 insertions, 0 deletions
diff --git a/src/edit/dir.go b/src/edit/dir.go
new file mode 100644
index 0000000..2be52d5
--- /dev/null
+++ b/src/edit/dir.go
@@ -0,0 +1,101 @@
+package main
+
+import (
+ "fmt"
+ "time"
+ "net/http"
+ "path"
+ "strings"
+ "os/user"
+)
+
+func ServeGit(out http.ResponseWriter, in *http.Request) {
+ upath := in.URL.Path
+ if strings.HasPrefix(upath, "/") {
+ upath = "/" + upath
+ }
+ upath = path.Clean(upath)
+ upath = upath[1:]
+
+
+ errcheck(GitPull())
+ tree, err := GitLsTree()
+ errcheck(err)
+ file, fileExists := tree[upath]
+ if in.Method != http.MethodPut {
+ if !fileExists {
+ http.NotFound(out, in)
+ return
+ }
+ if file.Type == "tree" && !strings.HasSuffix(in.URL.Path, "/") {
+ out.Header().Set("Location", path.Base(upath) + "/")
+ out.WriteHeader(http.StatusMovedPermanently)
+ return
+ }
+ }
+
+ switch in.Method {
+ case http.MethodGet, http.MethodHead:
+ out.Header().Set("Content-Type", "text/html; charset=utf-8")
+ if file.Type == "tree" {
+ errcheck(renderViewTree(out, upath, tree))
+ } else {
+ errcheck(renderViewBlob(out, upath, file))
+ }
+ out.WriteHeader(http.StatusOK)
+ case http.MethodPut:
+ username := in.Header.Get("X-Nginx-User")
+ userinfo, err := user.Lookup(username)
+ errcheck(err)
+ msg := in.Header.Get("X-Commit-Message")
+ if msg == "" {
+ msg = fmt.Sprintf("web edit: create/modify %q", upath)
+ }
+ var content []byte // TODO
+ edit := Edit{
+ UserName: userinfo.Name,
+ UserEmail: username+"@edit.team4272.com",
+ Time: time.Now(),
+ Message: msg+"\n",
+ Files: map[string][]byte{
+ upath: content,
+ },
+ }
+ errcheck(GitCommit(edit))
+ errcheck(GitPush())
+ errcheck(renderModified(out, upath))
+ if fileExists {
+ out.WriteHeader(http.StatusOK)
+ } else {
+ out.WriteHeader(http.StatusCreated)
+ }
+ case http.MethodDelete:
+ username := in.Header.Get("X-Nginx-User")
+ userinfo, err := user.Lookup(username)
+ errcheck(err)
+ msg := in.Header.Get("X-Commit-Message")
+ if msg == "" {
+ msg = fmt.Sprintf("web edit: delete %q", upath)
+ }
+ edit := Edit{
+ UserName: userinfo.Name,
+ UserEmail: username+"@edit.team4272.com",
+ Time: time.Now(),
+ Message: msg+"\n",
+ Files: map[string][]byte{
+ upath: nil,
+ },
+ }
+ errcheck(GitCommit(edit))
+ errcheck(GitPush())
+ errcheck(renderDeleted(out, upath))
+ out.WriteHeader(http.StatusOK)
+ case http.MethodOptions:
+ // POST because PostHack
+ out.Header().Set("Allow", "GET, HEAD, PUT, POST, DELETE, OPTIONS")
+ out.WriteHeader(http.StatusOK)
+ default:
+ out.Header().Set("Allow", "GET, HEAD, PUT, POST, DELETE, OPTIONS")
+ out.WriteHeader(http.StatusMethodNotAllowed)
+ }
+}
diff --git a/src/edit/git.go b/src/edit/git.go
new file mode 100644
index 0000000..010c928
--- /dev/null
+++ b/src/edit/git.go
@@ -0,0 +1,146 @@
+package main
+
+import (
+ "strconv"
+ "io"
+ "bytes"
+ "fmt"
+ "os/exec"
+ "time"
+ "errors"
+)
+
+func GitPull() error {
+ return exec.Command("git", "pull").Run()
+}
+
+func GitPush() error {
+ return exec.Command("git", "push").Run()
+}
+
+type Edit struct {
+ UserName string
+ UserEmail string
+ Time time.Time
+ Message string
+ Files map[string][]byte
+}
+
+func gitTime(t time.Time) string {
+ return fmt.Sprintf("%d %s", t.Unix(), t.Format("-0700"))
+}
+
+func (edit Edit) WriteTo(w io.Writer) error {
+ var err error
+ commit := make([]string, len(edit.Files))
+ i := 0
+ for name, content := range edit.Files {
+ commit[i] = name
+ if content != nil {
+ if _, err = fmt.Fprintf(w, "blob\nmark :%d\ndata %d\n%s", i+1, len(content), content); err != nil {
+ return err
+ }
+ }
+ }
+ _, err = fmt.Fprintf(w, `commit HEAD
+author %s <%s> %s
+committer %s <%s> %s
+data %d
+%sfrom HEAD
+`,
+ edit.UserName, edit.UserEmail, gitTime(edit.Time),
+ edit.UserName, edit.UserEmail, gitTime(edit.Time),
+ len(edit.Message), edit.Message)
+ if err != nil {
+ return err
+ }
+ for i, filename := range commit {
+ if edit.Files[filename] != nil {
+ if _, err = fmt.Fprintf(w, "M 100644 :%d %s\n", i+1, filename); err != nil {
+ return err
+ }
+ } else {
+ if _, err = fmt.Fprintf(w, "D %s\n", filename); err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+
+func GitCommit(edit Edit) error {
+ cmd := exec.Command("git", "fast-import")
+ pip, err := cmd.StdinPipe()
+ if err != nil {
+ return err
+ }
+ if err = cmd.Start(); err != nil {
+ return err
+ }
+ werr := edit.WriteTo(pip)
+ if err = cmd.Wait(); err == nil {
+ err = werr
+ }
+ return err
+}
+
+type GitFile struct {
+ Mode int32 // 18 bits unsigned
+ Type string
+ Hash string
+ Size int64
+}
+
+type GitTree map[string]GitFile
+
+var ParseError = errors.New("git ls-tree parse error")
+
+func GitLsTree() (GitTree, error) {
+ data, err := exec.Command("git", "ls-tree", "-trlz", "HEAD").Output()
+ if err != nil {
+ return nil, err
+ }
+ lines := bytes.Split(data, []byte{0})
+ ret := make(GitTree, len(lines)-1)
+ for _, line := range lines[:len(ret)] {
+ // trim the trailing "\0"
+ if line[len(line)-1] != 0 {
+ return nil, ParseError
+ }
+ line = line[:len(line)-1]
+ // line = mode SP type SP hash SP size TAB name
+ a := bytes.SplitN(line, []byte{' '}, 4)
+ if len(a) != 4 {
+ return nil, ParseError
+ }
+ b := bytes.SplitN(a[3], []byte{'\t'}, 2)
+ if len(b) != 2 {
+ return nil, ParseError
+ }
+ fmode := a[0]
+ ftype := a[1]
+ fhash := a[2]
+ fsize := b[0]
+ fname := b[1]
+ fmodeN, err := strconv.ParseInt(string(fmode), 10, 19)
+ if err != nil {
+ return nil, err
+ }
+ fsizeN := int64(-1)
+ fsizeS := string(fsize)
+ if fsizeS != "-" {
+ fsizeN, err = strconv.ParseInt(fsizeS, 10, 64)
+ if err != nil {
+ return nil, err
+ }
+ }
+ ret[string(fname)] = GitFile{
+ Mode: int32(fmodeN),
+ Type: string(ftype),
+ Hash: string(fhash),
+ Size: fsizeN,
+ }
+ }
+ return ret, nil
+}
diff --git a/src/edit/main.go b/src/edit/main.go
new file mode 100644
index 0000000..2a640d0
--- /dev/null
+++ b/src/edit/main.go
@@ -0,0 +1,24 @@
+package main
+
+import (
+ "net/http"
+ "util"
+ "os"
+)
+
+func ServeIndex(out http.ResponseWriter, in *http.Request) {
+ if in.URL.Path != "/" {
+ http.NotFound(out, in)
+ }
+ http.Redirect(out, in, "/files/", http.StatusMovedPermanently)
+}
+
+func main() {
+ socket, err := util.StreamListener(os.Args[1], os.Args[2])
+ errcheck(err)
+ errcheck(os.Chdir("/srv/http/edit.team4272.com/www.git"))
+ http.Handle("/", util.SaneHTTPHandler{http.HandlerFunc(ServeIndex)})
+ http.Handle("/static/", util.SaneHTTPHandler{http.FileServer(http.Dir("static"))})
+ http.Handle("/files/", util.SaneHTTPHandler{http.StripPrefix("/files", http.HandlerFunc(ServeGit))})
+ errcheck(http.Serve(socket, nil))
+}
diff --git a/src/edit/util.go b/src/edit/util.go
new file mode 100644
index 0000000..519e46c
--- /dev/null
+++ b/src/edit/util.go
@@ -0,0 +1,7 @@
+package main
+
+func errcheck(err error) {
+ if err != nil {
+ panic(err)
+ }
+}
diff --git a/src/edit/views.go b/src/edit/views.go
new file mode 100644
index 0000000..90b6f1b
--- /dev/null
+++ b/src/edit/views.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+ "io"
+)
+
+func renderViewTree(w io.Writer, upath string, tree GitTree) error {
+ // TODO
+ return nil
+}
+
+func renderViewBlob(w io.Writer, upath string, file GitFile) error {
+ // TODO
+ return nil
+}
+
+func renderModified(w io.Writer, upath string) error {
+ // TODO
+ return nil
+}
+
+func renderDeleted(w io.Writer, upath string) error {
+ // TODO
+ return nil
+}