- commit
- 0083d5e
- parent
- 6ac35cb
- author
- Eric Bower
- date
- 2024-05-31 18:43:09 -0400 EDT
feat: `pr add --force` to replace patchset style: various design tweaks feat: list repo ID with `pr ls`
5 files changed,
+135,
-32
M
cli.go
+58,
-20
1@@ -94,21 +94,31 @@ Here's how it works:
2 Usage: "Manage Patch Requests (PR)",
3 Subcommands: []*cli.Command{
4 {
5- Name: "ls",
6- Usage: "List all PRs",
7+ Name: "ls",
8+ Usage: "List all PRs",
9+ Args: true,
10+ ArgsUsage: "[repoID]",
11 Action: func(cCtx *cli.Context) error {
12- prs, err := pr.GetPatchRequests()
13+ repoID := cCtx.Args().First()
14+ var prs []*PatchRequest
15+ var err error
16+ if repoID == "" {
17+ prs, err = pr.GetPatchRequests()
18+ } else {
19+ prs, err = pr.GetPatchRequestsByRepoID(repoID)
20+ }
21 if err != nil {
22 return err
23 }
24
25 writer := NewTabWriter(sesh)
26- fmt.Fprintln(writer, "ID\tName\tStatus\tDate")
27+ fmt.Fprintln(writer, "ID\tRepoID\tName\tStatus\tDate")
28 for _, req := range prs {
29 fmt.Fprintf(
30 writer,
31- "%d\t%s\t[%s]\t%s\n",
32+ "%d\t%s\t%s\t[%s]\t%s\n",
33 req.ID,
34+ req.RepoID,
35 req.Name,
36 req.Status,
37 req.CreatedAt.Format(time.RFC3339Nano),
38@@ -119,8 +129,10 @@ Here's how it works:
39 },
40 },
41 {
42- Name: "create",
43- Usage: "Submit a new PR",
44+ Name: "create",
45+ Usage: "Submit a new PR",
46+ Args: true,
47+ ArgsUsage: "[repoID]",
48 Action: func(cCtx *cli.Context) error {
49 repoID := cCtx.Args().First()
50 request, err := pr.SubmitPatchRequest(repoID, pubkey, sesh)
51@@ -137,8 +149,10 @@ Here's how it works:
52 },
53 },
54 {
55- Name: "print",
56- Usage: "Print the patches for a PR",
57+ Name: "print",
58+ Usage: "Print the patches for a PR",
59+ Args: true,
60+ ArgsUsage: "[prID]",
61 Action: func(cCtx *cli.Context) error {
62 prID, err := getPrID(cCtx.Args().First())
63 if err != nil {
64@@ -166,8 +180,10 @@ Here's how it works:
65 },
66 },
67 {
68- Name: "stats",
69- Usage: "Print PR with diff stats",
70+ Name: "stats",
71+ Usage: "Print PR with diff stats",
72+ Args: true,
73+ ArgsUsage: "[prID]",
74 Action: func(cCtx *cli.Context) error {
75 prID, err := getPrID(cCtx.Args().First())
76 if err != nil {
77@@ -217,8 +233,10 @@ Here's how it works:
78 },
79 },
80 {
81- Name: "summary",
82- Usage: "List patches in PRs",
83+ Name: "summary",
84+ Usage: "List patches in PRs",
85+ Args: true,
86+ ArgsUsage: "[prID]",
87 Action: func(cCtx *cli.Context) error {
88 prID, err := getPrID(cCtx.Args().First())
89 if err != nil {
90@@ -268,8 +286,10 @@ Here's how it works:
91 },
92 },
93 {
94- Name: "accept",
95- Usage: "Accept a PR",
96+ Name: "accept",
97+ Usage: "Accept a PR",
98+ Args: true,
99+ ArgsUsage: "[prID]",
100 Action: func(cCtx *cli.Context) error {
101 prID, err := getPrID(cCtx.Args().First())
102 if err != nil {
103@@ -285,8 +305,10 @@ Here's how it works:
104 },
105 },
106 {
107- Name: "close",
108- Usage: "Close a PR",
109+ Name: "close",
110+ Usage: "Close a PR",
111+ Args: true,
112+ ArgsUsage: "[prID]",
113 Action: func(cCtx *cli.Context) error {
114 prID, err := getPrID(cCtx.Args().First())
115 if err != nil {
116@@ -307,8 +329,10 @@ Here's how it works:
117 },
118 },
119 {
120- Name: "reopen",
121- Usage: "Reopen a PR",
122+ Name: "reopen",
123+ Usage: "Reopen a PR",
124+ Args: true,
125+ ArgsUsage: "[prID]",
126 Action: func(cCtx *cli.Context) error {
127 prID, err := getPrID(cCtx.Args().First())
128 if err != nil {
129@@ -337,6 +361,10 @@ Here's how it works:
130 Name: "review",
131 Usage: "mark patch as a review",
132 },
133+ &cli.BoolFlag{
134+ Name: "force",
135+ Usage: "replace patchset with new patchset -- including reviews",
136+ },
137 },
138 Action: func(cCtx *cli.Context) error {
139 prID, err := getPrID(cCtx.Args().First())
140@@ -345,6 +373,7 @@ Here's how it works:
141 }
142 isAdmin := be.IsAdmin(sesh.PublicKey())
143 isReview := cCtx.Bool("review")
144+ isReplace := cCtx.Bool("force")
145 var req PatchRequest
146 err = be.DB.Get(&req, "SELECT * FROM patch_requests WHERE id=?", prID)
147 if err != nil {
148@@ -355,7 +384,16 @@ Here's how it works:
149 return fmt.Errorf("unauthorized, you are not the owner of this PR")
150 }
151
152- patches, err := pr.SubmitPatchSet(prID, pubkey, isReview, sesh)
153+ op := OpNormal
154+ if isReview {
155+ wish.Println(sesh, "Marking new patchset as a review")
156+ op = OpReview
157+ } else if isReplace {
158+ wish.Println(sesh, "Replacing current patchset with new one")
159+ op = OpReplace
160+ }
161+
162+ patches, err := pr.SubmitPatchSet(prID, pubkey, op, sesh)
163 if err != nil {
164 return err
165 }
M
pr.go
+68,
-6
1@@ -15,16 +15,25 @@ import (
2
3 var ErrPatchExists = errors.New("patch already exists for patch request")
4
5+type PatchsetOp int
6+
7+const (
8+ OpNormal PatchsetOp = iota
9+ OpReview
10+ OpReplace
11+)
12+
13 type GitPatchRequest interface {
14 GetRepos() ([]Repo, error)
15 GetRepoByID(repoID string) (*Repo, error)
16 SubmitPatchRequest(repoID string, pubkey string, patchset io.Reader) (*PatchRequest, error)
17- SubmitPatchSet(prID int64, pubkey string, review bool, patchset io.Reader) ([]*Patch, error)
18+ SubmitPatchSet(prID int64, pubkey string, op PatchsetOp, patchset io.Reader) ([]*Patch, error)
19 GetPatchRequestByID(prID int64) (*PatchRequest, error)
20 GetPatchRequests() ([]*PatchRequest, error)
21 GetPatchRequestsByRepoID(repoID string) ([]*PatchRequest, error)
22 GetPatchesByPrID(prID int64) ([]*Patch, error)
23 UpdatePatchRequest(prID int64, status string) error
24+ DeletePatchesByPrID(prID int64) error
25 }
26
27 type PrCmd struct {
28@@ -188,7 +197,7 @@ func (cmd PrCmd) parsePatchSet(patchset io.Reader) ([]*Patch, error) {
29 return patches, nil
30 }
31
32-func (cmd PrCmd) createPatch(tx *sqlx.Tx, patch *Patch) (int64, error) {
33+func (cmd PrCmd) createPatch(tx *sqlx.Tx, review bool, patch *Patch) (int64, error) {
34 patchExists := []Patch{}
35 _ = cmd.Backend.DB.Select(&patchExists, "SELECT * FROM patches WHERE patch_request_id = ? AND content_sha = ?", patch.PatchRequestID, patch.ContentSha)
36 if len(patchExists) > 0 {
37@@ -197,7 +206,7 @@ func (cmd PrCmd) createPatch(tx *sqlx.Tx, patch *Patch) (int64, error) {
38
39 var patchID int64
40 row := tx.QueryRow(
41- "INSERT INTO patches (pubkey, patch_request_id, author_name, author_email, author_date, title, body, body_appendix, commit_sha, content_sha, raw_text) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id",
42+ "INSERT INTO patches (pubkey, patch_request_id, author_name, author_email, author_date, title, body, body_appendix, commit_sha, content_sha, review, raw_text) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id",
43 patch.Pubkey,
44 patch.PatchRequestID,
45 patch.AuthorName,
46@@ -208,6 +217,7 @@ func (cmd PrCmd) createPatch(tx *sqlx.Tx, patch *Patch) (int64, error) {
47 patch.BodyAppendix,
48 patch.CommitSha,
49 patch.ContentSha,
50+ review,
51 patch.RawText,
52 )
53 err := row.Scan(&patchID)
54@@ -262,7 +272,7 @@ func (cmd PrCmd) SubmitPatchRequest(repoID string, pubkey string, patchset io.Re
55 for _, patch := range patches {
56 patch.Pubkey = pubkey
57 patch.PatchRequestID = prID
58- _, err = cmd.createPatch(tx, patch)
59+ _, err = cmd.createPatch(tx, false, patch)
60 if err != nil {
61 return nil, err
62 }
63@@ -278,7 +288,52 @@ func (cmd PrCmd) SubmitPatchRequest(repoID string, pubkey string, patchset io.Re
64 return &pr, err
65 }
66
67-func (cmd PrCmd) SubmitPatchSet(prID int64, pubkey string, review bool, patchset io.Reader) ([]*Patch, error) {
68+func (cmd PrCmd) SubmitPatchSet(prID int64, pubkey string, op PatchsetOp, patchset io.Reader) ([]*Patch, error) {
69+ fin := []*Patch{}
70+ tx, err := cmd.Backend.DB.Beginx()
71+ if err != nil {
72+ return fin, err
73+ }
74+
75+ defer func() {
76+ _ = tx.Rollback()
77+ }()
78+
79+ patches, err := cmd.parsePatchSet(patchset)
80+ if err != nil {
81+ return fin, err
82+ }
83+
84+ if op == OpReplace {
85+ err = cmd.DeletePatchesByPrID(prID)
86+ if err != nil {
87+ return fin, err
88+ }
89+ }
90+
91+ for _, patch := range patches {
92+ patch.Pubkey = pubkey
93+ patch.PatchRequestID = prID
94+ patchID, err := cmd.createPatch(tx, op == OpReview, patch)
95+ if err == nil {
96+ patch.ID = patchID
97+ fin = append(fin, patch)
98+ } else {
99+ if !errors.Is(ErrPatchExists, err) {
100+ return fin, err
101+ }
102+ }
103+ }
104+
105+ err = tx.Commit()
106+ if err != nil {
107+ return fin, err
108+ }
109+
110+ return fin, err
111+}
112+
113+func (cmd PrCmd) ReplacePatchSet(prID int64, pubkey string, patchset io.Reader) ([]*Patch, error) {
114 fin := []*Patch{}
115 tx, err := cmd.Backend.DB.Beginx()
116 if err != nil {
117@@ -297,7 +352,7 @@ func (cmd PrCmd) SubmitPatchSet(prID int64, pubkey string, review bool, patchset
118 for _, patch := range patches {
119 patch.Pubkey = pubkey
120 patch.PatchRequestID = prID
121- patchID, err := cmd.createPatch(tx, patch)
122+ patchID, err := cmd.createPatch(tx, false, patch)
123 if err == nil {
124 patch.ID = patchID
125 fin = append(fin, patch)
126@@ -315,3 +370,10 @@ func (cmd PrCmd) SubmitPatchSet(prID int64, pubkey string, review bool, patchset
127
128 return fin, err
129 }
130+
131+func (cmd PrCmd) DeletePatchesByPrID(prID int64) error {
132+ _, err := cmd.Backend.DB.Exec(
133+ "DELETE FROM patches WHERE patch_request_id=?", prID,
134+ )
135+ return err
136+}
+2,
-2
1@@ -21,8 +21,8 @@
2 <date>{{.AuthorDate}}</date>
3 </div>
4
5- <div>
6- <div>{{.Body}}</div>
7+ <div class="my">
8+ <pre>{{.Body}}</pre>
9 </div>
10 </div>
11 {{else}}
+1,
-1
1@@ -13,7 +13,7 @@
2 <div>Submit patch request: <code>git format-patch -1 HEAD --stdout | ssh pr.pico.sh pr create {{.ID}}</code></div>
3 </div>
4 </header>
5-<main>
6+<main class="group">
7 {{range .Prs}}
8 <article class="box">
9 <div><a href="{{.Url}}">{{.Text}}</a> <code>{{.Status}}</code></div>
+6,
-3
1@@ -10,10 +10,13 @@
2 <h1 class="text-2xl mb">Repos</h1>
3 </header>
4 <main>
5- <ul>
6+ <div class="group">
7 {{range .Repos}}
8- <li><a href="{{.Url}}">{{.Text}}</a></li>
9+ <div class="box">
10+ <h3 class="text-lg"><a href="{{.Url}}">{{.Text}}</a></h3>
11+ <div class="text-sm">{{.Desc}}</div>
12+ </div>
13 {{end}}
14- </ul>
15+ </div>
16 </main>
17 {{end}}