repos / git-pr

a self-hosted git collaboration server
git clone https://github.com/picosh/git-pr.git

commit
6aea3de
parent
c20af52
author
Eric Bower
date
2024-05-05 22:45:10 -0400 EDT
progress
4 files changed,  +95, -29
M cfg.go
M db.go
M mdw.go
M backend.go
+9, -0
 1@@ -25,3 +25,12 @@ func (be *Backend) RepoName(name string) string {
 2 func (be *Backend) Pubkey(pk ssh.PublicKey) string {
 3 	return gossh.FingerprintSHA256(pk)
 4 }
 5+
 6+func (be *Backend) IsAdmin(pk ssh.PublicKey) bool {
 7+	for _, apk := range be.Cfg.Admins {
 8+		if ssh.KeysEqual(pk, apk) {
 9+			return true
10+		}
11+	}
12+	return false
13+}
M cfg.go
+3, -0
 1@@ -1,7 +1,10 @@
 2 package git
 3 
 4+import "github.com/charmbracelet/ssh"
 5+
 6 type GitCfg struct {
 7 	DataPath string
 8+	Admins   []ssh.PublicKey
 9 }
10 
11 func NewGitCfg() *GitCfg {
M db.go
+1, -0
1@@ -62,6 +62,7 @@ CREATE TABLE IF NOT EXISTS patch_requests (
2   repo_id TEXT NOT NULL,
3   name TEXT NOT NULL,
4   text TEXT NOT NULL,
5+  status TEXT NOT NULL,
6   created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
7   updated_at DATETIME NOT NULL
8 );
M mdw.go
+82, -29
  1@@ -65,10 +65,20 @@ func flagSet(sesh ssh.Session, cmdName string) *flag.FlagSet {
  2 	return cmd
  3 }
  4 
  5+// ssh git.sh ls
  6+// git format-patch --stdout | ssh git.sh pr test
  7+// git format-patch --stdout | ssh git.sh pr 123
  8+// ssh git.sh pr ls
  9+// ssh git.sh pr 123 --stdout | git am -3
 10+// ssh git.sh pr 123 --approve # or --close
 11+// echo "here is a comment" | ssh git.sh pr 123 --comment
 12+
 13 type GitPatchRequest interface {
 14-	GetPatchesByPrID(prID int64) ([]*Patch, error)
 15-	SubmitPatch(pubkey string, prID int64, patch io.Reader) (*Patch, error)
 16 	SubmitPatchRequest(pubkey string, repoName string, patches io.Reader) (*PatchRequest, error)
 17+	SubmitPatch(pubkey string, prID int64, patch io.Reader, review bool) (*Patch, error)
 18+	GetPatchRequests() ([]*PatchRequest, error)
 19+	GetPatchesByPrID(prID int64) ([]*Patch, error)
 20+	UpdatePatchRequest(prID int64, status string) error
 21 }
 22 
 23 type PrCmd struct {
 24@@ -94,7 +104,24 @@ func (pr PrCmd) GetPatchesByPrID(prID int64) ([]*Patch, error) {
 25 	return patches, nil
 26 }
 27 
 28-func (cmd PrCmd) SubmitPatch(pubkey string, prID int64, patch io.Reader) (*Patch, error) {
 29+func (cmd PrCmd) GetPatchRequests() ([]*PatchRequest, error) {
 30+	prs := []*PatchRequest{}
 31+	err := cmd.Backend.DB.Select(
 32+		&prs,
 33+		"SELECT * FROM patch_requests",
 34+	)
 35+	return prs, err
 36+}
 37+
 38+// status types: open, close, accept, review
 39+func (cmd PrCmd) UpdatePatchRequest(prID int64, status string) error {
 40+	_, err := cmd.Backend.DB.Exec(
 41+		"UPDATE patch_requests SET status=? WHERE id=?", status, prID,
 42+	)
 43+	return err
 44+}
 45+
 46+func (cmd PrCmd) SubmitPatch(pubkey string, prID int64, patch io.Reader, review bool) (*Patch, error) {
 47 	pr := PatchRequest{}
 48 	err := cmd.Backend.DB.Get(&pr, "SELECT * FROM patch_requests WHERE id=?", prID)
 49 	if err != nil {
 50@@ -104,11 +131,6 @@ func (cmd PrCmd) SubmitPatch(pubkey string, prID int64, patch io.Reader) (*Patch
 51 		return nil, fmt.Errorf("patch request (ID: %d) does not exist", prID)
 52 	}
 53 
 54-	review := false
 55-	if pr.Pubkey != pubkey {
 56-		review = true
 57-	}
 58-
 59 	// need to read io.Reader from session twice
 60 	var buf bytes.Buffer
 61 	tee := io.TeeReader(patch, &buf)
 62@@ -234,20 +256,6 @@ func GitPatchRequestMiddleware(be *Backend, pr GitPatchRequest) wish.Middleware
 63 					}
 64 				}
 65 			} else if cmd == "pr" {
 66-				// PATCH REQUEST STATUS:
 67-				// APPROVED
 68-				// CLOSED
 69-				// REVIEWED
 70-
 71-				// ssh git.sh ls
 72-				// git format-patch --stdout | ssh git.sh pr test
 73-				// git format-patch --stdout | ssh git.sh pr 123
 74-				// ssh git.sh pr ls
 75-				// ssh git.sh pr 123 --approve
 76-				// ssh git.sh pr 123 --close
 77-				// ssh git.sh pr 123 --stdout | git am -3
 78-				// echo "here is a comment" | ssh git.sh pr 123 --comment
 79-
 80 				prCmd := flagSet(sesh, "pr")
 81 				subCmd := strings.TrimSpace(args[2])
 82 				repoName := ""
 83@@ -259,9 +267,15 @@ func GitPatchRequestMiddleware(be *Backend, pr GitPatchRequest) wish.Middleware
 84 				} else {
 85 					repoName = utils.SanitizeRepo(subCmd)
 86 				}
 87+				help := prCmd.Bool("help", false, "print patch request help")
 88 				out := prCmd.Bool("stdout", false, "print patchset to stdout")
 89+				accept := prCmd.Bool("accept", false, "mark patch request as accepted")
 90+				closed := prCmd.Bool("close", false, "mark patch request as closed")
 91+				review := prCmd.Bool("review", false, "mark patch request as reviewed")
 92 
 93-				if *out {
 94+				if *help {
 95+					wish.Println(sesh, "commands: [pr ls, pr {id}]")
 96+				} else if prID != 0 && *out {
 97 					patches, err := pr.GetPatchesByPrID(prID)
 98 					try(sesh, err)
 99 
100@@ -274,14 +288,53 @@ func GitPatchRequestMiddleware(be *Backend, pr GitPatchRequest) wish.Middleware
101 						wish.Printf(sesh, "%s\n\n\n", patch.RawText)
102 					}
103 				} else if prID != 0 {
104-					patch, err := pr.SubmitPatch(pubkey, prID, sesh)
105-					if err != nil {
106-						wish.Fatalln(sesh, err)
107-						return
108+					if *accept {
109+						if !be.IsAdmin(sesh.PublicKey()) {
110+							wish.Fatalln(sesh, "must be admin to accept PR")
111+							return
112+						}
113+						err := pr.UpdatePatchRequest(prID, "accept")
114+						try(sesh, err)
115+					} else if *closed {
116+						if !be.IsAdmin(sesh.PublicKey()) {
117+							wish.Fatalln(sesh, "must be admin to close PR")
118+							return
119+						}
120+						err := pr.UpdatePatchRequest(prID, "close")
121+						try(sesh, err)
122+					} else {
123+						rv := *review
124+						isAdmin := be.IsAdmin(sesh.PublicKey())
125+						if !isAdmin {
126+							rv = false
127+						}
128+						var req PatchRequest
129+						err = be.DB.Get(&req, "SELECT * FROM patch_requests WHERE id=?", prID)
130+						try(sesh, err)
131+						isOwner := req.Pubkey != be.Pubkey(sesh.PublicKey())
132+						if !isAdmin || isOwner {
133+							wish.Fatalln(sesh, "unauthorized, you are not the owner of this Patch Request")
134+							return
135+						}
136+
137+						patch, err := pr.SubmitPatch(pubkey, prID, sesh, rv)
138+						if err != nil {
139+							wish.Fatalln(sesh, err)
140+							return
141+						}
142+						if *review {
143+							err = pr.UpdatePatchRequest(prID, "review")
144+							try(sesh, err)
145+						}
146+						wish.Printf(sesh, "Patch submitted! (ID:%d)\n", patch.ID)
147 					}
148-					wish.Printf(sesh, "Patch submitted! (ID:%d)\n", patch.ID)
149 				} else if subCmd == "ls" {
150-					wish.Println(sesh, "list all patch requests")
151+					prs, err := pr.GetPatchRequests()
152+					try(sesh, err)
153+					wish.Printf(sesh, "Name\tID\n")
154+					for _, req := range prs {
155+						wish.Printf(sesh, "%s\t%d\n", req.Name, req.ID)
156+					}
157 				} else if repoName != "" {
158 					request, err := pr.SubmitPatchRequest(pubkey, repoName, sesh)
159 					if err != nil {