1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2024-11-30 09:16:47 +02:00
lazygit/pkg/commands/os.go

170 lines
4.5 KiB
Go
Raw Normal View History

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
"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
openCommand string
2018-08-14 11:05:26 +02:00
}
// OSCommand holds all the os commands
type OSCommand struct {
Log *logrus.Entry
2018-08-22 22:31:35 +02:00
Platform *Platform
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
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(),
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")
templateValues := map[string]string{
"filename": c.Quote(filename),
2018-08-14 11:05:26 +02:00
}
2018-08-21 22:17:34 +02:00
command := utils.ResolvePlaceholderString(commandTemplate, templateValues)
2018-09-01 06:33:01 +02:00
err := c.RunCommand(command)
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 == "" {
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)
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
}