- commit
- 8b876e0
- parent
- 584e21b
- author
- Eric Bower
- date
- 2024-05-05 11:09:35 -0400 EDT
progress
7 files changed,
+215,
-264
M
Makefile
+4,
-0
1@@ -3,6 +3,10 @@ fmt:
2 deno fmt README.md
3 .PHONY: fmt
4
5+lint:
6+ golangci-lint run -E goimports -E godot --timeout 10m
7+.PHONY: lint
8+
9 build:
10 go build -o ./build/git ./cmd/git
11 .PHONY: build
+0,
-2
1@@ -6,8 +6,6 @@ import (
2
3 "github.com/charmbracelet/ssh"
4 gossh "golang.org/x/crypto/ssh"
5- // ssgit "github.com/charmbracelet/soft-serve/git"
6- // "github.com/charmbracelet/soft-serve/pkg/utils"
7 )
8
9 type Backend struct {
M
db.go
+43,
-1
1@@ -2,11 +2,53 @@ package git
2
3 import (
4 "log/slog"
5+ "time"
6
7 "github.com/jmoiron/sqlx"
8- _ "modernc.org/sqlite" // sqlite driver
9+ _ "modernc.org/sqlite"
10 )
11
12+// PatchRequest is a database model for patches submitted to a Repo
13+type PatchRequest struct {
14+ ID int64 `db:"id"`
15+ Pubkey string `db:"pubkey"`
16+ RepoID int64 `db:"repo_id"`
17+ Name string `db:"name"`
18+ Text string `db:"text"`
19+ CreatedAt time.Time `db:"created_at"`
20+ UpdatedAt time.Time `db:"updated_at"`
21+}
22+
23+// Patch is a database model for a single entry in a patchset
24+// This usually corresponds to a git commit.
25+type Patch struct {
26+ ID int64 `db:"id"`
27+ Pubkey string `db:"pubkey"`
28+ PatchRequestID int64 `db:"patch_request_id"`
29+ AuthorName string `db:"author_name"`
30+ AuthorEmail string `db:"author_email"`
31+ Title string `db:"title"`
32+ Body string `db:"body"`
33+ CommitSha string `db:"commit_sha"`
34+ CommitDate time.Time `db:"commit_date"`
35+ Review bool `db:"review"`
36+ RawText string `db:"raw_text"`
37+ CreatedAt time.Time `db:"created_at"`
38+}
39+
40+// Comment is a database model for a non-patch comment within a PatchRequest
41+type Comment struct {
42+ ID int64 `db:"id"`
43+ Pubkey string `db:"pubkey"`
44+ PatchRequestID int64 `db:"patch_request_id"`
45+ Text string `db:"text"`
46+ CreatedAt time.Time `db:"created_at"`
47+ UpdatedAt time.Time `db:"updated_at"`
48+}
49+
50+type GitDB interface {
51+}
52+
53 // DB is the interface for a pico/git database.
54 type DB struct {
55 *sqlx.DB
M
mdw.go
+167,
-120
1@@ -7,7 +7,9 @@ import (
2 "io"
3 "os"
4 "path/filepath"
5+ "regexp"
6 "strconv"
7+ "strings"
8 "time"
9
10 "github.com/bluekeyes/go-gitdiff/gitdiff"
11@@ -63,7 +65,136 @@ func flagSet(sesh ssh.Session, cmdName string) *flag.FlagSet {
12 return cmd
13 }
14
15-func GitServerMiddleware(be *Backend) wish.Middleware {
16+type PrCmd struct {
17+ Session ssh.Session
18+ Backend *Backend
19+ Repo string
20+ Pubkey string
21+}
22+
23+func (pr *PrCmd) PrintPatches(prID int64) {
24+ patches := []*Patch{}
25+ pr.Backend.DB.Select(
26+ &patches,
27+ "SELECT * FROM patches WHERE patch_request_id=?",
28+ prID,
29+ )
30+ if len(patches) == 0 {
31+ wish.Printf(pr.Session, "no patches found for Patch Request ID: %d\n", prID)
32+ return
33+ }
34+
35+ if len(patches) == 1 {
36+ wish.Println(pr.Session, patches[0].RawText)
37+ return
38+ }
39+
40+ for _, patch := range patches {
41+ wish.Printf(pr.Session, "%s\n\n\n", patch.RawText)
42+ }
43+}
44+
45+func (cmd *PrCmd) SubmitPatch(prID int64) {
46+ pr := PatchRequest{}
47+ cmd.Backend.DB.Get(&pr, "SELECT * FROM patch_requests WHERE id=?", prID)
48+ if pr.ID == 0 {
49+ wish.Fatalln(cmd.Session, "patch request does not exist")
50+ return
51+ }
52+
53+ review := false
54+ if pr.Pubkey != cmd.Pubkey {
55+ review = true
56+ }
57+
58+ // need to read io.Reader from session twice
59+ var buf bytes.Buffer
60+ tee := io.TeeReader(cmd.Session, &buf)
61+
62+ _, preamble, err := gitdiff.Parse(tee)
63+ try(cmd.Session, err)
64+ header, err := gitdiff.ParsePatchHeader(preamble)
65+ try(cmd.Session, err)
66+
67+ _, err = cmd.Backend.DB.Exec(
68+ "INSERT INTO patches (pubkey, patch_request_id, author_name, author_email, title, body, commit_sha, commit_date, review, raw_text) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
69+ cmd.Pubkey,
70+ prID,
71+ header.Author.Name,
72+ header.Author.Email,
73+ header.Title,
74+ header.Body,
75+ header.SHA,
76+ header.CommitterDate,
77+ review,
78+ buf.String(),
79+ )
80+ try(cmd.Session, err)
81+
82+ wish.Printf(cmd.Session, "submitted review!\n")
83+}
84+
85+func (cmd *PrCmd) SubmitPatchRequest(repoName string) {
86+ err := git.EnsureWithin(cmd.Backend.ReposDir(), cmd.Backend.RepoName(repoName))
87+ try(cmd.Session, err)
88+ _, err = os.Stat(filepath.Join(cmd.Backend.ReposDir(), cmd.Backend.RepoName(repoName)))
89+ if os.IsNotExist(err) {
90+ wish.Fatalln(cmd.Session, "repo does not exist")
91+ return
92+ }
93+
94+ // need to read io.Reader from session twice
95+ var buf bytes.Buffer
96+ tee := io.TeeReader(cmd.Session, &buf)
97+
98+ _, preamble, err := gitdiff.Parse(tee)
99+ try(cmd.Session, err)
100+ header, err := gitdiff.ParsePatchHeader(preamble)
101+ try(cmd.Session, err)
102+ prName := header.Title
103+ prDesc := header.Body
104+
105+ var prID int64
106+ row := cmd.Backend.DB.QueryRow(
107+ "INSERT INTO patch_requests (pubkey, repo_id, name, text, updated_at) VALUES(?, ?, ?, ?, ?) RETURNING id",
108+ cmd.Pubkey,
109+ repoName,
110+ prName,
111+ prDesc,
112+ time.Now(),
113+ )
114+ row.Scan(&prID)
115+ if prID == 0 {
116+ wish.Fatal(cmd.Session, "could not create patch request")
117+ return
118+ }
119+ try(cmd.Session, err)
120+
121+ _, err = cmd.Backend.DB.Exec(
122+ "INSERT INTO patches (pubkey, patch_request_id, author_name, author_email, title, body, commit_sha, commit_date, raw_text) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
123+ cmd.Pubkey,
124+ prID,
125+ header.Author.Name,
126+ header.Author.Email,
127+ header.Title,
128+ header.Body,
129+ header.SHA,
130+ header.CommitterDate,
131+ buf.String(),
132+ )
133+ try(cmd.Session, err)
134+
135+ wish.Printf(
136+ cmd.Session,
137+ "created patch request!\nID: %d\nTitle: %s\n",
138+ prID,
139+ prName,
140+ )
141+}
142+
143+func GitPatchRequestMiddleware(be *Backend) wish.Middleware {
144+ isNumRe := regexp.MustCompile(`^\d+$`)
145+
146 return func(next ssh.Handler) ssh.Handler {
147 return func(sesh ssh.Session) {
148 pubkey := be.Pubkey(sesh.PublicKey())
149@@ -88,131 +219,47 @@ func GitServerMiddleware(be *Backend) wish.Middleware {
150 }
151 }
152 } else if cmd == "pr" {
153- prCmd := flagSet(sesh, "pr")
154- repo := prCmd.String("repo", "", "repository to target")
155- out := prCmd.Bool("stdout", false, "print patchset to stdout")
156- id := prCmd.Int("id", 0, "patch request id")
157+ // PATCH REQUEST STATUS:
158+ // APPROVED
159+ // CLOSED
160+ // REVIEWED
161
162- repoName := utils.SanitizeRepo(*repo)
163+ // ssh git.sh ls
164+ // git format-patch --stdout | ssh git.sh pr test
165+ // git format-patch --stdout | ssh git.sh pr 123
166+ // ssh git.sh pr ls
167+ // ssh git.sh pr 123 --approve
168+ // ssh git.sh pr 123 --close
169+ // ssh git.sh pr 123 --stdout | git am -3
170+ // echo "here is a comment" | ssh git.sh pr 123 --comment
171
172- if *out == true {
173- prID, err := strconv.ParseInt(args[2], 10, 64)
174- try(sesh, err)
175-
176- patches := []*Patch{}
177- be.DB.Select(
178- &patches,
179- "SELECT * FROM patches WHERE patch_request_id=?",
180- prID,
181- )
182- if len(patches) == 0 {
183- wish.Printf(sesh, "no patches found for Patch Request ID: %d\n", prID)
184- return
185- }
186-
187- if len(patches) == 1 {
188- wish.Println(sesh, patches[0].RawText)
189- return
190- }
191-
192- for _, patch := range patches {
193- wish.Printf(sesh, "%s\n\n\n", patch.RawText)
194- }
195- } else if *id != 0 {
196- prID := *id
197- pr := PatchRequest{}
198- be.DB.Get(&pr, "SELECT * FROM patch_requests WHERE id=?", prID)
199- if pr.ID == 0 {
200- wish.Fatalln(sesh, "patch request does not exist")
201- return
202- }
203-
204- review := false
205- if pr.Pubkey != pubkey {
206- review = true
207- }
208-
209- // need to read io.Reader from session twice
210- var buf bytes.Buffer
211- tee := io.TeeReader(sesh, &buf)
212-
213- _, preamble, err := gitdiff.Parse(tee)
214- try(sesh, err)
215- header, err := gitdiff.ParsePatchHeader(preamble)
216- try(sesh, err)
217-
218- _, err = be.DB.Exec(
219- "INSERT INTO patches (pubkey, patch_request_id, author_name, author_email, title, body, commit_sha, commit_date, review, raw_text) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
220- pubkey,
221- prID,
222- header.Author.Name,
223- header.Author.Email,
224- header.Title,
225- header.Body,
226- header.SHA,
227- header.CommitterDate,
228- review,
229- buf.String(),
230- )
231+ prCmd := flagSet(sesh, "pr")
232+ subCmd := strings.TrimSpace(args[2])
233+ repoName := ""
234+ var prID int64
235+ var err error
236+ if isNumRe.MatchString(subCmd) {
237+ prID, err = strconv.ParseInt(args[2], 10, 64)
238 try(sesh, err)
239-
240- wish.Printf(sesh, "submitted review!\n")
241 } else {
242- err := git.EnsureWithin(be.ReposDir(), be.RepoName(repoName))
243- try(sesh, err)
244- _, err = os.Stat(filepath.Join(be.ReposDir(), be.RepoName(repoName)))
245- if os.IsNotExist(err) {
246- wish.Fatalln(sesh, "repo does not exist")
247- return
248- }
249-
250- // need to read io.Reader from session twice
251- var buf bytes.Buffer
252- tee := io.TeeReader(sesh, &buf)
253-
254- _, preamble, err := gitdiff.Parse(tee)
255- try(sesh, err)
256- header, err := gitdiff.ParsePatchHeader(preamble)
257- try(sesh, err)
258- prName := header.Title
259- prDesc := header.Body
260-
261- var prID int64
262- row := be.DB.QueryRow(
263- "INSERT INTO patch_requests (pubkey, repo_id, name, text, updated_at) VALUES(?, ?, ?, ?, ?) RETURNING id",
264- pubkey,
265- repoName,
266- prName,
267- prDesc,
268- time.Now(),
269- )
270- row.Scan(&prID)
271- if prID == 0 {
272- wish.Fatal(sesh, "could not create patch request")
273- return
274- }
275- try(sesh, err)
276+ repoName = utils.SanitizeRepo(subCmd)
277+ }
278+ out := prCmd.Bool("stdout", false, "print patchset to stdout")
279
280- _, err = be.DB.Exec(
281- "INSERT INTO patches (pubkey, patch_request_id, author_name, author_email, title, body, commit_sha, commit_date, raw_text) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
282- pubkey,
283- prID,
284- header.Author.Name,
285- header.Author.Email,
286- header.Title,
287- header.Body,
288- header.SHA,
289- header.CommitterDate,
290- buf.String(),
291- )
292- try(sesh, err)
293+ pr := &PrCmd{
294+ Session: sesh,
295+ Backend: be,
296+ Pubkey: pubkey,
297+ }
298
299- wish.Printf(
300- sesh,
301- "created patch request!\nID: %d\nTitle: %s\n",
302- prID,
303- prName,
304- )
305+ if *out == true {
306+ pr.PrintPatches(prID)
307+ } else if prID != 0 {
308+ pr.SubmitPatch(prID)
309+ } else if subCmd == "ls" {
310+ wish.Println(sesh, "list all patch requests")
311+ } else if repoName != "" {
312+ pr.SubmitPatchRequest(repoName)
313 }
314
315 return
+0,
-45
1@@ -1,45 +0,0 @@
2-package git
3-
4-import (
5- "time"
6-)
7-
8-// PatchRequest is a database model for patches submitted to a Repo
9-type PatchRequest struct {
10- ID int64 `db:"id"`
11- Pubkey string `db:"pubkey"`
12- RepoID int64 `db:"repo_id"`
13- Name string `db:"name"`
14- Text string `db:"text"`
15- CreatedAt time.Time `db:"created_at"`
16- UpdatedAt time.Time `db:"updated_at"`
17-}
18-
19-// Patch is a database model for a single entry in a patchset
20-// This usually corresponds to a git commit.
21-type Patch struct {
22- ID int64 `db:"id"`
23- Pubkey string `db:"pubkey"`
24- PatchRequestID int64 `db:"patch_request_id"`
25- AuthorName string `db:"author_name"`
26- AuthorEmail string `db:"author_email"`
27- Title string `db:"title"`
28- Body string `db:"body"`
29- CommitSha string `db:"commit_sha"`
30- CommitDate time.Time `db:"commit_date"`
31- RawText string `db:"raw_text"`
32- CreatedAt time.Time `db:"created_at"`
33-}
34-
35-// Comment is a database model for a non-patch comment within a PatchRequest
36-type Comment struct {
37- ID int64 `db:"id"`
38- UserID int64 `db:"user_id"`
39- PatchRequestID int64 `db:"patch_request_id"`
40- Text string `db:"text"`
41- CreatedAt time.Time `db:"created_at"`
42- UpdatedAt time.Time `db:"updated_at"`
43-}
44-
45-type GitDB interface {
46-}
+0,
-88
1@@ -1,88 +0,0 @@
2-package git
3-
4-import (
5- "fmt"
6- "io"
7- "log/slog"
8- "os"
9- "path/filepath"
10-
11- "github.com/charmbracelet/ssh"
12- "github.com/picosh/send/send/utils"
13-)
14-
15-type UploadHandler struct {
16- Cfg *GitCfg
17- Logger *slog.Logger
18-}
19-
20-func NewUploadHandler(cfg *GitCfg, logger *slog.Logger) *UploadHandler {
21- return &UploadHandler{
22- Cfg: cfg,
23- Logger: logger,
24- }
25-}
26-
27-func (h *UploadHandler) Read(s ssh.Session, entry *utils.FileEntry) (os.FileInfo, utils.ReaderAtCloser, error) {
28- fmt.Println("read")
29- cleanFilename := filepath.Base(entry.Filepath)
30-
31- if cleanFilename == "" || cleanFilename == "." {
32- return nil, nil, os.ErrNotExist
33- }
34-
35- return nil, nil, os.ErrNotExist
36-}
37-
38-func (h *UploadHandler) List(s ssh.Session, fpath string, isDir bool, recursive bool) ([]os.FileInfo, error) {
39- fmt.Println("list")
40- var fileList []os.FileInfo
41- cleanFilename := filepath.Base(fpath)
42-
43- if cleanFilename == "" || cleanFilename == "." || cleanFilename == "/" {
44- name := cleanFilename
45- if name == "" {
46- name = "/"
47- }
48-
49- fileList = append(fileList, &utils.VirtualFile{
50- FName: name,
51- FIsDir: true,
52- })
53- } else {
54- }
55-
56- return fileList, nil
57-}
58-
59-func (h *UploadHandler) GetLogger() *slog.Logger {
60- return h.Logger
61-}
62-
63-func (h *UploadHandler) Validate(s ssh.Session) error {
64- fmt.Println("validate")
65- return nil
66-}
67-
68-func (h *UploadHandler) Write(s ssh.Session, entry *utils.FileEntry) (string, error) {
69- fmt.Println("write")
70- logger := h.GetLogger()
71- user := s.User()
72-
73- filename := filepath.Base(entry.Filepath)
74- logger = logger.With(
75- "user", user,
76- "filepath", entry.Filepath,
77- "size", entry.Size,
78- "filename", filename,
79- )
80-
81- var text []byte
82- if b, err := io.ReadAll(entry.Reader); err == nil {
83- text = b
84- }
85-
86- fmt.Println(string(text))
87-
88- return "", nil
89-}
M
ssh.go
+1,
-8
1@@ -12,9 +12,6 @@ import (
2
3 "github.com/charmbracelet/ssh"
4 "github.com/charmbracelet/wish"
5- wishrsync "github.com/picosh/send/send/rsync"
6- "github.com/picosh/send/send/scp"
7- "github.com/picosh/send/send/sftp"
8 )
9
10 func authHandler(ctx ssh.Context, key ssh.PublicKey) bool {
11@@ -33,7 +30,6 @@ func GitSshServer() {
12
13 cfg := NewGitCfg()
14 logger := slog.Default()
15- handler := NewUploadHandler(cfg, logger)
16 dbh, err := Open(":memory:", logger)
17 if err != nil {
18 panic(err)
19@@ -53,11 +49,8 @@ func GitSshServer() {
20 filepath.Join(cfg.DataPath, "term_info_ed25519"),
21 ),
22 wish.WithPublicKeyAuth(authHandler),
23- sftp.SSHOption(handler),
24 wish.WithMiddleware(
25- scp.Middleware(handler),
26- wishrsync.Middleware(handler),
27- GitServerMiddleware(be),
28+ GitPatchRequestMiddleware(be),
29 ),
30 )
31