mirror of
https://github.com/dolthub/dolt.git
synced 2026-03-03 18:20:07 -06:00
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:
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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.
|
||||
|
||||
5
go/libraries/doltcore/env/config.go
vendored
5
go/libraries/doltcore/env/config.go
vendored
@@ -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})
|
||||
|
||||
56
go/libraries/utils/editor/edit.go
Normal file
56
go/libraries/utils/editor/edit.go
Normal 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:]
|
||||
}
|
||||
30
go/libraries/utils/editor/edit_test.go
Normal file
30
go/libraries/utils/editor/edit_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user