diff options
author | Luke Shumaker <lukeshu@sbcglobal.net> | 2016-11-01 14:46:15 -0400 |
---|---|---|
committer | Luke Shumaker <lukeshu@sbcglobal.net> | 2016-11-01 14:46:15 -0400 |
commit | c7eea383aeaf6748daf994e9e28e4d0c25350736 (patch) | |
tree | a0979cbd4e6be06385fd0340e050147fdacd6e35 /src/edit |
initial commit
Diffstat (limited to 'src/edit')
-rw-r--r-- | src/edit/dir.go | 101 | ||||
-rw-r--r-- | src/edit/git.go | 146 | ||||
-rw-r--r-- | src/edit/main.go | 24 | ||||
-rw-r--r-- | src/edit/util.go | 7 | ||||
-rw-r--r-- | src/edit/views.go | 25 |
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 +} |