repos / git-pr

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

commit
c32b448
parent
5427dcb
author
Eric Bower
date
2024-05-30 15:17:51 -0400 EDT
refactor: split patchsets and calc content sha
2 files changed,  +152, -69
M db.go
M pr.go
M db.go
+2, -0
 1@@ -33,6 +33,7 @@ type Patch struct {
 2 	Body           string    `db:"body"`
 3 	BodyAppendix   string    `db:"body_appendix"`
 4 	CommitSha      string    `db:"commit_sha"`
 5+	ContentSha     string    `db:"content_sha"`
 6 	Review         bool      `db:"review"`
 7 	RawText        string    `db:"raw_text"`
 8 	CreatedAt      time.Time `db:"created_at"`
 9@@ -80,6 +81,7 @@ CREATE TABLE IF NOT EXISTS patches (
10   body TEXT NOT NULL,
11   body_appendix TEXT NOT NULL,
12   commit_sha TEXT NOT NULL,
13+  content_sha TEXT NOT NULL,
14   review BOOLEAN NOT NULL DEFAULT false,
15   raw_text TEXT NOT NULL,
16   created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
M pr.go
+150, -69
  1@@ -1,19 +1,22 @@
  2 package git
  3 
  4 import (
  5-	"bytes"
  6+	"crypto/sha256"
  7+	"encoding/hex"
  8 	"fmt"
  9 	"io"
 10+	"strings"
 11 	"time"
 12 
 13 	"github.com/bluekeyes/go-gitdiff/gitdiff"
 14+	"github.com/jmoiron/sqlx"
 15 )
 16 
 17 type GitPatchRequest interface {
 18 	GetRepos() ([]Repo, error)
 19 	GetRepoByID(repoID string) (*Repo, error)
 20-	SubmitPatchRequest(pubkey string, repoID string, patches io.Reader) (*PatchRequest, error)
 21-	SubmitPatch(pubkey string, prID int64, review bool, patch io.Reader) (*Patch, error)
 22+	SubmitPatchRequest(repoID int64, pubkey string, patchset io.Reader) (*PatchRequest, error)
 23+	SubmitPatchSet(prID int64, pubkey string, review bool, patchset io.Reader) error
 24 	GetPatchRequestByID(prID int64) (*PatchRequest, error)
 25 	GetPatchRequests() ([]*PatchRequest, error)
 26 	GetPatchRequestsByRepoID(repoID string) ([]*PatchRequest, error)
 27@@ -100,52 +103,114 @@ func (cmd PrCmd) UpdatePatchRequest(prID int64, status string) error {
 28 	return err
 29 }
 30 
 31-func (cmd PrCmd) SubmitPatch(pubkey string, prID int64, review bool, patch io.Reader) (*Patch, error) {
 32-	pr := PatchRequest{}
 33-	err := cmd.Backend.DB.Get(&pr, "SELECT * FROM patch_requests WHERE id=?", prID)
 34-	if err != nil {
 35-		return nil, err
 36+// calcContentSha calculates a shasum containing the important content
 37+// changes related to a patch.
 38+// We cannot rely on patch.CommitSha because it includes the commit date
 39+// that will change when a user fetches and applies the patch locally.
 40+func (cmd PrCmd) calcContentSha(diffFiles []*gitdiff.File, header *gitdiff.PatchHeader) string {
 41+	authorName := ""
 42+	authorEmail := ""
 43+	if header.Author != nil {
 44+		authorName = header.Author.Name
 45+		authorEmail = header.Author.Email
 46+	}
 47+	content := fmt.Sprintf(
 48+		"%s\n%s\n%s\n%s\n%s\n",
 49+		header.Title,
 50+		header.Body,
 51+		authorName,
 52+		authorEmail,
 53+		header.AuthorDate,
 54+	)
 55+	for _, diff := range diffFiles {
 56+		content += fmt.Sprintf(
 57+			"%s->%s %s..%s %s-%s\n",
 58+			diff.OldName, diff.NewName,
 59+			diff.OldOIDPrefix, diff.NewOIDPrefix,
 60+			diff.OldMode.String(), diff.NewMode.String(),
 61+		)
 62 	}
 63+	sha := sha256.Sum256([]byte(content))
 64+	shaStr := hex.EncodeToString(sha[:])
 65+	return shaStr
 66+}
 67 
 68-	// need to read io.Reader from session twice
 69-	var buf bytes.Buffer
 70-	tee := io.TeeReader(patch, &buf)
 71+func (cmd PrCmd) splitPatchSet(patchset string) []string {
 72+	return strings.Split(patchset, "\n\n\n")
 73+}
 74 
 75-	_, preamble, err := gitdiff.Parse(tee)
 76+func (cmd PrCmd) parsePatchSet(patchset io.Reader) ([]*Patch, error) {
 77+	patches := []*Patch{}
 78+	buf := new(strings.Builder)
 79+	_, err := io.Copy(buf, patchset)
 80 	if err != nil {
 81 		return nil, err
 82 	}
 83-	header, err := gitdiff.ParsePatchHeader(preamble)
 84-	if err != nil {
 85-		return nil, err
 86+
 87+	patchesRaw := cmd.splitPatchSet(buf.String())
 88+	for _, patchRaw := range patchesRaw {
 89+		reader := strings.NewReader(patchRaw)
 90+		diffFiles, preamble, err := gitdiff.Parse(reader)
 91+		if err != nil {
 92+			return nil, err
 93+		}
 94+		header, err := gitdiff.ParsePatchHeader(preamble)
 95+		if err != nil {
 96+			return nil, err
 97+		}
 98+
 99+		authorName := "Unknown"
100+		authorEmail := ""
101+		if header.Author != nil {
102+			authorName = header.Author.Name
103+			authorEmail = header.Author.Email
104+		}
105+
106+		contentSha := cmd.calcContentSha(diffFiles, header)
107+
108+		patches = append(patches, &Patch{
109+			AuthorName:   authorName,
110+			AuthorEmail:  authorEmail,
111+			AuthorDate:   header.AuthorDate.UTC().String(),
112+			Title:        header.Title,
113+			Body:         header.Body,
114+			BodyAppendix: header.BodyAppendix,
115+			ContentSha:   contentSha,
116+			CommitSha:    header.SHA,
117+			RawText:      patchRaw,
118+		})
119 	}
120 
121-	patchID := 0
122-	row := cmd.Backend.DB.QueryRow(
123-		"INSERT INTO patches (pubkey, patch_request_id, author_name, author_email, author_date, title, body, body_appendix, commit_sha, review, raw_text) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id",
124-		pubkey,
125-		prID,
126-		header.Author.Name,
127-		header.Author.Email,
128-		header.AuthorDate,
129-		header.Title,
130-		header.Body,
131-		header.BodyAppendix,
132-		header.SHA,
133-		review,
134-		buf.String(),
135+	return patches, nil
136+}
137+
138+func (cmd PrCmd) createPatch(tx *sqlx.Tx, patch *Patch) (int64, error) {
139+	var patchID int64
140+	row := tx.QueryRow(
141+		"INSERT INTO patches (pubkey, patch_request_id, author_name, author_email, author_date, title, body, body_appendix, commit_sha, content_sha, raw_text) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
142+		patch.Pubkey,
143+		patch.PatchRequestID,
144+		patch.AuthorName,
145+		patch.AuthorEmail,
146+		patch.AuthorDate,
147+		patch.Title,
148+		patch.Body,
149+		patch.BodyAppendix,
150+		patch.ContentSha,
151+		patch.CommitSha,
152+		patch.RawText,
153 	)
154-	err = row.Scan(&patchID)
155+	err := row.Scan(&patchID)
156 	if err != nil {
157-		return nil, err
158+		return 0, err
159 	}
160-
161-	var patchRec Patch
162-	err = cmd.Backend.DB.Get(&patchRec, "SELECT * FROM patches WHERE id=?", patchID)
163-	return &patchRec, err
164+	if patchID == 0 {
165+		return 0, fmt.Errorf("could not create patch request")
166+	}
167+	return patchID, err
168 }
169 
170-func (cmd PrCmd) SubmitPatchRequest(pubkey string, repoID string, patches io.Reader) (*PatchRequest, error) {
171+func (cmd PrCmd) SubmitPatchRequest(repoID int64, pubkey string, patchset io.Reader) (*PatchRequest, error) {
172 	tx, err := cmd.Backend.DB.Beginx()
173 	if err != nil {
174 		return nil, err
175@@ -158,20 +223,16 @@ func (cmd PrCmd) SubmitPatchRequest(pubkey string, repoID string, patches io.Rea
176 		}
177 	}()
178 
179-	// need to read io.Reader from session twice
180-	var buf bytes.Buffer
181-	tee := io.TeeReader(patches, &buf)
182-
183-	_, preamble, err := gitdiff.Parse(tee)
184+	patches, err := cmd.parsePatchSet(patchset)
185 	if err != nil {
186 		return nil, err
187 	}
188-	header, err := gitdiff.ParsePatchHeader(preamble)
189-	if err != nil {
190-		return nil, err
191+	prName := ""
192+	prText := ""
193+	if len(patches) > 0 {
194+		prName = patches[0].Title
195+		prText = patches[0].Body
196 	}
197-	prName := header.Title
198-	prDesc := header.Body
199 
200 	var prID int64
201 	row := tx.QueryRow(
202@@ -179,7 +240,7 @@ func (cmd PrCmd) SubmitPatchRequest(pubkey string, repoID string, patches io.Rea
203 		pubkey,
204 		repoID,
205 		prName,
206-		prDesc,
207+		prText,
208 		"open",
209 		time.Now(),
210 	)
211@@ -191,28 +252,13 @@ func (cmd PrCmd) SubmitPatchRequest(pubkey string, repoID string, patches io.Rea
212 		return nil, fmt.Errorf("could not create patch request")
213 	}
214 
215-	authorName := "Unknown"
216-	authorEmail := ""
217-	if header.Author != nil {
218-		authorName = header.Author.Name
219-		authorEmail = header.Author.Email
220-	}
221-
222-	_, err = tx.Exec(
223-		"INSERT INTO patches (pubkey, patch_request_id, author_name, author_email, author_date, title, body, body_appendix, commit_sha, raw_text) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
224-		pubkey,
225-		prID,
226-		authorName,
227-		authorEmail,
228-		header.AuthorDate,
229-		prName,
230-		prDesc,
231-		header.BodyAppendix,
232-		header.SHA,
233-		buf.String(),
234-	)
235-	if err != nil {
236-		return nil, err
237+	for _, patch := range patches {
238+		patch.Pubkey = pubkey
239+		patch.PatchRequestID = prID
240+		_, err = cmd.createPatch(tx, patch)
241+		if err != nil {
242+			return nil, err
243+		}
244 	}
245 
246 	err = tx.Commit()
247@@ -224,3 +270,38 @@ func (cmd PrCmd) SubmitPatchRequest(pubkey string, repoID string, patches io.Rea
248 	err = cmd.Backend.DB.Get(&pr, "SELECT * FROM patch_requests WHERE id=?", prID)
249 	return &pr, err
250 }
251+
252+func (cmd PrCmd) SubmitPatchSet(prID int64, pubkey string, review bool, patchset io.Reader) error {
253+	tx, err := cmd.Backend.DB.Beginx()
254+	if err != nil {
255+		return err
256+	}
257+
258+	defer func() {
259+		err := tx.Rollback()
260+		if err != nil {
261+			cmd.Backend.Logger.Error("rollback", "err", err)
262+		}
263+	}()
264+
265+	patches, err := cmd.parsePatchSet(patchset)
266+	if err != nil {
267+		return err
268+	}
269+
270+	for _, patch := range patches {
271+		patch.Pubkey = pubkey
272+		patch.PatchRequestID = prID
273+		_, err = cmd.createPatch(tx, patch)
274+		if err != nil {
275+			return err
276+		}
277+	}
278+
279+	err = tx.Commit()
280+	if err != nil {
281+		return err
282+	}
283+
284+	return err
285+}