repos / git-pr

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

commit
ac244f8
parent
837fe8a
author
Eric Bower
date
2024-11-06 10:01:23 -0500 EST
fix: repo owners have full perms
6 files changed,  +92, -45
M cli.go
M pr.go
M __snapshots__/e2e_test.snap
+10, -9
 1@@ -9,16 +9,16 @@ ID RepoID Name                    Status Patchsets User  Date
 2 PR submitted! Use the ID for interacting with this PR.
 3 Info
 4 ====
 5-URL: https://localhost/prs/8
 6+URL: https://localhost/prs/9
 7 Repo: contributor/bin
 8 
 9 ID Name                    Status Date
10-8  feat: lets build an rnn [open] 
11+9  feat: lets build an rnn [open] 
12 
13 Patchsets
14 ====
15 ID    Type User        Date
16-ps-12      contributor 
17+ps-13      contributor 
18 
19 Patches from latest patchset
20 ====
21@@ -29,21 +29,22 @@ Idx Title                   Commit  Author                   Date
22 
23 [TestE2E - 3]
24 ID RepoID           Name                       Status     Patchsets User        Date
25-8  contributor/bin  feat: lets build an rnn    [open]     1         contributor 
26-7  admin/ai         feat: lets build an rnn    [accepted] 2         contributor 
27+9  contributor/bin  feat: lets build an rnn    [open]     1         contributor 
28+8  admin/ai         feat: lets build an rnn    [accepted] 2         contributor 
29+7  contributor/ai   feat: lets build an rnn    [accepted] 1         admin       
30 6  contributor/test Closed patch with review   [closed]   2         contributor 
31 5  contributor/test Accepted patch with review [accepted] 2         contributor 
32 4  contributor/test Reviewed patch             [reviewed] 2         contributor 
33 3  contributor/test Closed patch (contributor) [closed]   1         contributor 
34 2  contributor/test Closed patch (admin)       [closed]   1         contributor 
35-1  contributor/test Accepted patch             [accepted] 1         contributor 
36+1  admin/test       Accepted patch             [accepted] 1         contributor 
37 
38 ---
39 
40 [TestE2E - 4]
41 RepoID   PrID PatchsetID Event             Created Data
42-admin/ai 7    ps-10      pr_created                
43-admin/ai 7    ps-11      pr_patchset_added         
44-admin/ai 7               pr_status_changed         {"status":"accepted"}
45+admin/ai 8    ps-11      pr_created                
46+admin/ai 8    ps-12      pr_patchset_added         
47+admin/ai 8               pr_status_changed         {"status":"accepted"}
48 
49 ---
M backend.go
+15, -2
 1@@ -110,8 +110,12 @@ type PrAcl struct {
 2 	CanDelete bool
 3 }
 4 
 5-func (be *Backend) GetPatchRequestAcl(prq *PatchRequest, requester *User) *PrAcl {
 6+func (be *Backend) GetPatchRequestAcl(repo *Repo, prq *PatchRequest, requester *User) *PrAcl {
 7 	acl := &PrAcl{}
 8+	if requester == nil {
 9+		return acl
10+	}
11+
12 	pubkey, err := be.PubkeyToPublicKey(requester.Pubkey)
13 	if err != nil {
14 		return acl
15@@ -126,14 +130,23 @@ func (be *Backend) GetPatchRequestAcl(prq *PatchRequest, requester *User) *PrAcl
16 		return acl
17 	}
18 
19+	// repo owner can do it all
20+	if repo.UserID == requester.ID {
21+		acl.CanModify = true
22+		acl.CanReview = true
23+		acl.CanDelete = true
24+		return acl
25+	}
26+
27 	// pr creator have special priv
28-	if requester != nil && be.IsPrOwner(prq.UserID, requester.ID) {
29+	if be.IsPrOwner(prq.UserID, requester.ID) {
30 		acl.CanModify = true
31 		acl.CanReview = false
32 		acl.CanDelete = true
33 		return acl
34 	}
35 
36+	// otherwise no perms
37 	acl.CanModify = false
38 	acl.CanReview = false
39 	acl.CanDelete = false
M cli.go
+39, -14
  1@@ -212,7 +212,6 @@ Here's how it works:
  2 					isPubkey := cCtx.Bool("pubkey")
  3 					prID := cCtx.Int64("pr")
  4 					repoNs := cCtx.String("repo")
  5-					fmt.Println("ZZZZ", repoNs)
  6 					var eventLogs []*EventLog
  7 					if isPubkey {
  8 						eventLogs, err = pr.GetEventLogsByUserID(user.ID)
  9@@ -650,7 +649,13 @@ Here's how it works:
 10 								return err
 11 							}
 12 
 13-							acl := be.GetPatchRequestAcl(prq, user)
 14+							repo, err := pr.GetRepoByID(prq.RepoID)
 15+							if err != nil {
 16+								return err
 17+							}
 18+
 19+							acl := be.GetPatchRequestAcl(repo, prq, user)
 20+							fmt.Println(repo.UserID, user.ID)
 21 							if !acl.CanReview {
 22 								return fmt.Errorf("you are not authorized to accept a PR")
 23 							}
 24@@ -683,22 +688,27 @@ Here's how it works:
 25 								return err
 26 							}
 27 
 28-							patchReq, err := pr.GetPatchRequestByID(prID)
 29+							prq, err := pr.GetPatchRequestByID(prID)
 30 							if err != nil {
 31 								return err
 32 							}
 33 
 34-							patchUser, err := pr.GetUserByID(patchReq.UserID)
 35+							patchUser, err := pr.GetUserByID(prq.UserID)
 36 							if err != nil {
 37 								return err
 38 							}
 39 
 40-							acl := be.GetPatchRequestAcl(patchReq, patchUser)
 41+							repo, err := pr.GetRepoByID(prq.RepoID)
 42+							if err != nil {
 43+								return err
 44+							}
 45+
 46+							acl := be.GetPatchRequestAcl(repo, prq, patchUser)
 47 							if !acl.CanModify {
 48 								return fmt.Errorf("you are not authorized to change PR status")
 49 							}
 50 
 51-							if patchReq.Status == "closed" {
 52+							if prq.Status == "closed" {
 53 								return fmt.Errorf("PR has already been closed")
 54 							}
 55 
 56@@ -711,7 +721,7 @@ Here's how it works:
 57 							if err != nil {
 58 								return err
 59 							}
 60-							wish.Printf(sesh, "Closed PR %s (#%d)\n", patchReq.Name, patchReq.ID)
 61+							wish.Printf(sesh, "Closed PR %s (#%d)\n", prq.Name, prq.ID)
 62 							return prSummary(be, pr, sesh, prID)
 63 						},
 64 					},
 65@@ -731,22 +741,27 @@ Here's how it works:
 66 								return err
 67 							}
 68 
 69-							patchReq, err := pr.GetPatchRequestByID(prID)
 70+							prq, err := pr.GetPatchRequestByID(prID)
 71+							if err != nil {
 72+								return err
 73+							}
 74+
 75+							patchUser, err := pr.GetUserByID(prq.UserID)
 76 							if err != nil {
 77 								return err
 78 							}
 79 
 80-							patchUser, err := pr.GetUserByID(patchReq.UserID)
 81+							repo, err := pr.GetRepoByID(prq.RepoID)
 82 							if err != nil {
 83 								return err
 84 							}
 85 
 86-							acl := be.GetPatchRequestAcl(patchReq, patchUser)
 87+							acl := be.GetPatchRequestAcl(repo, prq, patchUser)
 88 							if !acl.CanModify {
 89 								return fmt.Errorf("you are not authorized to change PR status")
 90 							}
 91 
 92-							if patchReq.Status == "open" {
 93+							if prq.Status == "open" {
 94 								return fmt.Errorf("PR is already open")
 95 							}
 96 
 97@@ -757,7 +772,7 @@ Here's how it works:
 98 
 99 							err = pr.UpdatePatchRequestStatus(prID, user.ID, "open")
100 							if err == nil {
101-								wish.Printf(sesh, "Reopened PR %s (#%d)\n", patchReq.Name, patchReq.ID)
102+								wish.Printf(sesh, "Reopened PR %s (#%d)\n", prq.Name, prq.ID)
103 							}
104 							return prSummary(be, pr, sesh, prID)
105 						},
106@@ -787,7 +802,12 @@ Here's how it works:
107 								return err
108 							}
109 
110-							acl := be.GetPatchRequestAcl(prq, user)
111+							repo, err := pr.GetRepoByID(prq.RepoID)
112+							if err != nil {
113+								return err
114+							}
115+
116+							acl := be.GetPatchRequestAcl(repo, prq, user)
117 							if !acl.CanModify {
118 								return fmt.Errorf("you are not authorized to change PR")
119 							}
120@@ -853,7 +873,12 @@ Here's how it works:
121 							isAccept := cCtx.Bool("accept")
122 							isClose := cCtx.Bool("close")
123 
124-							acl := be.GetPatchRequestAcl(prq, user)
125+							repo, err := pr.GetRepoByID(prq.RepoID)
126+							if err != nil {
127+								return err
128+							}
129+
130+							acl := be.GetPatchRequestAcl(repo, prq, user)
131 							if !acl.CanModify {
132 								return fmt.Errorf("you are not authorized to add patchsets to pr")
133 							}
M e2e_test.go
+23, -15
 1@@ -29,11 +29,11 @@ func testSingleTenantE2E(t *testing.T) {
 2 	time.Sleep(time.Millisecond * 100)
 3 	_, err := suite.userKey.Cmd(suite.patch, "pr create test")
 4 	if err == nil {
 5-		t.Error("user should not be able to create a PR")
 6+		t.Fatal("user should not be able to create a PR")
 7 	}
 8 	suite.adminKey.MustCmd(suite.patch, "pr create test")
 9 
10-	// Snapshot test ls command
11+	t.Log("Snapshot test ls command")
12 	actual, err := suite.userKey.Cmd(nil, "pr ls")
13 	bail(err)
14 	snaps.MatchSnapshot(t, actual)
15@@ -53,56 +53,64 @@ func testMultiTenantE2E(t *testing.T) {
16 
17 	time.Sleep(time.Millisecond * 100)
18 
19-	// Accepted pr
20-	suite.userKey.MustCmd(suite.patch, "pr create test")
21+	t.Log("Admin should be able to create a repo")
22+	suite.adminKey.MustCmd(nil, "repo create test")
23+
24+	t.Log("Accepted pr")
25+	suite.userKey.MustCmd(suite.patch, "pr create admin/test")
26 	suite.userKey.MustCmd(nil, "pr edit 1 Accepted patch")
27 	_, err := suite.userKey.Cmd(nil, "pr accept 1")
28 	if err == nil {
29-		t.Error("contrib should not be able to accept their own PR")
30+		t.Fatal("contrib should not be able to accept their own PR")
31 	}
32 	suite.adminKey.MustCmd(nil, "pr accept 1")
33 
34-	// Closed pr (admin)
35+	t.Log("Closed pr (admin)")
36 	suite.userKey.MustCmd(suite.patch, "pr create test")
37 	suite.userKey.MustCmd(nil, "pr edit 2 Closed patch (admin)")
38 	suite.adminKey.MustCmd(nil, "pr close 2")
39 
40-	// Closed pr (contributor)
41+	t.Log("Closed pr (contributor)")
42 	suite.userKey.MustCmd(suite.patch, "pr create test")
43 	suite.userKey.MustCmd(nil, "pr edit 3 Closed patch (contributor)")
44 	suite.userKey.MustCmd(nil, "pr close 3")
45 
46-	// Reviewed pr
47+	t.Log("Reviewed pr")
48 	suite.userKey.MustCmd(suite.patch, "pr create test")
49 	suite.userKey.MustCmd(nil, "pr edit 4 Reviewed patch")
50 	suite.adminKey.MustCmd(suite.otherPatch, "pr add --review 4")
51 
52-	// Accepted pr with review
53+	t.Log("Accepted pr with review")
54 	suite.userKey.MustCmd(suite.patch, "pr create test")
55 	suite.userKey.MustCmd(nil, "pr edit 5 Accepted patch with review")
56 	suite.adminKey.MustCmd(suite.otherPatch, "pr add --accept 5")
57 
58-	// Closed pr with review
59+	t.Log("Closed pr with review")
60 	suite.userKey.MustCmd(suite.patch, "pr create test")
61 	suite.userKey.MustCmd(nil, "pr edit 6 Closed patch with review")
62 	suite.adminKey.MustCmd(suite.otherPatch, "pr add --close 6")
63 
64-	// Create pr with user namespace
65+	t.Log("Create pr with user repo and user can accept")
66+	suite.userKey.MustCmd(nil, "repo create ai")
67+	suite.adminKey.MustCmd(suite.patch, "pr create contributor/ai")
68+	suite.userKey.MustCmd(suite.otherPatch, "pr accept 7")
69+
70+	t.Log("Create pr with user repo and admin can accept")
71 	suite.adminKey.MustCmd(nil, "repo create ai")
72 	suite.userKey.MustCmd(suite.patch, "pr create admin/ai")
73-	suite.adminKey.MustCmd(suite.otherPatch, "pr add --accept 7")
74+	suite.adminKey.MustCmd(suite.otherPatch, "pr add --accept 8")
75 
76-	// Create pr with default `bin` repo
77+	t.Log("Create pr with default `bin` repo")
78 	actual, err := suite.userKey.Cmd(suite.patch, "pr create")
79 	bail(err)
80 	snaps.MatchSnapshot(t, actual)
81 
82-	// Snapshot test ls command
83+	t.Log("Snapshot test ls command")
84 	actual, err = suite.userKey.Cmd(nil, "pr ls")
85 	bail(err)
86 	snaps.MatchSnapshot(t, actual)
87 
88-	// Snapshot test logs command
89+	t.Log("Snapshot test logs command")
90 	actual, err = suite.userKey.Cmd(nil, "logs --repo admin/ai")
91 	bail(err)
92 	snaps.MatchSnapshot(t, actual)
M pr.go
+0, -1
1@@ -147,7 +147,6 @@ func (pr PrCmd) GetRepos() (repos []*Repo, err error) {
2 
3 func (pr PrCmd) GetRepoByName(user *User, repoName string) (*Repo, error) {
4 	var repo Repo
5-	fmt.Println(user.ID, repoName)
6 	err := pr.Backend.DB.Get(&repo, "SELECT * FROM repos WHERE user_id=? AND name=?", user.ID, repoName)
7 	if err != nil {
8 		return nil, fmt.Errorf("repo not found: %s/%s", user.Name, repoName)
M sqlite.go
+5, -4
 1@@ -19,13 +19,14 @@ CREATE TABLE IF NOT EXISTS app_users (
 2 CREATE TABLE IF NOT EXISTS repos (
 3   id INTEGER PRIMARY KEY AUTOINCREMENT,
 4   user_id INTEGER NOT NULL,
 5-  name TEXT NOT NULL UNIQUE,
 6+  name TEXT NOT NULL,
 7   created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 8   updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
 9+  UNIQUE (user_id, name),
10   CONSTRAINT repo_user_id_fk
11-      FOREIGN KEY(user_id) REFERENCES app_users(id)
12-      ON DELETE CASCADE
13-      ON UPDATE CASCADE
14+	FOREIGN KEY(user_id) REFERENCES app_users(id)
15+	ON DELETE CASCADE
16+	ON UPDATE CASCADE
17 );
18 
19 CREATE TABLE IF NOT EXISTS acl (