- commit
- 58bf51e
- parent
- 04029e4
- author
- Eric Bower
- date
- 2024-05-11 13:53:59 -0400 EDT
refactor: use urfav/cli
A
cli.go
+363,
-0
1@@ -0,0 +1,363 @@
2+package git
3+
4+import (
5+ "fmt"
6+ "io"
7+ "path/filepath"
8+ "strconv"
9+ "text/tabwriter"
10+ "time"
11+
12+ "github.com/charmbracelet/soft-serve/pkg/utils"
13+ "github.com/charmbracelet/ssh"
14+ "github.com/charmbracelet/wish"
15+ "github.com/urfave/cli/v2"
16+)
17+
18+func NewTabWriter(out io.Writer) *tabwriter.Writer {
19+ return tabwriter.NewWriter(out, 0, 0, 1, ' ', tabwriter.TabIndent)
20+}
21+
22+func getPrID(str string) (int64, error) {
23+ prID, err := strconv.ParseInt(str, 10, 64)
24+ return prID, err
25+}
26+
27+func NewCli(sesh ssh.Session, be *Backend, pr GitPatchRequest) *cli.App {
28+ pubkey := be.Pubkey(sesh.PublicKey())
29+ app := &cli.App{
30+ Name: "ssh",
31+ Description: "A companion SSH server to allow external collaboration",
32+ Writer: sesh,
33+ ErrWriter: sesh,
34+ Commands: []*cli.Command{
35+ {
36+ Name: "git-receive-pack",
37+ Usage: "Receive what is pushed into the repository",
38+ Action: func(cCtx *cli.Context) error {
39+ repoName := cCtx.Args().First()
40+ err := gitServiceCommands(sesh, be, "git-receive-patch", repoName)
41+ return err
42+ },
43+ },
44+ {
45+ Name: "git-upload-pack",
46+ Usage: "Send objects packed back to git-fetch-pack",
47+ Action: func(cCtx *cli.Context) error {
48+ repoName := cCtx.Args().First()
49+ err := gitServiceCommands(sesh, be, "git-upload-patch", repoName)
50+ return err
51+ },
52+ },
53+ {
54+ Name: "ls",
55+ Usage: "list all git repos",
56+ Action: func(cCtx *cli.Context) error {
57+ repos, err := pr.GetRepos()
58+ if err != nil {
59+ return err
60+ }
61+ writer := NewTabWriter(sesh)
62+ fmt.Fprintln(writer, "Name\tDir")
63+ for _, repo := range repos {
64+ fmt.Fprintf(
65+ writer,
66+ "%s\t%s\n",
67+ utils.SanitizeRepo(repo),
68+ filepath.Join(be.ReposDir(), repo),
69+ )
70+ }
71+ writer.Flush()
72+ return nil
73+ },
74+ },
75+ {
76+ Name: "pr",
77+ Usage: "manage patch requests",
78+ Subcommands: []*cli.Command{
79+ {
80+ Name: "ls",
81+ Usage: "list all patch requests",
82+ Action: func(cCtx *cli.Context) error {
83+ prs, err := pr.GetPatchRequests()
84+ if err != nil {
85+ return err
86+ }
87+
88+ writer := NewTabWriter(sesh)
89+ fmt.Fprintln(writer, "ID\tName\tStatus\tDate")
90+ for _, req := range prs {
91+ fmt.Fprintf(
92+ writer,
93+ "%d\t%s\t[%s]\t%s\n",
94+ req.ID,
95+ req.Name,
96+ req.Status,
97+ req.CreatedAt.Format(time.RFC3339Nano),
98+ )
99+ }
100+ writer.Flush()
101+ return nil
102+ },
103+ },
104+ {
105+ Name: "create",
106+ Usage: "submit a new patch request",
107+ Action: func(cCtx *cli.Context) error {
108+ repoID := cCtx.Args().First()
109+ request, err := pr.SubmitPatchRequest(pubkey, repoID, sesh)
110+ if err != nil {
111+ return err
112+ }
113+ wish.Printf(
114+ sesh,
115+ "Patch Request submitted! Use the ID for interacting with this Patch Request.\nID\tName\n%d\t%s\n",
116+ request.ID,
117+ request.Name,
118+ )
119+ return nil
120+ },
121+ },
122+ {
123+ Name: "print",
124+ Usage: "print the patches for a patch request",
125+ Action: func(cCtx *cli.Context) error {
126+ prID, err := getPrID(cCtx.Args().First())
127+ if err != nil {
128+ return err
129+ }
130+
131+ patches, err := pr.GetPatchesByPrID(prID)
132+ if err != nil {
133+ return err
134+ }
135+
136+ if len(patches) == 1 {
137+ wish.Println(sesh, patches[0].RawText)
138+ return nil
139+ }
140+
141+ for _, patch := range patches {
142+ wish.Printf(sesh, "%s\n\n\n", patch.RawText)
143+ }
144+
145+ return nil
146+ },
147+ },
148+ {
149+ Name: "stats",
150+ Usage: "print patch request with patch stats",
151+ Action: func(cCtx *cli.Context) error {
152+ prID, err := getPrID(cCtx.Args().First())
153+ if err != nil {
154+ return err
155+ }
156+
157+ request, err := pr.GetPatchRequestByID(prID)
158+ if err != nil {
159+ return err
160+ }
161+
162+ writer := NewTabWriter(sesh)
163+ fmt.Fprintln(writer, "ID\tName\tStatus\tDate")
164+ fmt.Fprintf(
165+ writer,
166+ "%d\t%s\t[%s]\t%s\n%s\n\n",
167+ request.ID, request.Name, request.Status, request.CreatedAt.Format(time.RFC3339Nano),
168+ request.Text,
169+ )
170+ writer.Flush()
171+
172+ patches, err := pr.GetPatchesByPrID(prID)
173+ if err != nil {
174+ return err
175+ }
176+
177+ for _, patch := range patches {
178+ reviewTxt := ""
179+ if patch.Review {
180+ reviewTxt = "[review]"
181+ }
182+ wish.Printf(
183+ sesh,
184+ "%s %s %s\n%s <%s>\n%s\n\n---\n%s\n%s\n\n\n",
185+ patch.Title,
186+ reviewTxt,
187+ patch.CommitSha,
188+ patch.AuthorName,
189+ patch.AuthorEmail,
190+ patch.AuthorDate.Format(time.RFC3339Nano),
191+ patch.BodyAppendix,
192+ patch.Body,
193+ )
194+ }
195+
196+ return nil
197+ },
198+ },
199+ {
200+ Name: "summary",
201+ Usage: "list patches in patch request",
202+ Action: func(cCtx *cli.Context) error {
203+ prID, err := getPrID(cCtx.Args().First())
204+ if err != nil {
205+ return err
206+ }
207+ request, err := pr.GetPatchRequestByID(prID)
208+ if err != nil {
209+ return err
210+ }
211+
212+ writer := NewTabWriter(sesh)
213+ fmt.Fprintln(writer, "ID\tName\tStatus\tDate")
214+ fmt.Fprintf(
215+ writer,
216+ "%d\t%s\t[%s]\t%s\n%s\n",
217+ request.ID, request.Name, request.Status, request.CreatedAt.Format(time.RFC3339Nano),
218+ request.Text,
219+ )
220+ writer.Flush()
221+
222+ patches, err := pr.GetPatchesByPrID(prID)
223+ if err != nil {
224+ return err
225+ }
226+
227+ w := NewTabWriter(sesh)
228+ fmt.Fprintln(w, "Title\tStatus\tCommit\tAuthor\tDate")
229+ for _, patch := range patches {
230+ reviewTxt := ""
231+ if patch.Review {
232+ reviewTxt = "[review]"
233+ }
234+ fmt.Fprintf(
235+ w,
236+ "%s\t%s\t%s\t%s <%s>\t%s\n",
237+ patch.Title,
238+ reviewTxt,
239+ patch.CommitSha,
240+ patch.AuthorName,
241+ patch.AuthorEmail,
242+ patch.AuthorDate.Format(time.RFC3339Nano),
243+ )
244+ }
245+ w.Flush()
246+
247+ return nil
248+ },
249+ },
250+ {
251+ Name: "accept",
252+ Usage: "accept a patch request",
253+ Action: func(cCtx *cli.Context) error {
254+ prID, err := getPrID(cCtx.Args().First())
255+ if err != nil {
256+ return err
257+ }
258+ if !be.IsAdmin(sesh.PublicKey()) {
259+ return fmt.Errorf("must be admin to accpet PR")
260+ }
261+ err = pr.UpdatePatchRequest(prID, "accept")
262+ return err
263+ },
264+ },
265+ {
266+ Name: "close",
267+ Usage: "close a patch request",
268+ Action: func(cCtx *cli.Context) error {
269+ prID, err := getPrID(cCtx.Args().First())
270+ if err != nil {
271+ return err
272+ }
273+ if !be.IsAdmin(sesh.PublicKey()) {
274+ return fmt.Errorf("must be admin to close PR")
275+ }
276+ err = pr.UpdatePatchRequest(prID, "close")
277+ return err
278+ },
279+ },
280+ {
281+ Name: "reopen",
282+ Usage: "reopen a patch request",
283+ Action: func(cCtx *cli.Context) error {
284+ prID, err := getPrID(cCtx.Args().First())
285+ if err != nil {
286+ return err
287+ }
288+ if !be.IsAdmin(sesh.PublicKey()) {
289+ return fmt.Errorf("must be admin to close PR")
290+ }
291+ err = pr.UpdatePatchRequest(prID, "open")
292+ return err
293+ },
294+ },
295+ {
296+ Name: "add",
297+ Usage: "append a patch to the patch request",
298+ Flags: []cli.Flag{
299+ &cli.BoolFlag{
300+ Name: "review",
301+ Usage: "mark patch as a review",
302+ },
303+ },
304+ Action: func(cCtx *cli.Context) error {
305+ prID, err := getPrID(cCtx.Args().First())
306+ if err != nil {
307+ return err
308+ }
309+ isAdmin := be.IsAdmin(sesh.PublicKey())
310+ isReview := cCtx.Bool("review")
311+ var req PatchRequest
312+ err = be.DB.Get(&req, "SELECT * FROM patch_requests WHERE id=?", prID)
313+ if err != nil {
314+ return err
315+ }
316+ isPrOwner := req.Pubkey == be.Pubkey(sesh.PublicKey())
317+ if !isAdmin && !isPrOwner {
318+ return fmt.Errorf("unauthorized, you are not the owner of this Patch Request")
319+ }
320+
321+ patch, err := pr.SubmitPatch(pubkey, prID, isReview, sesh)
322+ if err != nil {
323+ return err
324+ }
325+ reviewTxt := ""
326+ if isReview {
327+ err = pr.UpdatePatchRequest(prID, "review")
328+ if err != nil {
329+ return err
330+ }
331+ reviewTxt = "[review]"
332+ }
333+
334+ wish.Println(sesh, "Patch submitted!")
335+ writer := NewTabWriter(sesh)
336+ fmt.Fprintln(
337+ writer,
338+ "ID\tTitle",
339+ )
340+ fmt.Fprintf(
341+ writer,
342+ "%d\t%s %s\n",
343+ patch.ID,
344+ patch.Title,
345+ reviewTxt,
346+ )
347+ writer.Flush()
348+ return nil
349+ },
350+ },
351+ {
352+ Name: "comment",
353+ Usage: "comment on a patch request",
354+ Action: func(cCtx *cli.Context) error {
355+ return nil
356+ },
357+ },
358+ },
359+ },
360+ },
361+ }
362+
363+ return app
364+}
M
go.mod
+4,
-0
1@@ -25,6 +25,7 @@ require (
2 github.com/charmbracelet/x/errors v0.0.0-20240117030013-d31dba354651 // indirect
3 github.com/charmbracelet/x/exp/term v0.0.0-20240229115032-4b79243a3516 // indirect
4 github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 // indirect
5+ github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
6 github.com/creack/pty v1.1.21 // indirect
7 github.com/dustin/go-humanize v1.0.1 // indirect
8 github.com/git-lfs/pktline v0.0.0-20230103162542-ca444d533ef1 // indirect
9@@ -55,7 +56,10 @@ require (
10 github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
11 github.com/rivo/uniseg v0.4.7 // indirect
12 github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086 // indirect
13+ github.com/russross/blackfriday/v2 v2.1.0 // indirect
14 github.com/sergi/go-diff v1.1.0 // indirect
15+ github.com/urfave/cli/v2 v2.27.2 // indirect
16+ github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
17 golang.org/x/crypto v0.21.0 // indirect
18 golang.org/x/exp v0.0.0-20231108232855-2478ac86f678 // indirect
19 golang.org/x/mod v0.14.0 // indirect
M
go.sum
+8,
-0
1@@ -34,6 +34,8 @@ github.com/charmbracelet/x/exp/term v0.0.0-20240229115032-4b79243a3516 h1:wL/Piy
2 github.com/charmbracelet/x/exp/term v0.0.0-20240229115032-4b79243a3516/go.mod h1:ntNL6rRIDmBHKUmo6ZKt344wCTcrPsSrfVj72qT8A5U=
3 github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81 h1:q2hJAaP1k2wIvVRd/hEHD7lacgqrCPS+k8g1MndzfWY=
4 github.com/containerd/console v1.0.4-0.20230313162750-1ae8d489ac81/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk=
5+github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
6+github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
7 github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0=
8 github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
9 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
10@@ -127,6 +129,8 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN
11 github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
12 github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086 h1:mncRSDOqYCng7jOD+Y6+IivdRI6Kzv2BLWYkWkdQfu0=
13 github.com/rubyist/tracerx v0.0.0-20170927163412-787959303086/go.mod h1:YpdgDXpumPB/+EGmGTYHeiW/0QVFRzBYTNFaxWfPDk4=
14+github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
15+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
16 github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
17 github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
18 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
19@@ -139,6 +143,10 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
20 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
21 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
22 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
23+github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
24+github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
25+github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
26+github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
27 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
28 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
29 golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
M
mdw.go
+6,
-329
1@@ -1,343 +1,20 @@
2 package git
3
4 import (
5- "flag"
6- "fmt"
7- "io"
8- "path/filepath"
9- "regexp"
10- "strconv"
11- "strings"
12- "text/tabwriter"
13- "time"
14-
15- "github.com/charmbracelet/soft-serve/pkg/git"
16- "github.com/charmbracelet/soft-serve/pkg/utils"
17 "github.com/charmbracelet/ssh"
18 "github.com/charmbracelet/wish"
19 )
20
21-func gitServiceCommands(sesh ssh.Session, be *Backend, cmd, repoName string) error {
22- name := utils.SanitizeRepo(repoName)
23- // git bare repositories should end in ".git"
24- // https://git-scm.com/docs/gitrepository-layout
25- repoID := be.RepoID(name)
26- reposDir := be.ReposDir()
27- err := git.EnsureWithin(reposDir, repoID)
28- if err != nil {
29- return err
30- }
31- repoPath := filepath.Join(reposDir, repoID)
32- serviceCmd := git.ServiceCommand{
33- Stdin: sesh,
34- Stdout: sesh,
35- Stderr: sesh.Stderr(),
36- Dir: repoPath,
37- Env: sesh.Environ(),
38- }
39-
40- if cmd == "git-receive-pack" {
41- err := git.ReceivePack(sesh.Context(), serviceCmd)
42- if err != nil {
43- return err
44- }
45- } else if cmd == "git-upload-pack" {
46- err := git.UploadPack(sesh.Context(), serviceCmd)
47- if err != nil {
48- return err
49- }
50- }
51-
52- return nil
53-}
54-
55-func flagSet(sesh ssh.Session, cmdName string) *flag.FlagSet {
56- cmd := flag.NewFlagSet(cmdName, flag.ContinueOnError)
57- cmd.SetOutput(sesh)
58- return cmd
59-}
60-
61-func NewTabWriter(out io.Writer) *tabwriter.Writer {
62- return tabwriter.NewWriter(out, 0, 0, 1, ' ', tabwriter.TabIndent)
63-}
64-
65 func GitPatchRequestMiddleware(be *Backend, pr GitPatchRequest) wish.Middleware {
66- isNumRe := regexp.MustCompile(`^\d+$`)
67-
68 return func(next ssh.Handler) ssh.Handler {
69 return func(sesh ssh.Session) {
70- pubkey := be.Pubkey(sesh.PublicKey())
71 args := sesh.Command()
72- cmd := "help"
73- if len(args) > 0 {
74- cmd = args[0]
75- }
76-
77- if cmd == "git-receive-pack" || cmd == "git-upload-pack" {
78- repoName := args[1]
79- err := gitServiceCommands(sesh, be, cmd, repoName)
80- if err != nil {
81- wish.Fatalln(sesh, err)
82- return
83- }
84- } else if cmd == "help" {
85- wish.Println(sesh, "commands: [help, pr, ls, git-receive-pack, git-upload-pack]")
86- } else if cmd == "ls" {
87- repos, err := pr.GetRepos()
88- if err != nil {
89- wish.Fatalln(sesh, err)
90- return
91- }
92- writer := NewTabWriter(sesh)
93- fmt.Fprintln(writer, "Name\tDir")
94- for _, repo := range repos {
95- fmt.Fprintf(
96- writer,
97- "%s\t%s\n",
98- utils.SanitizeRepo(repo),
99- filepath.Join(be.ReposDir(), repo),
100- )
101- }
102- writer.Flush()
103- } else if cmd == "pr" {
104- /*
105- ssh git.sh ls
106- ssh git.sh pr ls
107- git format-patch -1 HEAD~1 --stdout | ssh git.sh pr create
108- ssh git.sh pr print 1
109- ssh git.sh pr print 1 --summary
110- ssh git.sh pr print 1 --ls
111- ssh git.sh pr accept 1
112- ssh git.sh pr close 1
113- git format-patch -1 HEAD~1 --stdout | ssh git.sh pr review 1
114- echo "my feedback" | ssh git.sh pr comment 1
115- */
116- prCmd := flagSet(sesh, "pr")
117- out := prCmd.Bool("stdout", false, "print patchset to stdout")
118- accept := prCmd.Bool("accept", false, "mark patch request as accepted")
119- closed := prCmd.Bool("close", false, "mark patch request as closed")
120- review := prCmd.Bool("review", false, "mark patch request as reviewed")
121- stats := prCmd.Bool("stats", false, "return summary instead of patches")
122- prLs := prCmd.Bool("ls", false, "return list of patches")
123-
124- if len(args) < 2 {
125- wish.Fatalln(sesh, "must provide repo name or patch request ID")
126- return
127- }
128-
129- var err error
130- err = prCmd.Parse(args[2:])
131- if err != nil {
132- wish.Fatalln(sesh, err)
133- return
134- }
135- subCmd := strings.TrimSpace(args[1])
136-
137- repoID := ""
138- var prID int64
139- // figure out subcommand based on what was passed in
140- if subCmd == "ls" {
141- // skip proccessing
142- } else if isNumRe.MatchString(subCmd) {
143- // we probably have a patch request id
144- prID, err = strconv.ParseInt(subCmd, 10, 64)
145- if err != nil {
146- wish.Fatalln(sesh, err)
147- return
148- }
149- subCmd = "patchRequest"
150- } else {
151- // we probably have a repo name
152- repoID = be.RepoID(subCmd)
153- subCmd = "submitPatchRequest"
154- }
155-
156- if subCmd == "ls" {
157- prs, err := pr.GetPatchRequests()
158- if err != nil {
159- wish.Fatalln(sesh, err)
160- return
161- }
162-
163- writer := NewTabWriter(sesh)
164- fmt.Fprintln(writer, "ID\tName\tStatus\tDate")
165- for _, req := range prs {
166- fmt.Fprintf(
167- writer,
168- "%d\t%s\t[%s]\t%s\n",
169- req.ID,
170- req.Name,
171- req.Status,
172- req.CreatedAt.Format(time.RFC3339Nano),
173- )
174- }
175- writer.Flush()
176- } else if subCmd == "submitPatchRequest" {
177- request, err := pr.SubmitPatchRequest(pubkey, repoID, sesh)
178- if err != nil {
179- wish.Fatalln(sesh, err)
180- return
181- }
182- wish.Printf(sesh, "Patch Request submitted! Use the ID for interacting with this Patch Request.\nID\tName\n%d\t%s\n", request.ID, request.Name)
183- } else if subCmd == "patchRequest" {
184- if *prLs {
185- _, err := pr.GetPatchRequestByID(prID)
186- if err != nil {
187- wish.Fatalln(sesh, err)
188- return
189- }
190-
191- patches, err := pr.GetPatchesByPrID(prID)
192- if err != nil {
193- wish.Fatalln(sesh, err)
194- return
195- }
196-
197- writer := NewTabWriter(sesh)
198- fmt.Fprintln(writer, "ID\tTitle\tReview\tAuthor\tDate")
199- for _, patch := range patches {
200- fmt.Fprintf(
201- writer,
202- "%d\t%s\t%t\t%s\t%s\n",
203- patch.ID,
204- patch.Title,
205- patch.Review,
206- patch.AuthorName,
207- patch.AuthorDate.Format(time.RFC3339Nano),
208- )
209- }
210- writer.Flush()
211- return
212- }
213-
214- if *stats {
215- request, err := pr.GetPatchRequestByID(prID)
216- if err != nil {
217- wish.Fatalln(sesh, err)
218- return
219- }
220-
221- writer := NewTabWriter(sesh)
222- fmt.Fprintln(writer, "ID\tName\tStatus\tDate")
223- fmt.Fprintf(
224- writer,
225- "%d\t%s\t[%s]\t%s\n%s\n\n",
226- request.ID, request.Name, request.Status, request.CreatedAt.Format(time.RFC3339Nano),
227- request.Text,
228- )
229- writer.Flush()
230-
231- patches, err := pr.GetPatchesByPrID(prID)
232- if err != nil {
233- wish.Fatalln(sesh, err)
234- return
235- }
236-
237- for _, patch := range patches {
238- reviewTxt := ""
239- if patch.Review {
240- reviewTxt = "[review]"
241- }
242- wish.Printf(
243- sesh,
244- "%s %s %s\n%s <%s>\n%s\n\n---\n%s\n%s\n\n\n",
245- patch.Title,
246- reviewTxt,
247- patch.CommitSha,
248- patch.AuthorName,
249- patch.AuthorEmail,
250- patch.AuthorDate.Format(time.RFC3339Nano),
251- patch.BodyAppendix,
252- patch.Body,
253- )
254- }
255- return
256- }
257-
258- if *out {
259- patches, err := pr.GetPatchesByPrID(prID)
260- if err != nil {
261- wish.Fatalln(sesh, err)
262- return
263- }
264-
265- if len(patches) == 1 {
266- wish.Println(sesh, patches[0].RawText)
267- return
268- }
269-
270- for _, patch := range patches {
271- wish.Printf(sesh, "%s\n\n\n", patch.RawText)
272- }
273- } else if *accept {
274- if !be.IsAdmin(sesh.PublicKey()) {
275- wish.Fatalln(sesh, "must be admin to accept PR")
276- return
277- }
278- err := pr.UpdatePatchRequest(prID, "accept")
279- if err != nil {
280- wish.Fatalln(sesh, err)
281- return
282- }
283- } else if *closed {
284- if !be.IsAdmin(sesh.PublicKey()) {
285- wish.Fatalln(sesh, "must be admin to close PR")
286- return
287- }
288- err := pr.UpdatePatchRequest(prID, "close")
289- if err != nil {
290- wish.Fatalln(sesh, err)
291- return
292- }
293- } else {
294- isAdmin := be.IsAdmin(sesh.PublicKey())
295- var req PatchRequest
296- err = be.DB.Get(&req, "SELECT * FROM patch_requests WHERE id=?", prID)
297- if err != nil {
298- wish.Fatalln(sesh, err)
299- return
300- }
301- isPrOwner := req.Pubkey == be.Pubkey(sesh.PublicKey())
302- if !isAdmin && !isPrOwner {
303- wish.Fatalln(sesh, "unauthorized, you are not the owner of this Patch Request")
304- return
305- }
306-
307- patch, err := pr.SubmitPatch(pubkey, prID, *review, sesh)
308- if err != nil {
309- wish.Fatalln(sesh, err)
310- return
311- }
312- reviewTxt := ""
313- if *review {
314- err = pr.UpdatePatchRequest(prID, "review")
315- if err != nil {
316- wish.Fatalln(sesh, err)
317- return
318- }
319- reviewTxt = "[review]"
320- }
321-
322- wish.Println(sesh, "Patch submitted!")
323- writer := NewTabWriter(sesh)
324- fmt.Fprintln(
325- writer,
326- "ID\tTitle",
327- )
328- fmt.Fprintf(
329- writer,
330- "%d\t%s %s\n",
331- patch.ID,
332- patch.Title,
333- reviewTxt,
334- )
335- writer.Flush()
336- }
337- }
338-
339- return
340- } else {
341- next(sesh)
342+ cli := NewCli(sesh, be, pr)
343+ margs := append([]string{"git"}, args...)
344+ err := cli.Run(margs)
345+ if err != nil {
346+ be.Logger.Error("error when running cli", "err", err)
347+ wish.Fatalln(sesh, err)
348 return
349 }
350 }
M
util.go
+37,
-0
1@@ -6,8 +6,11 @@ import (
2 "errors"
3 "io"
4 "os"
5+ "path/filepath"
6 "strings"
7
8+ "github.com/charmbracelet/soft-serve/pkg/git"
9+ "github.com/charmbracelet/soft-serve/pkg/utils"
10 "github.com/charmbracelet/ssh"
11 )
12
13@@ -43,3 +46,37 @@ func getAuthorizedKeys(path string) ([]ssh.PublicKey, error) {
14
15 return keys, nil
16 }
17+
18+func gitServiceCommands(sesh ssh.Session, be *Backend, cmd, repoName string) error {
19+ name := utils.SanitizeRepo(repoName)
20+ // git bare repositories should end in ".git"
21+ // https://git-scm.com/docs/gitrepository-layout
22+ repoID := be.RepoID(name)
23+ reposDir := be.ReposDir()
24+ err := git.EnsureWithin(reposDir, repoID)
25+ if err != nil {
26+ return err
27+ }
28+ repoPath := filepath.Join(reposDir, repoID)
29+ serviceCmd := git.ServiceCommand{
30+ Stdin: sesh,
31+ Stdout: sesh,
32+ Stderr: sesh.Stderr(),
33+ Dir: repoPath,
34+ Env: sesh.Environ(),
35+ }
36+
37+ if cmd == "git-receive-pack" {
38+ err := git.ReceivePack(sesh.Context(), serviceCmd)
39+ if err != nil {
40+ return err
41+ }
42+ } else if cmd == "git-upload-pack" {
43+ err := git.UploadPack(sesh.Context(), serviceCmd)
44+ if err != nil {
45+ return err
46+ }
47+ }
48+
49+ return nil
50+}