2018-08-14 11:05:26 +02:00
|
|
|
package commands
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
2018-08-16 23:04:39 +02:00
|
|
|
"strings"
|
2018-08-14 11:05:26 +02:00
|
|
|
|
2018-08-31 10:43:54 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/config"
|
|
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
2018-08-14 11:05:26 +02:00
|
|
|
|
|
|
|
"github.com/mgutz/str"
|
|
|
|
|
2018-08-23 14:22:03 +02:00
|
|
|
"github.com/sirupsen/logrus"
|
2018-08-14 11:05:26 +02:00
|
|
|
gitconfig "github.com/tcnksm/go-gitconfig"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Platform stores the os state
|
|
|
|
type Platform struct {
|
|
|
|
os string
|
|
|
|
shell string
|
|
|
|
shellArg string
|
|
|
|
escapedQuote string
|
2018-08-31 10:43:54 +02:00
|
|
|
openCommand string
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// OSCommand holds all the os commands
|
|
|
|
type OSCommand struct {
|
2018-08-27 10:50:24 +02:00
|
|
|
Log *logrus.Entry
|
2018-08-22 22:31:35 +02:00
|
|
|
Platform *Platform
|
2018-08-31 10:43:54 +02:00
|
|
|
Config config.AppConfigurer
|
2018-08-22 22:31:35 +02:00
|
|
|
command func(string, ...string) *exec.Cmd
|
|
|
|
getGlobalGitConfig func(string) (string, error)
|
|
|
|
getenv func(string) string
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewOSCommand os command runner
|
2018-08-31 10:43:54 +02:00
|
|
|
func NewOSCommand(log *logrus.Entry, config config.AppConfigurer) *OSCommand {
|
2018-08-21 21:09:53 +02:00
|
|
|
return &OSCommand{
|
2018-08-22 22:31:35 +02:00
|
|
|
Log: log,
|
|
|
|
Platform: getPlatform(),
|
2018-08-31 10:43:54 +02:00
|
|
|
Config: config,
|
2018-08-22 22:31:35 +02:00
|
|
|
command: exec.Command,
|
|
|
|
getGlobalGitConfig: gitconfig.Global,
|
|
|
|
getenv: os.Getenv,
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// RunCommandWithOutput wrapper around commands returning their output and error
|
|
|
|
func (c *OSCommand) RunCommandWithOutput(command string) (string, error) {
|
|
|
|
c.Log.WithField("command", command).Info("RunCommand")
|
|
|
|
splitCmd := str.ToArgv(command)
|
|
|
|
c.Log.Info(splitCmd)
|
2018-08-21 21:48:42 +02:00
|
|
|
return sanitisedCommandOutput(
|
2018-08-22 22:31:35 +02:00
|
|
|
c.command(splitCmd[0], splitCmd[1:]...).CombinedOutput(),
|
2018-08-21 21:48:42 +02:00
|
|
|
)
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// RunCommand runs a command and just returns the error
|
|
|
|
func (c *OSCommand) RunCommand(command string) error {
|
|
|
|
_, err := c.RunCommandWithOutput(command)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-08-28 11:12:35 +02:00
|
|
|
// FileType tells us if the file is a file, directory or other
|
|
|
|
func (c *OSCommand) FileType(path string) string {
|
|
|
|
fileInfo, err := os.Stat(path)
|
|
|
|
if err != nil {
|
|
|
|
return "other"
|
|
|
|
}
|
|
|
|
if fileInfo.IsDir() {
|
|
|
|
return "directory"
|
|
|
|
}
|
|
|
|
return "file"
|
|
|
|
}
|
|
|
|
|
2018-08-14 11:05:26 +02:00
|
|
|
// RunDirectCommand wrapper around direct commands
|
|
|
|
func (c *OSCommand) RunDirectCommand(command string) (string, error) {
|
|
|
|
c.Log.WithField("command", command).Info("RunDirectCommand")
|
|
|
|
|
2018-08-21 21:48:42 +02:00
|
|
|
return sanitisedCommandOutput(
|
2018-09-01 04:13:41 +02:00
|
|
|
c.command(c.Platform.shell, c.Platform.shellArg, command).
|
2018-08-21 21:48:42 +02:00
|
|
|
CombinedOutput(),
|
|
|
|
)
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func sanitisedCommandOutput(output []byte, err error) (string, error) {
|
|
|
|
outputString := string(output)
|
|
|
|
if err != nil {
|
|
|
|
// errors like 'exit status 1' are not very useful so we'll create an error
|
|
|
|
// from the combined output
|
2018-09-01 05:27:58 +02:00
|
|
|
if outputString == "" {
|
|
|
|
return "", err
|
|
|
|
}
|
2018-08-14 11:05:26 +02:00
|
|
|
return outputString, errors.New(outputString)
|
|
|
|
}
|
|
|
|
return outputString, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// OpenFile opens a file with the given
|
2018-08-21 22:17:34 +02:00
|
|
|
func (c *OSCommand) OpenFile(filename string) error {
|
2018-09-01 06:33:01 +02:00
|
|
|
commandTemplate := c.Config.GetUserConfig().GetString("os.openCommand")
|
2018-08-31 10:43:54 +02:00
|
|
|
templateValues := map[string]string{
|
|
|
|
"filename": c.Quote(filename),
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
2018-08-21 22:17:34 +02:00
|
|
|
|
2018-08-31 10:43:54 +02:00
|
|
|
command := utils.ResolvePlaceholderString(commandTemplate, templateValues)
|
2018-09-01 06:33:01 +02:00
|
|
|
err := c.RunCommand(command)
|
2018-08-31 10:43:54 +02:00
|
|
|
return err
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// EditFile opens a file in a subprocess using whatever editor is available,
|
|
|
|
// falling back to core.editor, VISUAL, EDITOR, then vi
|
|
|
|
func (c *OSCommand) EditFile(filename string) (*exec.Cmd, error) {
|
2018-08-22 22:31:35 +02:00
|
|
|
editor, _ := c.getGlobalGitConfig("core.editor")
|
2018-08-21 22:33:25 +02:00
|
|
|
|
2018-08-14 11:05:26 +02:00
|
|
|
if editor == "" {
|
2018-08-22 22:31:35 +02:00
|
|
|
editor = c.getenv("VISUAL")
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
if editor == "" {
|
2018-08-22 22:31:35 +02:00
|
|
|
editor = c.getenv("EDITOR")
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
if editor == "" {
|
|
|
|
if err := c.RunCommand("which vi"); err == nil {
|
|
|
|
editor = "vi"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if editor == "" {
|
2018-08-14 15:47:14 +02:00
|
|
|
return nil, errors.New("No editor defined in $VISUAL, $EDITOR, or git config")
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
2018-08-21 22:33:25 +02:00
|
|
|
|
|
|
|
return c.PrepareSubProcess(editor, filename), nil
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// PrepareSubProcess iniPrepareSubProcessrocess then tells the Gui to switch to it
|
2018-08-21 22:33:25 +02:00
|
|
|
func (c *OSCommand) PrepareSubProcess(cmdName string, commandArgs ...string) *exec.Cmd {
|
2018-08-22 22:31:35 +02:00
|
|
|
return c.command(cmdName, commandArgs...)
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Quote wraps a message in platform-specific quotation marks
|
|
|
|
func (c *OSCommand) Quote(message string) string {
|
2018-08-16 23:04:39 +02:00
|
|
|
message = strings.Replace(message, "`", "\\`", -1)
|
2018-09-10 06:34:26 +02:00
|
|
|
if c.Platform.os == "linux" {
|
|
|
|
if strings.ContainsRune(message, '\'') {
|
|
|
|
c.Platform.escapedQuote = `"`
|
|
|
|
} else {
|
|
|
|
c.Platform.escapedQuote = `'`
|
|
|
|
}
|
|
|
|
}
|
2018-08-14 11:05:26 +02:00
|
|
|
return c.Platform.escapedQuote + message + c.Platform.escapedQuote
|
|
|
|
}
|
2018-08-18 12:14:44 +02:00
|
|
|
|
|
|
|
// Unquote removes wrapping quotations marks if they are present
|
|
|
|
// this is needed for removing quotes from staged filenames with spaces
|
|
|
|
func (c *OSCommand) Unquote(message string) string {
|
2018-08-21 22:05:05 +02:00
|
|
|
return strings.Replace(message, `"`, "", -1)
|
2018-08-18 12:14:44 +02:00
|
|
|
}
|
2018-08-19 12:41:04 +02:00
|
|
|
|
2018-08-21 08:40:51 +02:00
|
|
|
// AppendLineToFile adds a new line in file
|
|
|
|
func (c *OSCommand) AppendLineToFile(filename, line string) error {
|
2018-08-19 12:41:04 +02:00
|
|
|
f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
_, err = f.WriteString("\n" + line)
|
|
|
|
return err
|
|
|
|
}
|