repos / git-pr

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

commit
84dd266
parent
1824e58
author
Eric Bower
date
2024-07-14 08:12:55 -0400 EDT
refactor: move patch parsing code
4 files changed,  +232, -107
M pr.go
A fixtures/with-cover.patch
+87, -0
 1@@ -0,0 +1,87 @@
 2+From 71a58613493dd0211a576ed6531c5ee712a03ff3 Mon Sep 17 00:00:00 2001
 3+From: Eric Bower <me@erock.io>
 4+Date: Sun, 14 Jul 2024 07:14:44 -0400
 5+Subject: [PATCH 0/2] Add torch deps
 6+
 7+I took the liberty of adding a requirements file for python.
 8+
 9+Bob Sour (1):
10+  chore: add torch to requirements
11+
12+Eric Bower (1):
13+  feat: lets build an rnn
14+
15+ README.md        | 4 ++--
16+ requirements.txt | 1 +
17+ train.py         | 5 +++++
18+ 3 files changed, 8 insertions(+), 2 deletions(-)
19+ create mode 100644 requirements.txt
20+ create mode 100644 train.py
21+
22+-- 
23+2.45.2
24+
25+From 59456574a0bfee9f71c91c13046173c820152346 Mon Sep 17 00:00:00 2001
26+From: Eric Bower <me@erock.io>
27+Date: Wed, 3 Jul 2024 15:18:47 -0400
28+Subject: [PATCH 1/2] feat: lets build an rnn
29+
30+---
31+ README.md | 4 ++--
32+ train.py  | 2 ++
33+ 2 files changed, 4 insertions(+), 2 deletions(-)
34+ create mode 100644 train.py
35+
36+diff --git a/README.md b/README.md
37+index 586bc0d..8f3a780 100644
38+--- a/README.md
39++++ b/README.md
40+@@ -1,3 +1,3 @@
41+-# test
42++# Let's build an RNN
43+ 
44+-testing git pr
45++This repo demonstrates building an RNN using `pytorch`
46+diff --git a/train.py b/train.py
47+new file mode 100644
48+index 0000000..5c027f4
49+--- /dev/null
50++++ b/train.py
51+@@ -0,0 +1,2 @@
52++if __name__ == "__main__":
53++    print("train!")
54+-- 
55+2.45.2
56+
57+
58+From 71a58613493dd0211a576ed6531c5ee712a03ff3 Mon Sep 17 00:00:00 2001
59+From: Bob Sour <bob@bower.sh>
60+Date: Fri, 5 Jul 2024 18:57:03 +0000
61+Subject: [PATCH 2/2] chore: add torch to requirements
62+
63+---
64+ requirements.txt | 1 +
65+ train.py         | 3 +++
66+ 2 files changed, 4 insertions(+)
67+ create mode 100644 requirements.txt
68+
69+diff --git a/requirements.txt b/requirements.txt
70+new file mode 100644
71+index 0000000..4968a39
72+--- /dev/null
73++++ b/requirements.txt
74+@@ -0,0 +1 @@
75++torch==2.3.1
76+diff --git a/train.py b/train.py
77+index 5c027f4..8f32fe2 100644
78+--- a/train.py
79++++ b/train.py
80+@@ -1,2 +1,5 @@
81++import torch
82++
83+ if __name__ == "__main__":
84+     print("train!")
85++    print(torch.rand(6,6))
86+-- 
87+2.45.2
88+
M pr.go
+2, -107
  1@@ -1,17 +1,11 @@
  2 package git
  3 
  4 import (
  5-	"crypto/sha256"
  6-	"database/sql"
  7-	"encoding/hex"
  8 	"errors"
  9 	"fmt"
 10 	"io"
 11-	"regexp"
 12-	"strings"
 13 	"time"
 14 
 15-	"github.com/bluekeyes/go-gitdiff/gitdiff"
 16 	"github.com/jmoiron/sqlx"
 17 )
 18 
 19@@ -336,105 +330,6 @@ func (cmd PrCmd) CreateEventLog(eventLog EventLog) error {
 20 	return err
 21 }
 22 
 23-// calcContentSha calculates a shasum containing the important content
 24-// changes related to a patch.
 25-// We cannot rely on patch.CommitSha because it includes the commit date
 26-// that will change when a user fetches and applies the patch locally.
 27-func (cmd PrCmd) calcContentSha(diffFiles []*gitdiff.File, header *gitdiff.PatchHeader) string {
 28-	authorName := ""
 29-	authorEmail := ""
 30-	if header.Author != nil {
 31-		authorName = header.Author.Name
 32-		authorEmail = header.Author.Email
 33-	}
 34-	content := fmt.Sprintf(
 35-		"%s\n%s\n%s\n%s\n%s\n",
 36-		header.Title,
 37-		header.Body,
 38-		authorName,
 39-		authorEmail,
 40-		header.AuthorDate,
 41-	)
 42-	for _, diff := range diffFiles {
 43-		dff := fmt.Sprintf(
 44-			"%s->%s %s..%s %s->%s\n",
 45-			diff.OldName, diff.NewName,
 46-			diff.OldOIDPrefix, diff.NewOIDPrefix,
 47-			diff.OldMode.String(), diff.NewMode.String(),
 48-		)
 49-		content += dff
 50-	}
 51-	sha := sha256.Sum256([]byte(content))
 52-	shaStr := hex.EncodeToString(sha[:])
 53-	return shaStr
 54-}
 55-
 56-func splitPatchSet(patchset string) []string {
 57-	return strings.Split(patchset, "\n\n\n")
 58-}
 59-
 60-func findBaseCommit(patch string) string {
 61-	re := regexp.MustCompile(`base-commit: (.+)\s*`)
 62-	strs := re.FindStringSubmatch(patch)
 63-	baseCommit := ""
 64-	if len(strs) > 1 {
 65-		baseCommit = strs[1]
 66-	}
 67-	return baseCommit
 68-}
 69-
 70-func (cmd PrCmd) parsePatchSet(patchset io.Reader) ([]*Patch, error) {
 71-	patches := []*Patch{}
 72-	buf := new(strings.Builder)
 73-	_, err := io.Copy(buf, patchset)
 74-	if err != nil {
 75-		return nil, err
 76-	}
 77-
 78-	patchesRaw := splitPatchSet(buf.String())
 79-	for _, patchRaw := range patchesRaw {
 80-		reader := strings.NewReader(patchRaw)
 81-		diffFiles, preamble, err := gitdiff.Parse(reader)
 82-		if err != nil {
 83-			return nil, err
 84-		}
 85-		header, err := gitdiff.ParsePatchHeader(preamble)
 86-		if err != nil {
 87-			return nil, err
 88-		}
 89-
 90-		if len(diffFiles) == 0 {
 91-			cmd.Backend.Logger.Info("not diff files found for patch, skipping")
 92-			continue
 93-		}
 94-
 95-		baseCommit := findBaseCommit(patchRaw)
 96-		authorName := "Unknown"
 97-		authorEmail := ""
 98-		if header.Author != nil {
 99-			authorName = header.Author.Name
100-			authorEmail = header.Author.Email
101-		}
102-
103-		contentSha := cmd.calcContentSha(diffFiles, header)
104-
105-		patches = append(patches, &Patch{
106-			AuthorName:    authorName,
107-			AuthorEmail:   authorEmail,
108-			AuthorDate:    header.AuthorDate.UTC().String(),
109-			Title:         header.Title,
110-			Body:          header.Body,
111-			BodyAppendix:  header.BodyAppendix,
112-			CommitSha:     header.SHA,
113-			ContentSha:    contentSha,
114-			RawText:       patchRaw,
115-			BaseCommitSha: sql.NullString{String: baseCommit},
116-		})
117-	}
118-
119-	return patches, nil
120-}
121-
122 func (cmd PrCmd) createPatch(tx *sqlx.Tx, review bool, patch *Patch) (int64, error) {
123 	patchExists := []Patch{}
124 	_ = cmd.Backend.DB.Select(&patchExists, "SELECT * FROM patches WHERE patch_request_id=? AND content_sha=?", patch.PatchRequestID, patch.ContentSha)
125@@ -479,7 +374,7 @@ func (cmd PrCmd) SubmitPatchRequest(repoID string, userID int64, patchset io.Rea
126 		_ = tx.Rollback()
127 	}()
128 
129-	patches, err := cmd.parsePatchSet(patchset)
130+	patches, err := parsePatchSet(patchset)
131 	if err != nil {
132 		return nil, err
133 	}
134@@ -549,7 +444,7 @@ func (cmd PrCmd) SubmitPatchSet(prID int64, userID int64, op PatchsetOp, patchse
135 		_ = tx.Rollback()
136 	}()
137 
138-	patches, err := cmd.parsePatchSet(patchset)
139+	patches, err := parsePatchSet(patchset)
140 	if err != nil {
141 		return fin, err
142 	}
M util.go
+109, -0
  1@@ -1,15 +1,22 @@
  2 package git
  3 
  4 import (
  5+	"crypto/sha256"
  6+	"database/sql"
  7+	"encoding/hex"
  8 	"fmt"
  9+	"io"
 10 	"math/rand"
 11+	"regexp"
 12 	"strconv"
 13 	"strings"
 14 
 15+	"github.com/bluekeyes/go-gitdiff/gitdiff"
 16 	"github.com/charmbracelet/ssh"
 17 )
 18 
 19 var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
 20+var startOfPatch = "From "
 21 
 22 // https://stackoverflow.com/a/22892986
 23 func randSeq(n int) string {
 24@@ -104,6 +111,108 @@ func filterPatches(ranger *Ranger, patches []*Patch) []*Patch {
 25 	return patches[ranger.Left:ranger.Right]
 26 }
 27 
 28+func splitPatchSet(patchset string) []string {
 29+	return strings.Split(patchset, "\n"+startOfPatch)
 30+}
 31+
 32+func findBaseCommit(patch string) string {
 33+	re := regexp.MustCompile(`base-commit: (.+)\s*`)
 34+	strs := re.FindStringSubmatch(patch)
 35+	baseCommit := ""
 36+	if len(strs) > 1 {
 37+		baseCommit = strs[1]
 38+	}
 39+	return baseCommit
 40+}
 41+
 42+func parsePatchSet(patchset io.Reader) ([]*Patch, error) {
 43+	patches := []*Patch{}
 44+	buf := new(strings.Builder)
 45+	_, err := io.Copy(buf, patchset)
 46+	if err != nil {
 47+		return nil, err
 48+	}
 49+
 50+	patchesRaw := splitPatchSet(buf.String())
 51+	for idx, patchRaw := range patchesRaw {
 52+		patchStr := patchRaw
 53+		if idx > 0 {
 54+			patchStr = startOfPatch + patchRaw
 55+		}
 56+		reader := strings.NewReader(patchStr)
 57+		diffFiles, preamble, err := gitdiff.Parse(reader)
 58+		if err != nil {
 59+			return nil, err
 60+		}
 61+		header, err := gitdiff.ParsePatchHeader(preamble)
 62+		if err != nil {
 63+			return nil, err
 64+		}
 65+
 66+		baseCommit := findBaseCommit(patchRaw)
 67+		authorName := "Unknown"
 68+		authorEmail := ""
 69+		if header.Author != nil {
 70+			authorName = header.Author.Name
 71+			authorEmail = header.Author.Email
 72+		}
 73+
 74+		if len(diffFiles) == 0 {
 75+			continue
 76+		}
 77+
 78+		contentSha := calcContentSha(diffFiles, header)
 79+
 80+		patches = append(patches, &Patch{
 81+			AuthorName:    authorName,
 82+			AuthorEmail:   authorEmail,
 83+			AuthorDate:    header.AuthorDate.UTC().String(),
 84+			Title:         header.Title,
 85+			Body:          header.Body,
 86+			BodyAppendix:  header.BodyAppendix,
 87+			CommitSha:     header.SHA,
 88+			ContentSha:    contentSha,
 89+			RawText:       patchRaw,
 90+			BaseCommitSha: sql.NullString{String: baseCommit},
 91+		})
 92+	}
 93+
 94+	return patches, nil
 95+}
 96+
 97+// calcContentSha calculates a shasum containing the important content
 98+// changes related to a patch.
 99+// We cannot rely on patch.CommitSha because it includes the commit date
100+// that will change when a user fetches and applies the patch locally.
101+func calcContentSha(diffFiles []*gitdiff.File, header *gitdiff.PatchHeader) string {
102+	authorName := ""
103+	authorEmail := ""
104+	if header.Author != nil {
105+		authorName = header.Author.Name
106+		authorEmail = header.Author.Email
107+	}
108+	content := fmt.Sprintf(
109+		"%s\n%s\n%s\n%s\n%s\n",
110+		header.Title,
111+		header.Body,
112+		authorName,
113+		authorEmail,
114+		header.AuthorDate,
115+	)
116+	for _, diff := range diffFiles {
117+		dff := fmt.Sprintf(
118+			"%s->%s %s..%s %s->%s\n",
119+			diff.OldName, diff.NewName,
120+			diff.OldOIDPrefix, diff.NewOIDPrefix,
121+			diff.OldMode.String(), diff.NewMode.String(),
122+		)
123+		content += dff
124+	}
125+	sha := sha256.Sum256([]byte(content))
126+	shaStr := hex.EncodeToString(sha[:])
127+	return shaStr
128+}
129+
130 /* func gitServiceCommands(sesh ssh.Session, be *Backend, cmd, repoName string) error {
131 	name := utils.SanitizeRepo(repoName)
132 	// git bare repositories should end in ".git"
A util_test.go
+34, -0
 1@@ -0,0 +1,34 @@
 2+package git
 3+
 4+import (
 5+	"os"
 6+	"testing"
 7+)
 8+
 9+func TestParsePatchsetWithCover(t *testing.T) {
10+	file, err := os.Open("fixtures/with-cover.patch")
11+	defer func() {
12+		_ = file.Close()
13+	}()
14+	if err != nil {
15+		t.Fatalf(err.Error())
16+	}
17+	actual, err := parsePatchSet(file)
18+	if err != nil {
19+		t.Fatalf(err.Error())
20+	}
21+	expected := []*Patch{
22+		// {Title: "Add torch deps"},
23+		{Title: "feat: lets build an rnn"},
24+		{Title: "chore: add torch to requirements"},
25+	}
26+	if len(actual) != len(expected) {
27+		t.Fatalf("patches not same length (expected:%d, actual:%d)\n", len(expected), len(actual))
28+	}
29+	for idx, act := range actual {
30+		exp := expected[idx]
31+		if exp.Title != act.Title {
32+			t.Fatalf("title does not match expected (expected:%s, actual:%s)", exp.Title, act.Title)
33+		}
34+	}
35+}