From 0f181f8920bf60290f2f76503c496a9a6fe73b96 Mon Sep 17 00:00:00 2001 From: Tim Culverhouse Date: Sat, 10 May 2025 07:43:36 -0500 Subject: [PATCH] pr(create): open patches in $EDITOR before sending --- git/git.go | 36 ++++++++++++++++++++++++++++++++++++ knit.go | 24 +++++++++++++++++++++++- pr/create.go | 32 ++++++++++++++++++++++++++------ 3 files changed, 85 insertions(+), 7 deletions(-) diff --git a/git/git.go b/git/git.go index 9d5c865..7d22555 100644 --- a/git/git.go +++ b/git/git.go @@ -4,7 +4,10 @@ import ( "bufio" "fmt" "net/url" + "os" "os/exec" + "path" + "sort" "strings" ) @@ -98,6 +101,39 @@ func FormatPatch(revRange string) (string, error) { return string(output), nil } +func FormatPatchToTmp(revRange string) ([]string, error) { + tmpDir, err := os.MkdirTemp("", "knit-patches-") + if err != nil { + return nil, err + } + + cmd := exec.Command( + "git", + "format-patch", + "--output-directory", + tmpDir, + revRange, + ) + if err := cmd.Run(); err != nil { + return nil, err + } + + entries, err := os.ReadDir(tmpDir) + if err != nil { + return nil, err + } + + var paths []string + for _, entry := range entries { + if entry.IsDir() { + continue + } + paths = append(paths, path.Join(tmpDir, entry.Name())) + } + sort.Strings(paths) + return paths, nil +} + func RemoteBranches(remote string) ([]string, error) { cmd := exec.Command("git", "branch", "--remotes", "--list", remote+"*") diff --git a/knit.go b/knit.go index 4886598..684cd23 100644 --- a/knit.go +++ b/knit.go @@ -1,6 +1,10 @@ package knit -import "errors" +import ( + "errors" + "os" + "os/exec" +) // DefaultHost is the default host for tangled.sh services const DefaultHost = "tangled.sh" @@ -11,3 +15,21 @@ const DefaultKnot = "knot1.tangled.sh" // ErrRequiresAuth is returned when a command requires authentication but the // user has not authenticated with the host var ErrRequiresAuth = errors.New("authentication required") + +func Editor() string { + e := os.Getenv("EDITOR") + if e != "" { + return e + } + + options := []string{"nvim", "vim", "nano"} + for _, opt := range options { + _, err := exec.LookPath(opt) + if err != nil { + continue + } + return opt + } + + return "vi" +} diff --git a/pr/create.go b/pr/create.go index e73e48e..f9b798a 100644 --- a/pr/create.go +++ b/pr/create.go @@ -6,6 +6,8 @@ import ( "fmt" "net/http" "net/url" + "os" + "os/exec" "path" "strings" "sync" @@ -36,6 +38,29 @@ func Create(cmd *cobra.Command, args []string) error { } _ = client + paths, err := git.FormatPatchToTmp(args[0]) + if err != nil { + return err + } + + editor := exec.Command(knit.Editor(), paths...) + editor.Stdout = os.Stdout + editor.Stderr = os.Stderr + editor.Stdin = os.Stdin + if err := editor.Run(); err != nil { + return err + } + + // Combine the result into a single slice + var patch []byte + for _, p := range paths { + b, err := os.ReadFile(p) + if err != nil { + return err + } + patch = append(patch, b...) + } + // Get available remotes remotes, err := git.Remotes() if err != nil { @@ -60,11 +85,6 @@ func Create(cmd *cobra.Command, args []string) error { Run() } - patch, err := git.FormatPatch(args[0]) - if err != nil { - return err - } - branches, err := git.RemoteBranches(remote.Name) if err != nil { return err @@ -118,7 +138,7 @@ func Create(cmd *cobra.Command, args []string) error { form.Add("title", title) form.Add("body", description) form.Add("targetBranch", targetBranch) - form.Add("patch", patch) + form.Add("patch", string(patch)) p := remote.Path if !strings.HasPrefix(p, "@") { -- 2.51.2