git.go

 1package main
 2
 3import (
 4	"context"
 5	"fmt"
 6	"os"
 7	"os/exec"
 8
 9	"github.com/gliderlabs/ssh"
10)
11
12func GitMiddleware(repoDir string) Middleware {
13	return func(sh ssh.Handler) ssh.Handler {
14		return func(s ssh.Session) {
15			cmd := s.Command()
16			if len(cmd) == 2 {
17				switch cmd[0] {
18				case "git-upload-pack", "git-receive-pack", "git-upload-archive":
19					r := cmd[1]
20					rp := fmt.Sprintf("%s%s", repoDir, r)
21					ctx := s.Context()
22					err := ensureRepo(ctx, repoDir, r)
23					if err != nil {
24						fatalGit(s, err)
25						break
26					}
27					c := exec.CommandContext(ctx, cmd[0], rp)
28					c.Dir = "./"
29					c.Stdout = s
30					c.Stdin = s
31					err = c.Run()
32					if err != nil {
33						fatalGit(s, err)
34						break
35					}
36				}
37			}
38			sh(s)
39		}
40	}
41}
42
43func fileExists(path string) (bool, error) {
44	_, err := os.Stat(path)
45	if err == nil {
46		return true, nil
47	}
48	if os.IsNotExist(err) {
49		return false, nil
50	}
51	return true, err
52}
53
54func fatalGit(s ssh.Session, err error) {
55	// hex length includes 4 byte length prefix and ending newline
56	logError(s, err)
57	msg := err.Error()
58	pktLine := fmt.Sprintf("%04x%s\n", len(msg)+5, msg)
59	_, err = s.Write([]byte(pktLine))
60	if err != nil {
61		logError(s, err)
62	}
63	s.Exit(1)
64}
65
66func ensureRepo(ctx context.Context, dir string, repo string) error {
67	exists, err := fileExists(dir)
68	if err != nil {
69		return err
70	}
71	if !exists {
72		err = os.MkdirAll(dir, os.ModeDir|os.FileMode(0700))
73		if err != nil {
74			return err
75		}
76	}
77	rp := fmt.Sprintf("%s%s", dir, repo)
78	exists, err = fileExists(rp)
79	if err != nil {
80		return err
81	}
82	if !exists {
83		c := exec.CommandContext(ctx, "git", "init", "--bare", rp)
84		err = c.Run()
85		if err != nil {
86			return err
87		}
88	}
89	return nil
90}