From 98d8aed08aef15c198de0b75fba41ed72f69dc4c Mon Sep 17 00:00:00 2001 From: mediocregopher <> Date: Fri, 21 Feb 2020 16:35:53 -0700 Subject: [PATCH] Add support for using EDITOR to construct commit messages. message: |- Add support for using EDITOR to construct commit messages. I feel like I can breathe again! The code is more functional than aesthetic, but it works damnit! change_hash: AP+JESJn2+CP1FCDvwJqaXUM354rpoK5oU6Me4uByxaf credentials: - type: pgp_signature pub_key_id: 95C46FA6A41148AC body: iQIzBAABAgAdFiEEJ6tQKp6olvZKJ0lwlcRvpqQRSKwFAl5QaUwACgkQlcRvpqQRSKxY4g/9E6D6CdR3h9JuCHVhVQTeU5NzEPoJTQdGZgOTr8t9lEAceNfzBlxVExKf3KFRDQhDjXOu/tbqgsCAu6n6xdSGc+7nnFRhA+nYrg6c9PMenjbH43eB8s+MxrrXoWYWEKr1inlHEBMoV6GQQpXMuSCPDJ/HwfWqzjG7bHM9Fi1nDQwlh5cBY6KxQwKWWBrCeG5FDdT9AKKZY6Y7WPC9HEPnk1TdkaJNXsZ65IxycggaLc25qWx6EyjQjHONrzpV3R7b6LJQkxrp5tyA60IauGdeAivKmghLDvBK7ItjA8TGu81PUuvthyBQTRfs+3vrYIAtXUzo/LODRjTJumEPCiJ7zUfbhVNqYDR/EdXC9E8eBSPhK37twzdDvdHAfyXWuH9wAZ0B8oqv7ChwGyHlKkCbMksu8OTAllAdYOUpFpVQfNdTrSY/fUO/oWeTG7cHuTCN7AYphkHDjVgUh3A0b1QDwZnH7j4C6oRvExb7rUfgFw/9EIG1hSMMdb4imbJmjkEVWhchHGF9ipkMPYANswQSuDpbtOEyGf3OK0PlFFeqn3k5RyecmmuQd4pKKE7ASNKZwOd//UVxjz8Aa3NsZoZU3tbQJ8dnE6vit0Iggz7d4jbIxaQCd0gGbHRPtCppU6kTa52Lx+Zg01F+KGvr+/E6jXB/6pGoOqK0XObQtMKGSJ0= account: mediocregopher --- cmd/dehub/main.go | 74 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/cmd/dehub/main.go b/cmd/dehub/main.go index 31f0a81..bbd166e 100644 --- a/cmd/dehub/main.go +++ b/cmd/dehub/main.go @@ -2,12 +2,15 @@ package main import ( "bufio" + "bytes" "dehub" "errors" "flag" "fmt" "io" + "io/ioutil" "os" + "os/exec" "strings" "gopkg.in/src-d/go-git.v4/plumbing" @@ -20,6 +23,63 @@ func exitErr(err error) { os.Exit(1) } +func tmpFileMsg() (string, error) { + editor := os.Getenv("EDITOR") + if editor == "" { + return "", errors.New("EDITOR not set, please set it or use -msg in order to create your commit message") + } else if _, err := os.Stat(editor); err != nil { + return "", fmt.Errorf("could not stat EDITOR %q: %w", editor, err) + } + + tmpf, err := ioutil.TempFile("", "") + if err != nil { + return "", fmt.Errorf("could not open temp file: %w", err) + } + tmpfName := tmpf.Name() + defer os.Remove(tmpfName) + + tmpBody := bytes.NewBufferString(` + +# Please enter the commit message for your changes. Lines starting +# with '#' will be ignored, and an empty message aborts the commit.`) + + _, err = io.Copy(tmpf, tmpBody) + tmpf.Close() + if err != nil { + return "", fmt.Errorf("could not write helper message to temp file %q: %w", tmpfName, err) + } + + cmd := exec.Command(editor, tmpfName) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return "", fmt.Errorf("error running '%s %q': %w", editor, tmpfName, err) + } + + body, err := ioutil.ReadFile(tmpfName) + if err != nil { + return "", fmt.Errorf("error retrieving message body from %q: %w", tmpfName, err) + } + + bodyFiltered := new(bytes.Buffer) + bodyBR := bufio.NewReader(bytes.NewBuffer(body)) + for { + line, err := bodyBR.ReadString('\n') + if errors.Is(err, io.EOF) { + break + } else if err != nil { + return "", fmt.Errorf("error reading from buffered body: %w", err) + } + + if !strings.HasPrefix(strings.TrimSpace(line), "#") { + bodyFiltered.WriteString(line) + } + } + + return strings.TrimSpace(bodyFiltered.String()), nil +} + type subCmdCtx struct { repo func() *dehub.Repo flag *flag.FlagSet @@ -46,9 +106,19 @@ var subCmds = []subCmd{ accountID := sctx.flag.String("account-id", "", "Account to sign commit as") sctx.flagParse() - if *msg == "" || *accountID == "" { + if *accountID == "" { flag.PrintDefaults() - return errors.New("-msg and -account-id are both required") + return errors.New("-account-id is required") + } + + if *msg == "" { + var err error + if *msg, err = tmpFileMsg(); err != nil { + return fmt.Errorf("error collecting commit message from user: %w", err) + + } else if *msg == "" { + return errors.New("empty commit message, not doing anything") + } } cfg, err := sctx.repo().LoadConfig()