A CLI for tangled.sh

pr(create): open patches in $EDITOR before sending

authored by rockorager.dev and committed by Tangled 0f181f89 f6235fb2

Changed files
+85 -7
git
pr
+36
git/git.go
··· 4 4 "bufio" 5 5 "fmt" 6 6 "net/url" 7 + "os" 7 8 "os/exec" 9 + "path" 10 + "sort" 8 11 "strings" 9 12 ) 10 13 ··· 96 99 } 97 100 98 101 return string(output), nil 102 + } 103 + 104 + func FormatPatchToTmp(revRange string) ([]string, error) { 105 + tmpDir, err := os.MkdirTemp("", "knit-patches-") 106 + if err != nil { 107 + return nil, err 108 + } 109 + 110 + cmd := exec.Command( 111 + "git", 112 + "format-patch", 113 + "--output-directory", 114 + tmpDir, 115 + revRange, 116 + ) 117 + if err := cmd.Run(); err != nil { 118 + return nil, err 119 + } 120 + 121 + entries, err := os.ReadDir(tmpDir) 122 + if err != nil { 123 + return nil, err 124 + } 125 + 126 + var paths []string 127 + for _, entry := range entries { 128 + if entry.IsDir() { 129 + continue 130 + } 131 + paths = append(paths, path.Join(tmpDir, entry.Name())) 132 + } 133 + sort.Strings(paths) 134 + return paths, nil 99 135 } 100 136 101 137 func RemoteBranches(remote string) ([]string, error) {
+23 -1
knit.go
··· 1 1 package knit 2 2 3 - import "errors" 3 + import ( 4 + "errors" 5 + "os" 6 + "os/exec" 7 + ) 4 8 5 9 // DefaultHost is the default host for tangled.sh services 6 10 const DefaultHost = "tangled.sh" ··· 11 15 // ErrRequiresAuth is returned when a command requires authentication but the 12 16 // user has not authenticated with the host 13 17 var ErrRequiresAuth = errors.New("authentication required") 18 + 19 + func Editor() string { 20 + e := os.Getenv("EDITOR") 21 + if e != "" { 22 + return e 23 + } 24 + 25 + options := []string{"nvim", "vim", "nano"} 26 + for _, opt := range options { 27 + _, err := exec.LookPath(opt) 28 + if err != nil { 29 + continue 30 + } 31 + return opt 32 + } 33 + 34 + return "vi" 35 + }
+26 -6
pr/create.go
··· 6 6 "fmt" 7 7 "net/http" 8 8 "net/url" 9 + "os" 10 + "os/exec" 9 11 "path" 10 12 "strings" 11 13 "sync" ··· 36 38 } 37 39 _ = client 38 40 41 + paths, err := git.FormatPatchToTmp(args[0]) 42 + if err != nil { 43 + return err 44 + } 45 + 46 + editor := exec.Command(knit.Editor(), paths...) 47 + editor.Stdout = os.Stdout 48 + editor.Stderr = os.Stderr 49 + editor.Stdin = os.Stdin 50 + if err := editor.Run(); err != nil { 51 + return err 52 + } 53 + 54 + // Combine the result into a single slice 55 + var patch []byte 56 + for _, p := range paths { 57 + b, err := os.ReadFile(p) 58 + if err != nil { 59 + return err 60 + } 61 + patch = append(patch, b...) 62 + } 63 + 39 64 // Get available remotes 40 65 remotes, err := git.Remotes() 41 66 if err != nil { ··· 58 83 Value(&remote). 59 84 Options(options...). 60 85 Run() 61 - } 62 - 63 - patch, err := git.FormatPatch(args[0]) 64 - if err != nil { 65 - return err 66 86 } 67 87 68 88 branches, err := git.RemoteBranches(remote.Name) ··· 118 138 form.Add("title", title) 119 139 form.Add("body", description) 120 140 form.Add("targetBranch", targetBranch) 121 - form.Add("patch", patch) 141 + form.Add("patch", string(patch)) 122 142 123 143 p := remote.Path 124 144 if !strings.HasPrefix(p, "@") {