mirror of
https://github.com/Pull-Pal/pull-pal.git
synced 2024-06-12 11:20:44 +00:00
Move issue parsing code into the vc package
Write some tests to verify the issue parsing code add additional option for specifying a base branch to use
This commit is contained in:
parent
ab7521477a
commit
7053a0b693
3
go.mod
3
go.mod
|
@ -8,6 +8,7 @@ require (
|
|||
github.com/sashabaranov/go-openai v1.9.0
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/spf13/viper v1.15.0
|
||||
github.com/stretchr/testify v1.8.1
|
||||
go.uber.org/zap v1.24.0
|
||||
golang.org/x/oauth2 v0.7.0
|
||||
)
|
||||
|
@ -17,6 +18,7 @@ require (
|
|||
github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect
|
||||
github.com/acomagu/bufpipe v1.0.4 // indirect
|
||||
github.com/cloudflare/circl v1.1.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/go-git/gcfg v1.5.0 // indirect
|
||||
|
@ -32,6 +34,7 @@ require (
|
|||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.0.6 // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/skeema/knownhosts v1.1.0 // indirect
|
||||
github.com/spf13/afero v1.9.3 // indirect
|
||||
|
|
|
@ -19,10 +19,11 @@ const (
|
|||
|
||||
// CodeChangeRequest contains all necessary information for generating a prompt for a LLM.
|
||||
type CodeChangeRequest struct {
|
||||
Files []File
|
||||
Subject string
|
||||
Body string
|
||||
IssueID string
|
||||
Files []File
|
||||
Subject string
|
||||
Body string
|
||||
IssueID string
|
||||
BaseBranch string
|
||||
}
|
||||
|
||||
// CodeChangeResponse contains data derived from an LLM response to a prompt generated via a CodeChangeRequest.
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/mobyvb/pull-pal/llm"
|
||||
|
@ -74,6 +73,7 @@ func (p *PullPal) Run() error {
|
|||
issue := issues[0]
|
||||
err = p.handleIssue(issue)
|
||||
if err != nil {
|
||||
// TODO leave comment if error (make configurable)
|
||||
p.log.Error("error handling issue", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
@ -95,6 +95,7 @@ func (p *PullPal) Run() error {
|
|||
comment := comments[0]
|
||||
err = p.handleComment(comment)
|
||||
if err != nil {
|
||||
// TODO leave comment if error (make configurable)
|
||||
p.log.Error("error handling comment", zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
@ -112,7 +113,7 @@ func (p *PullPal) handleIssue(issue vc.Issue) error {
|
|||
return err
|
||||
}
|
||||
|
||||
err = p.ghClient.CommentOnIssue(issueNumber, "on it")
|
||||
err = p.ghClient.CommentOnIssue(issueNumber, "working on it")
|
||||
if err != nil {
|
||||
p.log.Error("error commenting on issue", zap.Error(err))
|
||||
return err
|
||||
|
@ -125,51 +126,14 @@ func (p *PullPal) handleIssue(issue vc.Issue) error {
|
|||
}
|
||||
}
|
||||
|
||||
// remove file list from issue body
|
||||
// TODO do this better and probably somewhere else
|
||||
parts := strings.Split(issue.Body, "Files:")
|
||||
issue.Body = parts[0]
|
||||
|
||||
fileList := []string{}
|
||||
if len(parts) > 1 {
|
||||
fileList = strings.Split(parts[1], ",")
|
||||
}
|
||||
|
||||
// get file contents from local git repository
|
||||
files := []llm.File{}
|
||||
for _, path := range fileList {
|
||||
path = strings.TrimSpace(path)
|
||||
nextFile, err := p.localGitClient.GetLocalFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
files = append(files, nextFile)
|
||||
}
|
||||
|
||||
changeRequest := llm.CodeChangeRequest{
|
||||
Subject: issue.Subject,
|
||||
Body: issue.Body,
|
||||
IssueID: issue.ID,
|
||||
Files: files,
|
||||
changeRequest, err := p.localGitClient.ParseIssueAndStartCommit(issue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
changeResponse, err := p.openAIClient.EvaluateCCR(p.ctx, "", changeRequest)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
}
|
||||
|
||||
// create commit with file changes
|
||||
err = p.localGitClient.StartCommit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// todo remove hardcoded main
|
||||
p.log.Info("checking out main branch")
|
||||
err = p.localGitClient.CheckoutRemoteBranch("main")
|
||||
if err != nil {
|
||||
p.log.Info("error checking out main branch", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
|
||||
newBranchName := fmt.Sprintf("fix-%s", changeRequest.IssueID)
|
||||
|
@ -197,7 +161,7 @@ func (p *PullPal) handleIssue(issue vc.Issue) error {
|
|||
|
||||
// open code change request
|
||||
// TODO don't hardcode main branch, make configurable
|
||||
_, url, err := p.ghClient.OpenCodeChangeRequest(changeRequest, changeResponse, newBranchName, "main")
|
||||
_, url, err := p.ghClient.OpenCodeChangeRequest(changeRequest, changeResponse, newBranchName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
44
vc/common.go
44
vc/common.go
|
@ -2,6 +2,7 @@ package vc
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
)
|
||||
|
@ -81,3 +82,46 @@ func (repo Repository) SSH() string {
|
|||
func (repo Repository) HTTPS() string {
|
||||
return fmt.Sprintf("https://%s/%s/%s.git", repo.HostDomain, repo.Owner.Handle, repo.Name)
|
||||
}
|
||||
|
||||
type IssueBody struct {
|
||||
PromptBody string
|
||||
FilePaths []string
|
||||
BaseBranch string
|
||||
}
|
||||
|
||||
func ParseIssueBody(body string) IssueBody {
|
||||
issueBody := IssueBody{
|
||||
BaseBranch: "main",
|
||||
}
|
||||
divider := "---"
|
||||
|
||||
parts := strings.Split(body, divider)
|
||||
issueBody.PromptBody = strings.TrimSpace(parts[0])
|
||||
// if there was nothing to split, no additional configuration was provided
|
||||
if len(parts) <= 1 {
|
||||
return issueBody
|
||||
}
|
||||
|
||||
configStr := parts[1]
|
||||
configLines := strings.Split(configStr, "\n")
|
||||
for _, line := range configLines {
|
||||
lineParts := strings.Split(line, ":")
|
||||
if len(lineParts) < 2 {
|
||||
continue
|
||||
}
|
||||
key := strings.ToLower(strings.TrimSpace(lineParts[0]))
|
||||
if key == "base" {
|
||||
issueBody.BaseBranch = strings.TrimSpace(lineParts[1])
|
||||
continue
|
||||
}
|
||||
if key == "files" {
|
||||
filePaths := strings.Split(lineParts[1], ",")
|
||||
for _, p := range filePaths {
|
||||
issueBody.FilePaths = append(issueBody.FilePaths, strings.TrimSpace(p))
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
return issueBody
|
||||
}
|
||||
|
|
99
vc/common_test.go
Normal file
99
vc/common_test.go
Normal file
|
@ -0,0 +1,99 @@
|
|||
package vc_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mobyvb/pull-pal/vc"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestParseIssueBody(t *testing.T) {
|
||||
var testCases = []struct {
|
||||
testcase string
|
||||
body string
|
||||
parsed vc.IssueBody
|
||||
}{
|
||||
{
|
||||
"simple issue",
|
||||
`
|
||||
add an html file
|
||||
`,
|
||||
vc.IssueBody{
|
||||
PromptBody: "add an html file",
|
||||
BaseBranch: "main",
|
||||
},
|
||||
},
|
||||
{
|
||||
"issue with explicit file list",
|
||||
`
|
||||
add an html file
|
||||
and also a go file
|
||||
read a readme file too
|
||||
|
||||
---
|
||||
|
||||
FiLeS: index.html, README.md ,main.go
|
||||
`,
|
||||
vc.IssueBody{
|
||||
PromptBody: "add an html file\nand also a go file\nread a readme file too",
|
||||
BaseBranch: "main",
|
||||
FilePaths: []string{"index.html", "README.md", "main.go"},
|
||||
},
|
||||
},
|
||||
{
|
||||
"issue with a custom base branch",
|
||||
`
|
||||
add an html file
|
||||
---
|
||||
base: some-base-branch
|
||||
`,
|
||||
vc.IssueBody{
|
||||
PromptBody: "add an html file",
|
||||
BaseBranch: "some-base-branch",
|
||||
},
|
||||
},
|
||||
{
|
||||
"issue with an explicit base branch and file list",
|
||||
`
|
||||
add an html file
|
||||
---
|
||||
base: some-base-branch
|
||||
files: index.html, main.go
|
||||
`,
|
||||
vc.IssueBody{
|
||||
PromptBody: "add an html file",
|
||||
BaseBranch: "some-base-branch",
|
||||
FilePaths: []string{"index.html", "main.go"},
|
||||
},
|
||||
},
|
||||
{
|
||||
"issue with garbage in config section",
|
||||
`
|
||||
add an html file
|
||||
---
|
||||
asdf:
|
||||
files: index.html, main.go
|
||||
: asdfsadf
|
||||
base: some-base-branch
|
||||
asdfjljldsfj
|
||||
nonexistentoption: asdf
|
||||
`,
|
||||
vc.IssueBody{
|
||||
PromptBody: "add an html file",
|
||||
BaseBranch: "some-base-branch",
|
||||
FilePaths: []string{"index.html", "main.go"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range testCases {
|
||||
t.Log("testing case:", tt.testcase)
|
||||
parsed := vc.ParseIssueBody(tt.body)
|
||||
require.Equal(t, tt.parsed.PromptBody, parsed.PromptBody)
|
||||
require.Equal(t, tt.parsed.BaseBranch, parsed.BaseBranch)
|
||||
require.Equal(t, len(tt.parsed.FilePaths), len(parsed.FilePaths))
|
||||
for i, p := range tt.parsed.FilePaths {
|
||||
require.Equal(t, p, parsed.FilePaths[i])
|
||||
}
|
||||
}
|
||||
}
|
41
vc/git.go
41
vc/git.go
|
@ -219,3 +219,44 @@ func (gc *LocalGitClient) FinishCommit(message string) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseIssueAndStartCommit parses the information provided in the issue to check out the appropriate branch,
|
||||
// get the contents of the files mentioned in the issue, and initialize the worktree.
|
||||
func (gc *LocalGitClient) ParseIssue(issue Issue) (llm.CodeChangeRequest, error) {
|
||||
var changeRequest llm.CodeChangeRequest
|
||||
|
||||
if gc.worktree != nil {
|
||||
return changeRequest, errors.New("worktree is active - some other work is incomplete")
|
||||
}
|
||||
|
||||
issueBody := ParseIssueBody(issue.Body)
|
||||
|
||||
// start a worktree
|
||||
err := gc.StartCommit()
|
||||
if err != nil {
|
||||
return changeRequest, err
|
||||
}
|
||||
|
||||
err = gc.CheckoutRemoteBranch(issueBody.BaseBranch)
|
||||
if err != nil {
|
||||
return changeRequest, err
|
||||
}
|
||||
|
||||
// get file contents from local git repository
|
||||
files := []llm.File{}
|
||||
for _, path := range issueBody.FilePaths {
|
||||
nextFile, err := gc.GetLocalFile(path)
|
||||
if err != nil {
|
||||
return changeRequest, err
|
||||
}
|
||||
files = append(files, nextFile)
|
||||
}
|
||||
|
||||
return llm.CodeChangeRequest{
|
||||
Subject: issue.Subject,
|
||||
Body: issueBody.PromptBody,
|
||||
IssueID: issue.ID,
|
||||
Files: files,
|
||||
BaseBranch: issueBody.BaseBranch,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ func NewGithubClient(ctx context.Context, log *zap.Logger, self Author, repo Rep
|
|||
}
|
||||
|
||||
// OpenCodeChangeRequest pushes to a new remote branch and opens a PR on Github.
|
||||
func (gc *GithubClient) OpenCodeChangeRequest(req llm.CodeChangeRequest, res llm.CodeChangeResponse, fromBranch, toBranch string) (id, url string, err error) {
|
||||
func (gc *GithubClient) OpenCodeChangeRequest(req llm.CodeChangeRequest, res llm.CodeChangeResponse, fromBranch string) (id, url string, err error) {
|
||||
// TODO handle gc.ctx canceled
|
||||
|
||||
title := req.Subject
|
||||
|
@ -63,7 +63,7 @@ func (gc *GithubClient) OpenCodeChangeRequest(req llm.CodeChangeRequest, res llm
|
|||
pr, _, err := gc.client.PullRequests.Create(gc.ctx, gc.repo.Owner.Handle, gc.repo.Name, &github.NewPullRequest{
|
||||
Title: &title,
|
||||
Head: &fromBranch,
|
||||
Base: &toBranch,
|
||||
Base: &req.BaseBranch,
|
||||
Body: &body,
|
||||
})
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in New Issue
Block a user