big cleanup

This commit is contained in:
Moby von Briesen 2023-05-04 19:58:11 -04:00
parent eea53db3a2
commit 0c69b2fd4c
16 changed files with 38 additions and 464 deletions

View File

@ -1,36 +0,0 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var listCommentsCmd = &cobra.Command{
Use: "list-comments",
Short: "Lists comments on a Github PR meeting the configured criteria",
Long: "Lists comments on a Github PR meeting the configured criteria",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
cfg := getConfig()
p, err := getPullPal(cmd.Context(), cfg)
if err != nil {
fmt.Println("error creating new pull pal", err)
return
}
fmt.Println("Successfully initialized pull pal")
prID := args[0]
issueList, err := p.ListComments(prID, cfg.usersToListenTo)
if err != nil {
fmt.Println("error listing issues", err)
return
}
fmt.Println(issueList)
},
}
func init() {
rootCmd.AddCommand(listCommentsCmd)
}

View File

@ -1,34 +0,0 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var listIssuesCmd = &cobra.Command{
Use: "list-issues",
Short: "Lists github issues meeting the configured criteria",
Long: "Lists github issues meeting the configured criteria",
Run: func(cmd *cobra.Command, args []string) {
cfg := getConfig()
p, err := getPullPal(cmd.Context(), cfg)
if err != nil {
fmt.Println("error creating new pull pal", err)
return
}
fmt.Println("Successfully initialized pull pal")
issueList, err := p.ListIssues(cfg.usersToListenTo, cfg.requiredIssueLabels)
if err != nil {
fmt.Println("error listing issues", err)
return
}
fmt.Println(issueList)
},
}
func init() {
rootCmd.AddCommand(listIssuesCmd)
}

View File

@ -1,43 +0,0 @@
package cmd
import (
"fmt"
"github.com/mobyvb/pull-pal/vc"
"github.com/spf13/cobra"
)
var localIssueCmd = &cobra.Command{
Use: "local-issue",
Short: "Processes a locally-defined/provided issue rather than remotely reading one from the Github repo",
// TODO csv filepath as arg?
// Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
cfg := getConfig()
p, err := getPullPal(cmd.Context(), cfg)
if err != nil {
fmt.Println("error creating new pull pal", err)
return
}
fmt.Println("Successfully initialized pull pal")
newIssue := vc.Issue{
Subject: "a few updates",
Body: "Add a quote from Frodo to the README.md and index.html files.\nSwitch main.go to port 7777.\nFiles:index.html,README.md,main.go",
Author: vc.Author{
Handle: "mobyvb",
},
}
err = p.MakeLocalChange(newIssue)
if err != nil {
fmt.Println("err making local change", err)
return
}
},
}
func init() {
rootCmd.AddCommand(localIssueCmd)
}

View File

@ -2,11 +2,9 @@ package cmd
import (
"context"
"errors"
"fmt"
"os"
"github.com/mobyvb/pull-pal/llm"
"github.com/mobyvb/pull-pal/pullpal"
"github.com/mobyvb/pull-pal/vc"
"go.uber.org/zap"
@ -30,8 +28,6 @@ type config struct {
// local paths
localRepoPath string
promptPath string
responsePath string
// program settings
promptToClipboard bool
@ -51,8 +47,6 @@ func getConfig() config {
repoName: viper.GetString("repo-name"),
localRepoPath: viper.GetString("local-repo-path"),
promptPath: viper.GetString("prompt-path"),
responsePath: viper.GetString("response-path"),
promptToClipboard: viper.GetBool("prompt-to-clipboard"),
usersToListenTo: viper.GetStringSlice("users-to-listen-to"),
@ -61,6 +55,7 @@ func getConfig() config {
}
func getPullPal(ctx context.Context, cfg config) (*pullpal.PullPal, error) {
// TODO figure out debug logging
log, err := zap.NewProduction()
if err != nil {
panic(err)
@ -91,16 +86,7 @@ func getPullPal(ctx context.Context, cfg config) (*pullpal.PullPal, error) {
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "pull-pal",
Short: "A bot that uses large language models to act as a collaborator on a git project",
Long: `A bot that uses large language models to act as a collaborator on a git project.
It can be used to:
* Monitor a repository for open issues, and generate LLM prompts according to the issue details
* Read an LLM response and process it into a new git commit and code change request on the version control server
`,
// Uncomment the following line if your bare application
// has an action associated with it:
Short: "run an automated digital assitant to monitor and make code changes to a github repository",
Run: func(cmd *cobra.Command, args []string) {
cfg := getConfig()
@ -111,67 +97,11 @@ It can be used to:
}
fmt.Println("Successfully initialized pull pal")
// TODO this loop breaks on the second iteration due to a weird git state or something
for {
var input string
fmt.Println("Press 'enter' when ready to select issue. Type 'exit' to exit.")
fmt.Scanln(&input)
if input == "exit" {
break
}
var issue vc.Issue
var changeRequest llm.CodeChangeRequest
if cfg.promptToClipboard {
issue, changeRequest, err = p.PickIssueToClipboard()
if err != nil {
if !errors.Is(err, pullpal.IssueNotFound) {
fmt.Println("error selecting issue and/or generating prompt", err)
return
} else {
fmt.Println("No issues found. Proceeding to parse prompt")
}
} else {
fmt.Printf("Picked issue and copied prompt to clipboard. Issue #%s\n", issue.ID)
}
} else {
issue, changeRequest, err = p.PickIssueToFile(cfg.promptPath)
if err != nil {
if !errors.Is(err, pullpal.IssueNotFound) {
fmt.Println("error selecting issue and/or generating prompt", err)
return
}
fmt.Println("No issues found. Proceeding to parse prompt")
} else {
fmt.Printf("Picked issue and copied prompt to clipboard. Issue #%s. Prompt location %s\n", issue.ID, cfg.promptPath)
}
}
fmt.Printf("\nInsert LLM response into response file: %s", cfg.responsePath)
fmt.Println("Press 'enter' when ready to parse response. Enter 'skip' to skip response parsing. Enter 'exit' to exit.")
fmt.Scanln(&input)
if input == "exit" {
break
}
if input == "skip" {
fmt.Println()
continue
}
/*
prURL, err := p.ProcessResponseFromFile(changeRequest, cfg.responsePath)
if err != nil {
fmt.Println("error parsing LLM response and/or making version control changes", err)
return
}
fmt.Printf("Successfully opened a code change request. Link: %s\n", prURL)
*/
_ = changeRequest
err = p.Run()
if err != nil {
fmt.Println("error running", err)
return
}
fmt.Println("Done. Thank you!")
},
}
@ -201,11 +131,8 @@ func init() {
rootCmd.PersistentFlags().StringP("repo-handle", "o", "REPO-HANDLE", "handle of repository's owner on version control server")
rootCmd.PersistentFlags().StringP("repo-name", "n", "REPO-NAME", "name of repository on version control server")
rootCmd.PersistentFlags().StringP("local-repo-path", "l", "/tmp/pullpalrepo/", "path where pull pal will check out a local copy of the repository")
rootCmd.PersistentFlags().StringP("prompt-path", "p", "./path/to/prompt.txt", "path where pull pal will write the llm prompt")
rootCmd.PersistentFlags().StringP("response-path", "r", "./path/to/response.txt", "path where pull pal will read the llm response from")
rootCmd.PersistentFlags().StringP("local-repo-path", "l", "/tmp/pullpallrepo", "local path to check out ephemeral repository in")
rootCmd.PersistentFlags().BoolP("prompt-to-clipboard", "c", false, "whether to copy LLM prompt to clipboard rather than using a file")
rootCmd.PersistentFlags().StringSliceP("users-to-listen-to", "a", []string{}, "a list of Github users that Pull Pal will respond to")
rootCmd.PersistentFlags().StringSliceP("required-issue-labels", "i", []string{}, "a list of labels that are required for Pull Pal to select an issue")
@ -219,10 +146,7 @@ func init() {
viper.BindPFlag("repo-name", rootCmd.PersistentFlags().Lookup("repo-name"))
viper.BindPFlag("local-repo-path", rootCmd.PersistentFlags().Lookup("local-repo-path"))
viper.BindPFlag("prompt-path", rootCmd.PersistentFlags().Lookup("prompt-path"))
viper.BindPFlag("response-path", rootCmd.PersistentFlags().Lookup("response-path"))
viper.BindPFlag("prompt-to-clipboard", rootCmd.PersistentFlags().Lookup("prompt-to-clipboard"))
viper.BindPFlag("users-to-listen-to", rootCmd.PersistentFlags().Lookup("users-to-listen-to"))
viper.BindPFlag("required-issue-labels", rootCmd.PersistentFlags().Lookup("required-issue-labels"))
}

View File

@ -1,32 +0,0 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var runCmd = &cobra.Command{
Use: "run",
Short: "Runs a fully automated pull pal service",
Run: func(cmd *cobra.Command, args []string) {
cfg := getConfig()
p, err := getPullPal(cmd.Context(), cfg)
if err != nil {
fmt.Println("error creating new pull pal", err)
return
}
fmt.Println("Successfully initialized pull pal")
err = p.Run()
if err != nil {
fmt.Println("error running", err)
return
}
},
}
func init() {
rootCmd.AddCommand(runCmd)
}

1
go.mod
View File

@ -3,7 +3,6 @@ module github.com/mobyvb/pull-pal
go 1.20
require (
github.com/atotto/clipboard v0.1.4
github.com/go-git/go-git/v5 v5.6.1
github.com/google/go-github v17.0.0+incompatible
github.com/sashabaranov/go-openai v1.9.0

2
go.sum
View File

@ -48,8 +48,6 @@ github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFI
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=

View File

@ -37,7 +37,7 @@ func (req CodeChangeRequest) MustGetPrompt() string {
// GetPrompt converts the information in the request to a prompt for an LLM.
func (req CodeChangeRequest) GetPrompt() (string, error) {
tmpl, err := template.ParseFiles("./llm/code-change-request.tmpl")
tmpl, err := template.ParseFiles("./llm/prompts/code-change-request.tmpl")
if err != nil {
return "", err
}
@ -72,7 +72,7 @@ func (res CodeChangeResponse) String() string {
// ParseCodeChangeResponse parses the LLM's response to CodeChangeRequest (string) into a CodeChangeResponse.
func ParseCodeChangeResponse(llmResponse string) CodeChangeResponse {
sections := strings.Split(llmResponse, "Notes:")
sections := strings.Split(llmResponse, "ppnotes:")
filesSection := ""
if len(sections) > 0 {
@ -93,7 +93,7 @@ func ParseCodeChangeResponse(llmResponse string) CodeChangeResponse {
// parseFiles process the "files" subsection of the LLM's response. It is a helper for GetCodeChangeResponse.
func parseFiles(filesSection string) []File {
fileStringList := strings.Split(filesSection, "name:")
fileStringList := strings.Split(filesSection, "ppname:")
if len(fileStringList) < 2 {
return []File{}
}
@ -107,7 +107,7 @@ func parseFiles(filesSection string) []File {
)
fileList := make([]File, len(fileStringList))
for i, f := range fileStringList {
fileParts := strings.Split(f, "contents:")
fileParts := strings.Split(f, "ppcontents:")
if len(fileParts) < 2 {
continue
}

View File

@ -23,10 +23,11 @@ func (oc *OpenAIClient) EvaluateCCR(ctx context.Context, req CodeChangeRequest)
resp, err := oc.client.CreateChatCompletion(
ctx,
openai.ChatCompletionRequest{
Model: openai.GPT3Dot5Turbo,
// TODO make model configurable
Model: openai.GPT4,
//Model: openai.GPT3Dot5Turbo,
Messages: []openai.ChatCompletionMessage{
{
// TODO is this the correct role for my prompts?
Role: openai.ChatMessageRoleUser,
Content: req.String(),
},
@ -38,9 +39,9 @@ func (oc *OpenAIClient) EvaluateCCR(ctx context.Context, req CodeChangeRequest)
return res, err
}
// TODO use different choices/different options in different branches/worktrees?
choice := resp.Choices[0].Message.Content
// TODO make debug log when I figure out how to config that
oc.log.Info("got response from llm", zap.String("output", choice))
return ParseCodeChangeResponse(choice), nil

View File

@ -15,10 +15,10 @@ Body:
Respond in the exact format:
Files:
{{ range $index, $file := .Files }}
- name: {{ $file.Path }}
contents:
ppname: {{ $file.Path }}
ppcontents:
[new {{ $file.Path }} contents]
{{ end }}
Notes:
ppnotes:
[additional context about your changes]

View File

@ -24,9 +24,9 @@ Q
Response Template B:
R
Files:
- name: {{ .Path }}
contents:
ppname: {{ .Path }}
ppcontents:
[new {{ .Path }} contents]
Response:
ppresponse:
[additional context about your changes]

View File

@ -3,10 +3,5 @@ package main
import "github.com/mobyvb/pull-pal/cmd"
func main() {
/*
response := "Files:\n\n - name: main.go\n contents:\n ```go\n package main\n\n import (\n \"fmt\"\n \"log\"\n \"net/http\"\n )\n\n func main() {\n http.Handle(\"/\", http.FileServer(http.Dir(\"./\")))\n fmt.Println(\"Server listening on :7777\")\n log.Fatal(http.ListenAndServe(\":7777\", nil))\n }\n ```\n\n - name: index.html\n contents:\n ```html\n <!DOCTYPE html>\n <html>\n <head>\n <title>Pull Pal</title>\n <style>\n body {\n font-family: sans-serif;\n }\n .content {\n background-color: #f2f2f2;\n border-radius: 10px;\n box-shadow: 2px 2px 10px rgba(0,0,0,0.2);\n padding: 20px;\n margin: 20px;\n }\n h1 {\n color: #3399cc;\n background-color: #f2f2f2;\n text-align: center;\n padding: 10px;\n border-radius: 10px;\n box-shadow: 2px 2px 10px rgba(0,0,0,0.2);\n }\n </style>\n </head>\n <body>\n <div class=\"content\">\n <h1>Introducing Pull Pal!</h1>\n <p>Pull Pal is a digital assistant that can monitor your Github repositories and create pull requests using the power of artificial intelligence. Say goodbye to manual pull request creation like it's 2005!</p>\n <p>Sign up now to start automating your workflow and get more done in less time. </p>\n </div>\n </body>\n </html>\n ```\n\nNotes:\n- Added a basic HTTP server in main.go which serves index.html from the root path on port 7777\n- Added basic HTML structure and CSS styling to index.html, using sans-serif font and a soft color scheme with blue as the main accent color\n- Added a content container with an off-white background, rounded corners, and a box shadow, and a centered heading with blue font color and an off-white background to draw attention to the product name and value proposition"
res := llm.ParseCodeChangeResponse(response)
fmt.Println(res.String())
*/
cmd.Execute()
}

View File

@ -4,7 +4,6 @@ import (
"context"
"errors"
"fmt"
"io/ioutil"
"strconv"
"strings"
"time"
@ -12,7 +11,6 @@ import (
"github.com/mobyvb/pull-pal/llm"
"github.com/mobyvb/pull-pal/vc"
"github.com/atotto/clipboard"
"go.uber.org/zap"
)
@ -20,10 +18,10 @@ import (
var IssueNotFound = errors.New("no issue found")
// PullPal is the service responsible for:
// * Interacting with git server (e.g. reading issues and making PRs on Github)
// * Generating LLM prompts
// * Parsing LLM responses
// * Interacting with LLM (e.g. with GPT via OpenAI API)
// - Interacting with git server (e.g. reading issues and making PRs on Github)
// - Generating LLM prompts
// - Parsing LLM responses
// - Interacting with LLM (e.g. with GPT via OpenAI API)
type PullPal struct {
ctx context.Context
log *zap.Logger
@ -40,7 +38,7 @@ func NewPullPal(ctx context.Context, log *zap.Logger, listIssueOptions vc.ListIs
if err != nil {
return nil, err
}
localGitClient, err := vc.NewLocalGitClient(self, repo)
localGitClient, err := vc.NewLocalGitClient(log, self, repo)
if err != nil {
return nil, err
}
@ -108,7 +106,7 @@ func (p *PullPal) Run() error {
files := []llm.File{}
for _, path := range fileList {
path = strings.TrimSpace(path)
nextFile, err := p.ghClient.GetLocalFile(path)
nextFile, err := p.localGitClient.GetLocalFile(path)
if err != nil {
p.log.Error("error getting file from vcclient", zap.Error(err))
continue
@ -189,6 +187,8 @@ func (p *PullPal) Run() error {
return nil
}
/*
// PickIssueToFile is the same as PickIssue, but the changeRequest is converted to a string and written to a file.
func (p *PullPal) PickIssueToFile(promptPath string) (issue vc.Issue, changeRequest llm.CodeChangeRequest, err error) {
issue, changeRequest, err = p.PickIssue()
@ -220,7 +220,8 @@ func (p *PullPal) PickIssueToClipboard() (issue vc.Issue, changeRequest llm.Code
err = clipboard.WriteAll(prompt)
return issue, changeRequest, err
}
*/
/*
// PickIssue selects an issue from the version control server and returns the selected issue, as well as the LLM prompt needed to fulfill the request.
func (p *PullPal) PickIssue() (issue vc.Issue, changeRequest llm.CodeChangeRequest, err error) {
// TODO I should be able to pass in settings for listing issues from here
@ -263,7 +264,7 @@ func (p *PullPal) PickIssue() (issue vc.Issue, changeRequest llm.CodeChangeReque
return issue, changeRequest, nil
}
*/
/*
// ProcessResponseFromFile is the same as ProcessResponse, but the response is inputted into a file rather than passed directly as an argument.
func (p *PullPal) ProcessResponseFromFile(codeChangeRequest llm.CodeChangeRequest, llmResponsePath string) (url string, err error) {
@ -303,6 +304,7 @@ func (p *PullPal) ProcessResponse(codeChangeRequest llm.CodeChangeRequest, llmRe
}
*/
/*
// ListIssues gets a list of all issues meeting the provided criteria.
func (p *PullPal) ListIssues(handles, labels []string) ([]vc.Issue, error) {
issues, err := p.ghClient.ListOpenIssues(vc.ListIssueOptions{
@ -368,3 +370,4 @@ func (p *PullPal) MakeLocalChange(issue vc.Issue) error {
return nil
}
*/

View File

@ -80,26 +80,3 @@ 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)
}
// VCClient is an interface for version control server's client, e.g. a Github or Gerrit client.
/*
type VCClient interface {
// ListOpenIssues lists unresolved issues meeting the provided criteria on the version control server.
ListOpenIssues(opts ListIssueOptions) ([]Issue, error)
// ListOpenComments lists unresolved comments meeting the provided criteria on the version control server.
ListOpenComments(opts ListCommentOptions) ([]Comment, error)
// OpenCodeChangeRequest opens a new "code change request" on the version control server (e.g. "pull request" in Github).
OpenCodeChangeRequest(req llm.CodeChangeRequest, res llm.CodeChangeResponse) (id, url string, err error)
// UpdateCodeChangeRequest updates an existing code change request on the version control server.
// UpdateCodeChangeRequest(id string, res llm.CodeChangeResponse)
// TODO: add/read comments to/from issues and code change requests
// GetLocalFile gets the current representation of the file at the provided path from the local git repo.
GetLocalFile(path string) (llm.File, error)
// StartCommit initiates a commit process, after which files can be modified and added to the commit.
StartCommit() error
// ReplaceOrAddLocalFile updates or adds a file in the locally cloned repo, and applies these changes to the current git worktree.
ReplaceOrAddLocalFile(newFile llm.File) error
// FinishCommit completes a commit, after which a code change request can be opened or updated.
FinishCommit(message string) error
}
*/

View File

@ -11,6 +11,7 @@ import (
"time"
"github.com/mobyvb/pull-pal/llm"
"go.uber.org/zap"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
@ -27,7 +28,8 @@ type LocalGitClient struct {
}
// NewLocalGitClient initializes a local git client by checking out a repository locally.
func NewLocalGitClient( /*ctx context.Context, log *zap.Logger, */ self Author, repo Repository) (*LocalGitClient, error) {
func NewLocalGitClient( /*ctx context.Context, */ log *zap.Logger, self Author, repo Repository) (*LocalGitClient, error) {
log.Info("checking out local github repo", zap.String("repo name", repo.Name), zap.String("local path", repo.LocalPath))
// clone provided repository to local path
if repo.LocalPath == "" {
return nil, errors.New("local path to clone repository not provided")

View File

@ -2,23 +2,12 @@ package vc
import (
"context"
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"go/format"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/mobyvb/pull-pal/llm"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/google/go-github/github"
"go.uber.org/zap"
"golang.org/x/oauth2"
@ -32,8 +21,6 @@ type GithubClient struct {
client *github.Client
self Author
repo Repository
worktree *git.Worktree
}
// NewGithubClient initializes a Github client and checks out a repository locally.
@ -48,35 +35,6 @@ func NewGithubClient(ctx context.Context, log *zap.Logger, self Author, repo Rep
// oauth client is used to list issues, open pull requests, etc...
tc := oauth2.NewClient(ctx, ts)
// clone provided repository to local path
if repo.LocalPath == "" {
return nil, errors.New("local path to clone repository not provided")
}
if repo.LocalPath != "" {
// remove local repo if it exists already
err := os.RemoveAll(repo.LocalPath)
if err != nil {
return nil, err
}
}
log.Info("Cloning repository locally...", zap.String("local repo path", repo.LocalPath), zap.String("url", repo.SSH()))
// TODO this can be done in-memory - see https://pkg.go.dev/github.com/go-git/go-git/v5#readme-in-memory-example
localRepo, err := git.PlainClone(repo.LocalPath, false, &git.CloneOptions{
URL: repo.SSH(),
// URL: repo.HTTPS(),
Auth: &http.BasicAuth{
Username: self.Handle,
Password: self.Token,
},
})
if err != nil {
log.Info("failed")
return nil, err
}
repo.localRepo = localRepo
log.Info("Success. Github client set up.")
return &GithubClient{
@ -96,54 +54,10 @@ func (gc *GithubClient) OpenCodeChangeRequest(req llm.CodeChangeRequest, res llm
if title == "" {
title = "update files"
}
/*
branchName := randomBranchName()
branchRefName := plumbing.NewBranchReferenceName(branchName)
baseBranch := "main"
remoteName := "origin"
*/
body := res.Notes
body += fmt.Sprintf("\n\nResolves #%s", req.IssueID)
// Create new local branch
/*
headRef, err := gc.repo.localRepo.Head()
if err != nil {
return "", "", err
}
err = gc.repo.localRepo.CreateBranch(&config.Branch{
Name: branchName,
Remote: remoteName,
Merge: branchRefName,
})
if err != nil {
return "", "", err
}
// Update the branch to point to the new commit
err = gc.repo.localRepo.Storer.SetReference(plumbing.NewHashReference(branchRefName, headRef.Hash()))
if err != nil {
return "", "", err
}
// Push the new branch to the remote repository
remote, err := gc.repo.localRepo.Remote(remoteName)
if err != nil {
return "", "", err
}
err = remote.Push(&git.PushOptions{
RefSpecs: []config.RefSpec{config.RefSpec(fmt.Sprintf("%s:refs/heads/%s", branchRefName, branchName))},
Auth: &http.BasicAuth{
Username: gc.self.Handle,
Password: gc.self.Token,
},
})
if err != nil {
return "", "", err
}
*/
// Finally, open a pull request from the new branch.
pr, _, err := gc.client.PullRequests.Create(gc.ctx, gc.repo.Owner.Handle, gc.repo.Name, &github.NewPullRequest{
Title: &title,
@ -161,12 +75,6 @@ func (gc *GithubClient) OpenCodeChangeRequest(req llm.CodeChangeRequest, res llm
return id, url, nil
}
func randomBranchName() string {
bytes := make([]byte, 4)
rand.Read(bytes)
return hex.EncodeToString(bytes)
}
// ListOpenIssues lists unresolved issues in the Github repository.
func (gc *GithubClient) ListOpenIssues(options ListIssueOptions) ([]Issue, error) {
// List and parse GitHub issues
@ -285,91 +193,3 @@ func (gc *GithubClient) ListOpenComments(options ListCommentOptions) ([]Comment,
return toReturn, nil
}
// GetLocalFile gets the current representation of the file at the provided path from the local git repo.
func (gc *GithubClient) GetLocalFile(path string) (llm.File, error) {
fullPath := filepath.Join(gc.repo.LocalPath, path)
data, err := ioutil.ReadFile(fullPath)
if err != nil {
// if file doesn't exist, just return an empty file
// this means we want to prompt the llm to populate it for the first time
if errors.Is(err, os.ErrNotExist) {
return llm.File{
Path: path,
Contents: "",
}, nil
}
return llm.File{}, err
}
return llm.File{
Path: path,
Contents: string(data),
}, nil
}
// StartCommit creates a new worktree associated with this Github client.
func (gc *GithubClient) StartCommit() error {
if gc.worktree != nil {
return errors.New("worktree is not nil - cannot start a new commit")
}
worktree, err := gc.repo.localRepo.Worktree()
if err != nil {
return err
}
gc.worktree = worktree
return nil
}
// ReplaceOrAddLocalFile updates or adds a file in the locally cloned repo, and applies these changes to the current git worktree.
func (gc *GithubClient) ReplaceOrAddLocalFile(newFile llm.File) error {
if gc.worktree == nil {
return errors.New("worktree is nil - StartCommit must be called")
}
// TODO format non-go files as well
if strings.HasSuffix(newFile.Path, ".go") {
newContents, err := format.Source([]byte(newFile.Contents))
if err != nil {
return err
}
newFile.Contents = string(newContents)
}
fullPath := filepath.Join(gc.repo.LocalPath, newFile.Path)
err := ioutil.WriteFile(fullPath, []byte(newFile.Contents), 0644)
if err != nil {
return err
}
_, err = gc.worktree.Add(newFile.Path)
return err
}
// FinishCommit completes a commit, after which a code change request can be opened or updated.
func (gc *GithubClient) FinishCommit(message string) error {
if gc.worktree == nil {
return errors.New("worktree is nil - StartCommit must be called")
}
_, err := gc.worktree.Commit(message, &git.CommitOptions{
Author: &object.Signature{
Name: gc.self.Handle,
Email: gc.self.Email,
When: time.Now(),
},
})
if err != nil {
return err
}
// set worktree to nil so a new commit can be started
gc.worktree = nil
return nil
}