Skip to content

Commit e761919

Browse files
committed
WIP refactor
1 parent 2074e97 commit e761919

File tree

6 files changed

+187
-149
lines changed

6 files changed

+187
-149
lines changed

.github/workflows/cr.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Code Review
2+
3+
permissions:
4+
contents: read
5+
pull-requests: write
6+
7+
on:
8+
pull_request:
9+
types: [opened, reopened, synchronize]
10+
11+
jobs:
12+
test:
13+
# if: ${{ contains(github.event.*.labels.*.name, 'gpt review') }} # Optional; to run only when a label is attached
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: anc95/ChatGPT-CodeReview@main
17+
env:
18+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
19+
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}

github.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package main
2+
3+
import (
4+
"context"
5+
6+
"github.com/google/go-github/v51/github"
7+
"golang.org/x/oauth2"
8+
)
9+
10+
const baseGithubURL = "https://api.github.com"
11+
12+
type GithubClient struct {
13+
client *github.Client
14+
}
15+
16+
func NewGithubClient(ctx context.Context, token string) *GithubClient {
17+
ts := oauth2.StaticTokenSource(
18+
&oauth2.Token{AccessToken: token},
19+
)
20+
tc := oauth2.NewClient(ctx, ts)
21+
22+
return &GithubClient{github.NewClient(tc)}
23+
}
24+
25+
func (g *GithubClient) GetPullRequestFiles(ctx context.Context, owner, repo string, prNumber int) ([]*github.CommitFile, error) {
26+
files, _, err := g.client.PullRequests.ListFiles(ctx, owner, repo, prNumber, nil)
27+
return files, err
28+
}
29+
30+
func (g *GithubClient) GetPullRequest(ctx context.Context, owner, repo string, number int) (*github.PullRequest, error) {
31+
pr, _, err := g.client.PullRequests.Get(ctx, owner, repo, number)
32+
return pr, err
33+
}
34+
35+
func (g *GithubClient) GetPullRequestDiff(ctx context.Context, owner, repo string, number int) (string, error) {
36+
diff, _, err := g.client.PullRequests.GetRaw(ctx, owner, repo, number, github.RawOptions{Type: github.Diff})
37+
return diff, err
38+
}
39+
40+
func (g *GithubClient) UpdatePullRequest(ctx context.Context, owner, repo string, number int, pr *github.PullRequest) (*github.PullRequest, error) {
41+
updatedPR, _, err := g.client.PullRequests.Edit(ctx, owner, repo, number, pr)
42+
return updatedPR, err
43+
}

go.mod

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,20 @@ module github.com/ravilushqa/gpt-pullrequest-updater
33
go 1.19
44

55
require (
6+
github.com/google/go-github/v51 v51.0.0
67
github.com/jessevdk/go-flags v1.5.0
78
github.com/sashabaranov/go-openai v1.7.0
89
)
910

10-
require golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 // indirect
11+
require (
12+
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
13+
github.com/cloudflare/circl v1.1.0 // indirect
14+
github.com/golang/protobuf v1.5.2 // indirect
15+
github.com/google/go-querystring v1.1.0 // indirect
16+
golang.org/x/crypto v0.7.0 // indirect
17+
golang.org/x/net v0.8.0 // indirect
18+
golang.org/x/oauth2 v0.6.0 // indirect
19+
golang.org/x/sys v0.6.0 // indirect
20+
google.golang.org/appengine v1.6.7 // indirect
21+
google.golang.org/protobuf v1.28.0 // indirect
22+
)

go.sum

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,49 @@
1+
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA=
2+
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g=
3+
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
4+
github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY=
5+
github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
6+
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
7+
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
8+
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
9+
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
10+
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
11+
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
12+
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
13+
github.com/google/go-github/v51 v51.0.0 h1:KCjsbgPV28VoRftdP+K2mQL16jniUsLAJknsOVKwHyU=
14+
github.com/google/go-github/v51 v51.0.0/go.mod h1:kZj/rn/c1lSUbr/PFWl2hhusPV7a5XNYKcwPrd5L3Us=
15+
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
16+
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
117
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
218
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
319
github.com/sashabaranov/go-openai v1.7.0 h1:D1dBXoZhtf/aKNu6WFf0c7Ah2NM30PZ/3Mqly6cZ7fk=
420
github.com/sashabaranov/go-openai v1.7.0/go.mod h1:lj5b/K+zjTSFxVLijLSTDZuP7adOgerWeFyZLUhAKRg=
5-
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 h1:EZ2mChiOa8udjfp6rRmswTbtZN/QzUQp4ptM4rnjHvc=
21+
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
22+
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
23+
golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
24+
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
25+
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
26+
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
27+
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
28+
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
29+
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
30+
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
31+
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
32+
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
633
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
34+
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
35+
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
36+
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
37+
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
38+
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
39+
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
40+
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
41+
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
42+
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
43+
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
44+
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
45+
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
46+
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
47+
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
48+
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
49+
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=

main.go

Lines changed: 29 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
package main
22

33
import (
4-
"bytes"
54
"context"
6-
"encoding/json"
75
"fmt"
8-
"io"
9-
"net/http"
106
"os"
117
"strings"
128

9+
"github.com/google/go-github/v51/github"
1310
"github.com/jessevdk/go-flags"
1411
"github.com/sashabaranov/go-openai"
1512
)
@@ -37,116 +34,87 @@ func main() {
3734
}
3835
os.Exit(0)
3936
}
40-
openaiClient := openai.NewClient(opts.OpenAIToken)
37+
openaiClient := NewOpenAIClient(opts.OpenAIToken)
38+
githubClient := NewGithubClient(context.Background(), opts.GithubToken)
4139

42-
diff, err := getDiffContent(opts.GithubToken, opts.Owner, opts.Repo, opts.PRNumber)
40+
diff, err := githubClient.GetPullRequestDiff(context.Background(), opts.Owner, opts.Repo, opts.PRNumber)
4341
if err != nil {
44-
fmt.Printf("Error fetching diff content: %v\n", err)
42+
fmt.Printf("Error getting pull request diff: %v\n", err)
4543
return
4644
}
47-
filesDiff, err := parseGitDiffAndSplitPerFile(diff)
45+
filesDiff, err := parseGitDiffAndSplitPerFile(diff, strings.Split(opts.SkipFiles, ","))
4846
if err != nil {
4947
return
5048
}
5149

5250
var messages []openai.ChatCompletionMessage
53-
prompt := fmt.Sprintf(
54-
"Generate a GitHub pull request description based on the following changes " +
55-
"without basic prefix in markdown format with ###Description and ###Changes blocks:\n",
56-
)
51+
prompt := fmt.Sprintf("Generate a GitHub pull request description based on the following changes without basic prefix in markdown format with ###Description and ###Changes blocks:\n")
5752
messages = append(messages, openai.ChatCompletionMessage{
5853
Role: openai.ChatMessageRoleUser,
5954
Content: prompt,
6055
})
6156
for _, fileDiff := range filesDiff {
6257
fileName := getFilenameFromDiffHeader(fileDiff.Header)
6358

64-
isSkipped := false
65-
for _, skipFile := range strings.Split(opts.SkipFiles, ",") {
66-
if strings.Contains(fileName, skipFile) {
67-
isSkipped = true
68-
break
69-
}
70-
}
71-
if isSkipped {
72-
continue
73-
}
74-
7559
prompt := fmt.Sprintf("File %s:\n%s\n%s\n", fileName, fileDiff.Header, fileDiff.Diff)
7660
messages = append(messages, openai.ChatCompletionMessage{
7761
Role: openai.ChatMessageRoleUser,
7862
Content: prompt,
7963
})
8064
}
81-
chatGPTDescription, err := generatePRDescription(openaiClient, messages)
65+
chatGPTDescription, err := openaiClient.ChatCompletion(context.Background(), messages)
8266
if err != nil {
8367
fmt.Printf("Error generating pull request description: %v\n", err)
8468
return
8569
}
8670

87-
title, err := getPullRequestTitle(opts.GithubToken, opts.Owner, opts.Repo, opts.PRNumber)
71+
pr, err := githubClient.GetPullRequest(context.Background(), opts.Owner, opts.Repo, opts.PRNumber)
8872
if err != nil {
73+
fmt.Printf("Error getting pull request: %v\n", err)
8974
return
9075
}
9176

92-
jiraLink := generateJiraLinkByTitle(title)
77+
jiraLink := generateJiraLinkByTitle(*pr.Title)
9378

9479
description := fmt.Sprintf("### Jira\n%s\n%s", jiraLink, chatGPTDescription)
9580
if opts.Test {
9681
fmt.Println(description)
9782
os.Exit(0)
9883
}
9984
// Update the pull request with the generated description
100-
err = updatePullRequestDescription(opts.GithubToken, opts.Owner, opts.Repo, opts.PRNumber, description)
85+
_, err = githubClient.UpdatePullRequest(
86+
context.Background(), opts.Owner, opts.Repo, opts.PRNumber, &github.PullRequest{Body: &description},
87+
)
10188
if err != nil {
102-
fmt.Printf("Error updating pull request description: %v\n", err)
89+
fmt.Printf("Error updating pull request: %v\n", err)
10390
return
10491
}
105-
106-
fmt.Println("Pull request description updated successfully")
107-
}
108-
109-
func getDiffContent(token, owner, repo string, prNumber int) (string, error) {
110-
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/pulls/%d", owner, repo, prNumber)
111-
method := "GET"
112-
113-
client := &http.Client{}
114-
req, err := http.NewRequest(method, url, nil)
115-
116-
if err != nil {
117-
fmt.Println(err)
118-
return "", err
119-
}
120-
req.Header.Add("Accept", "application/vnd.github.v3.diff")
121-
req.Header.Add("Authorization", fmt.Sprintf("token %s", token))
122-
//req.Header.Add("Cookie", "logged_in=no")
123-
124-
res, err := client.Do(req)
125-
if err != nil {
126-
return "", err
127-
}
128-
defer res.Body.Close()
129-
130-
body, err := io.ReadAll(res.Body)
131-
if err != nil {
132-
return "", err
133-
}
134-
135-
return string(body), nil
13692
}
13793

13894
// parseGitDiffAndSplitPerFile parses a git diff and splits it into a slice of FileDiff.
139-
func parseGitDiffAndSplitPerFile(diff string) ([]FileDiff, error) {
95+
func parseGitDiffAndSplitPerFile(diff string, skipFiles []string) ([]FileDiff, error) {
14096
lines := strings.Split(diff, "\n")
14197
var fileDiffs []FileDiff
14298

143-
inFileDiff := false
99+
inFileDiff, isSkipFile := false, false
144100
var currentFileDiff FileDiff
145101
for _, line := range lines {
146102
if strings.HasPrefix(line, "diff --git") {
147103
if inFileDiff {
148104
fileDiffs = append(fileDiffs, currentFileDiff)
149105
}
106+
if len(skipFiles) > 0 {
107+
isSkipFile = false
108+
for _, skipFile := range skipFiles {
109+
if strings.Contains(line, skipFile) {
110+
isSkipFile = true
111+
break
112+
}
113+
}
114+
}
115+
if isSkipFile {
116+
continue
117+
}
150118
currentFileDiff = FileDiff{Header: line}
151119
inFileDiff = true
152120
} else if inFileDiff {
@@ -178,57 +146,6 @@ func getFilenameFromDiffHeader(diffHeader string) string {
178146
}
179147
}
180148

181-
func generatePRDescription(client *openai.Client, messages []openai.ChatCompletionMessage) (string, error) {
182-
resp, err := client.CreateChatCompletion(
183-
context.Background(),
184-
openai.ChatCompletionRequest{
185-
Model: openai.GPT3Dot5Turbo,
186-
Messages: messages,
187-
},
188-
)
189-
190-
if err != nil {
191-
fmt.Printf("ChatCompletion error: %v\n", err)
192-
return "", err
193-
}
194-
195-
return resp.Choices[0].Message.Content, nil
196-
}
197-
198-
func getPullRequestTitle(token, owner, repo string, prNumber int) (string, error) {
199-
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/pulls/%d", owner, repo, prNumber)
200-
201-
req, err := http.NewRequest("GET", url, nil)
202-
if err != nil {
203-
return "", err
204-
}
205-
206-
req.Header.Set("Authorization", fmt.Sprintf("token %s", token))
207-
req.Header.Set("Accept", "application/vnd.github.v3+json")
208-
209-
client := &http.Client{}
210-
resp, err := client.Do(req)
211-
if err != nil {
212-
return "", err
213-
}
214-
defer resp.Body.Close()
215-
216-
if resp.StatusCode != http.StatusOK {
217-
return "", fmt.Errorf("Failed to fetch pull request details. Status code: %d", resp.StatusCode)
218-
}
219-
220-
var pr struct {
221-
Title string `json:"title"`
222-
}
223-
224-
err = json.NewDecoder(resp.Body).Decode(&pr)
225-
if err != nil {
226-
return "", err
227-
}
228-
229-
return pr.Title, nil
230-
}
231-
232149
func generateJiraLinkByTitle(title string) string {
233150
//NCR-1234
234151
issueKey := strings.ToUpper(strings.Split(title, " ")[0])
@@ -239,38 +156,3 @@ func generateJiraLinkByTitle(title string) string {
239156

240157
return fmt.Sprintf("[%s](%s%s)", issueKey, jiraBaseURL, issueKey)
241158
}
242-
243-
func updatePullRequestDescription(token string, o string, r string, number int, description string) error {
244-
url := fmt.Sprintf("https://api.github.com/repos/%s/%s/pulls/%d", o, r, number)
245-
246-
data := map[string]string{
247-
"body": description,
248-
}
249-
250-
payload, err := json.Marshal(data)
251-
if err != nil {
252-
return err
253-
}
254-
255-
req, err := http.NewRequest("PATCH", url, bytes.NewBuffer(payload))
256-
if err != nil {
257-
return err
258-
}
259-
260-
req.Header.Set("Authorization", fmt.Sprintf("token %s", token))
261-
req.Header.Set("Accept", "application/vnd.github.v3+json")
262-
req.Header.Set("Content-Type", "application/json")
263-
264-
client := &http.Client{}
265-
resp, err := client.Do(req)
266-
if err != nil {
267-
return err
268-
}
269-
defer resp.Body.Close()
270-
271-
if resp.StatusCode != http.StatusOK {
272-
return fmt.Errorf("failed to update pull request description. Status code: %d", resp.StatusCode)
273-
}
274-
275-
return nil
276-
}

0 commit comments

Comments
 (0)