Commit message editor (#294)

Added ability for user to create and edit commit message in text editor of their choice
This commit is contained in:
Osheiza Otori
2019-01-23 12:13:33 -08:00
committed by GitHub
parent fbf26d018d
commit d5c8d03217
7 changed files with 189 additions and 24 deletions

View File

@@ -2,15 +2,18 @@ package cli
import (
"fmt"
"github.com/fatih/color"
"github.com/google/uuid"
"os"
"path/filepath"
"github.com/fatih/color"
"github.com/google/uuid"
)
var CliOut = color.Output
var CliErr = color.Error
var ExecuteWithStdioRestored func(userFunc func())
func InitIO() (restoreIO func()) {
stdOut, stdErr := os.Stdout, os.Stderr
@@ -31,6 +34,19 @@ func InitIO() (restoreIO func()) {
os.Stderr = stdErr
}
ExecuteWithStdioRestored = func(userFunc func()) {
initialNoColor := color.NoColor
color.NoColor = true
os.Stdout = stdOut
os.Stderr = stdErr
userFunc()
os.Stdout = f
os.Stderr = f
color.NoColor = initialNoColor
}
return restoreIO
}

View File

@@ -1,12 +1,16 @@
package commands
import (
"bytes"
"strings"
"github.com/fatih/color"
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/cli"
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/errhand"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/env"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/env/actions"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/argparser"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/editor"
)
var commitShortDesc = `Record changes to the repository`
@@ -26,9 +30,7 @@ func Commit(commandStr string, args []string, dEnv *env.DoltEnv) int {
msg, msgOk := apr.GetValue(commitMessageArg)
if !msgOk {
cli.PrintErrln(color.RedString("Missing required parameter -m"))
usage()
return 1
msg = getCommitMessageFromEditor(dEnv)
}
err := actions.CommitStaged(dEnv, msg)
@@ -48,7 +50,7 @@ func handleCommitErr(err error, usage cli.UsagePrinter) int {
return HandleVErrAndExitCode(bdr.Build(), usage)
} else if actions.IsNothingStaged(err) {
notStaged := actions.NothingStagedDiffs(err)
printDiffsNotStaged(notStaged, false, 0, []string{})
printDiffsNotStaged(cli.CliOut, notStaged, false, 0, []string{})
return 1
} else if err != nil {
@@ -58,3 +60,57 @@ func handleCommitErr(err error, usage cli.UsagePrinter) int {
return 0
}
func getCommitMessageFromEditor(dEnv *env.DoltEnv) string {
var finalMsg string
initialMsg := buildInitalCommitMsg(dEnv)
editorStr := dEnv.Config.GetStringOrDefault(env.DoltEditor, "vim")
cli.ExecuteWithStdioRestored(func() {
commitMsg, _ := editor.OpenCommitEditor(*editorStr, initialMsg)
finalMsg = parseCommitMessage(commitMsg)
})
return finalMsg
}
func buildInitalCommitMsg(dEnv *env.DoltEnv) string {
initialNoColor := color.NoColor
color.NoColor = true
currBranch := dEnv.RepoState.Branch
stagedDiffs, notStagedDiffs, _ := actions.GetTableDiffs(dEnv)
buf := bytes.NewBuffer([]byte{})
workingInConflict, _, _, err := actions.GetTablesInConflict(dEnv)
if err != nil {
workingInConflict = []string{}
}
n := printStagedDiffs(buf, stagedDiffs, true)
n = printDiffsNotStaged(buf, notStagedDiffs, true, n, workingInConflict)
initialCommitMessage := "\n" + "# Please enter the commit message for your changes. Lines starting" + "\n" +
"# with '#' will be ignored, and an empty message aborts the commit." + "\n# On branch " + currBranch + "\n#" + "\n"
msgLines := strings.Split(buf.String(), "\n")
for i, msg := range msgLines {
msgLines[i] = "# " + msg
}
statusMsg := strings.Join(msgLines, "\n")
color.NoColor = initialNoColor
return initialCommitMessage + statusMsg
}
func parseCommitMessage(cm string) string {
lines := strings.Split(cm, "\n")
filtered := make([]string, 0, len(lines))
for _, line := range lines {
if len(line) >= 1 && line[0] == '#' {
continue
}
filtered = append(filtered, line)
}
return strings.Join(filtered, "\n")
}

View File

@@ -2,13 +2,16 @@ package commands
import (
"fmt"
"io"
"strings"
"github.com/fatih/color"
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/cli"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/env"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/env/actions"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/argparser"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/iohelp"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/set"
"strings"
)
var statusShortDesc = "Show the working status"
@@ -69,12 +72,12 @@ const (
bothModifiedLabel = "both modified:"
)
func printStagedDiffs(staged *actions.TableDiffs, printHelp bool) int {
func printStagedDiffs(wr io.Writer, staged *actions.TableDiffs, printHelp bool) int {
if staged.Len() > 0 {
cli.Println(stagedHeader)
iohelp.WriteLine(wr, stagedHeader)
if printHelp {
cli.Println(stagedHeaderHelp)
iohelp.WriteLine(wr, stagedHeaderHelp)
}
lines := make([]string, 0, staged.Len())
@@ -83,23 +86,23 @@ func printStagedDiffs(staged *actions.TableDiffs, printHelp bool) int {
lines = append(lines, fmt.Sprintf(statusFmt, tblDiffTypeToLabel[tdt], tblName))
}
cli.Println(color.GreenString(strings.Join(lines, "\n")))
iohelp.WriteLine(wr, color.GreenString(strings.Join(lines, "\n")))
return len(lines)
}
return 0
}
func printDiffsNotStaged(notStaged *actions.TableDiffs, printHelp bool, linesPrinted int, workingNotStaged []string) int {
func printDiffsNotStaged(wr io.Writer, notStaged *actions.TableDiffs, printHelp bool, linesPrinted int, workingNotStaged []string) int {
if notStaged.NumRemoved+notStaged.NumModified > 0 {
if linesPrinted > 0 {
cli.Println()
}
cli.Println(workingHeader)
iohelp.WriteLine(wr, workingHeader)
if printHelp {
cli.Println(workingHeaderHelp)
iohelp.WriteLine(wr, workingHeaderHelp)
}
inCnfSet := set.NewStrSet(workingNotStaged)
@@ -117,7 +120,7 @@ func printDiffsNotStaged(notStaged *actions.TableDiffs, printHelp bool, linesPri
}
}
cli.Println(color.RedString(strings.Join(lines, "\n")))
iohelp.WriteLine(wr, color.RedString(strings.Join(lines, "\n")))
linesPrinted += len(lines)
}
@@ -126,10 +129,10 @@ func printDiffsNotStaged(notStaged *actions.TableDiffs, printHelp bool, linesPri
cli.Println()
}
cli.Println(untrackedHeader)
iohelp.WriteLine(wr, untrackedHeader)
if printHelp {
cli.Println(untrackedHeaderHelp)
iohelp.WriteLine(wr, untrackedHeaderHelp)
}
lines := make([]string, 0, notStaged.Len())
@@ -141,7 +144,7 @@ func printDiffsNotStaged(notStaged *actions.TableDiffs, printHelp bool, linesPri
}
}
cli.Println(color.RedString(strings.Join(lines, "\n")))
iohelp.WriteLine(wr, color.RedString(strings.Join(lines, "\n")))
linesPrinted += len(lines)
}
@@ -151,8 +154,8 @@ func printDiffsNotStaged(notStaged *actions.TableDiffs, printHelp bool, linesPri
func printStatus(dEnv *env.DoltEnv, staged, notStaged *actions.TableDiffs, workingInConflict []string) {
cli.Printf(branchHeader, dEnv.RepoState.Branch)
n := printStagedDiffs(staged, true)
n = printDiffsNotStaged(notStaged, true, n, workingInConflict)
n := printStagedDiffs(cli.CliOut, staged, true)
n = printDiffsNotStaged(cli.CliOut, notStaged, true, n, workingInConflict)
if n == 0 {
cli.Println("nothing to commit, working tree clean")

View File

@@ -1,6 +1,9 @@
package main
import (
"os"
"strings"
"github.com/fatih/color"
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/cli"
"github.com/liquidata-inc/ld/dolt/go/cmd/dolt/commands"
@@ -9,8 +12,6 @@ import (
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/doltdb"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/env"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/filesys"
"os"
"strings"
)
const (
@@ -36,8 +37,8 @@ var doltCommand = cli.GenSubCommandHandler([]*cli.Command{
})
func main() {
args := os.Args[1:]
args := os.Args[1:]
// Currently goland doesn't support running with a different working directory when using go modules.
// This is a hack that allows a different working directory to be set after the application starts using
// chdir=<DIR>. The syntax is not flexible and must match exactly this.

View File

@@ -2,11 +2,12 @@ package env
import (
"errors"
"strings"
"github.com/liquidata-inc/ld/dolt/go/libraries/doltcore/doltdb"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/config"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/filesys"
"github.com/liquidata-inc/ld/dolt/go/libraries/utils/set"
"strings"
)
const (
@@ -15,6 +16,8 @@ const (
UserEmailKey = "user.email"
UserNameKey = "user.name"
DoltEditor = "core.editor"
)
var LocalConfigWhitelist = set.NewStrSet([]string{UserNameKey, UserEmailKey})

View File

@@ -0,0 +1,56 @@
package editor
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/google/uuid"
)
//OpenCommitEditor allows user to write/edit commit message in temporary file
func OpenCommitEditor(ed string, initialContents string) (string, error) {
filename := filepath.Join(os.TempDir(), uuid.New().String())
err := ioutil.WriteFile(filename, []byte(initialContents), os.ModePerm)
if err != nil {
return "", err
}
cmdName, cmdArgs := getEditorString(ed)
if cmdName == "" {
cmdName = os.Getenv("EDITOR")
cmdArgs = []string{}
}
cmdArgs = append(cmdArgs, filename)
cmd := exec.Command(cmdName, cmdArgs...)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Start()
if err != nil {
fmt.Printf("Start failed: %s", err)
}
fmt.Printf("Waiting for command to finish.\n")
err = cmd.Wait()
fmt.Printf("Command finished with error: %v\n", err)
data, err := ioutil.ReadFile(filename)
if err != nil {
return "", err
}
return string(data), nil
}
func getEditorString(edStr string) (string, []string) {
splitStr := strings.Split(edStr, " ")
return splitStr[0], splitStr[1:]
}

View File

@@ -0,0 +1,30 @@
package editor
import (
"reflect"
"testing"
)
func TestGetEditorString(t *testing.T) {
tests := []struct {
inStr string
expectedCmd string
expectedArgs []string
}{
{"", "", []string{}},
{"vim", "vim", []string{}},
{"subl -n -w", "subl", []string{"-n", "-w"}},
}
for _, test := range tests {
actualCmd, actualArgs := getEditorString(test.inStr)
if actualCmd != test.expectedCmd {
t.Error(actualCmd, "!=", test.expectedCmd)
}
if !reflect.DeepEqual(actualArgs, test.expectedArgs) {
t.Error(actualCmd, "!=", test.expectedCmd)
}
}
}