2020-09-29 11:10:57 +02:00
|
|
|
package oscommands
|
2018-08-14 11:05:26 +02:00
|
|
|
|
|
|
|
import (
|
2019-11-21 12:09:14 +02:00
|
|
|
"fmt"
|
2018-12-05 10:33:46 +02:00
|
|
|
"io/ioutil"
|
2018-08-14 11:05:26 +02:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
2019-03-03 03:44:10 +02:00
|
|
|
"path/filepath"
|
2018-08-16 23:04:39 +02:00
|
|
|
"strings"
|
2019-05-30 14:45:56 +02:00
|
|
|
"sync"
|
2018-08-14 11:05:26 +02:00
|
|
|
|
2019-02-11 12:30:27 +02:00
|
|
|
"github.com/go-errors/errors"
|
|
|
|
|
2020-08-23 14:04:24 +02:00
|
|
|
"github.com/atotto/clipboard"
|
2022-03-19 07:34:46 +02:00
|
|
|
"github.com/jesseduffield/generics/slices"
|
2021-12-29 02:37:15 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/common"
|
2022-04-01 16:38:41 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/config"
|
2018-08-31 10:43:54 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
2018-08-14 11:05:26 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// OSCommand holds all the os commands
|
|
|
|
type OSCommand struct {
|
2021-12-29 02:37:15 +02:00
|
|
|
*common.Common
|
2021-12-07 12:59:36 +02:00
|
|
|
Platform *Platform
|
2022-01-08 04:22:29 +02:00
|
|
|
getenvFn func(string) string
|
2022-01-07 05:45:18 +02:00
|
|
|
guiIO *guiIO
|
2021-04-10 08:01:46 +02:00
|
|
|
|
2022-01-08 04:22:29 +02:00
|
|
|
removeFileFn func(string) error
|
2021-12-07 12:59:36 +02:00
|
|
|
|
2021-12-29 05:33:38 +02:00
|
|
|
Cmd *CmdObjBuilder
|
2022-04-01 16:38:41 +02:00
|
|
|
|
|
|
|
tempDir string
|
2021-04-10 08:01:46 +02:00
|
|
|
}
|
|
|
|
|
2021-12-30 04:11:58 +02:00
|
|
|
// Platform stores the os state
|
|
|
|
type Platform struct {
|
|
|
|
OS string
|
|
|
|
Shell string
|
|
|
|
ShellArg string
|
|
|
|
OpenCommand string
|
|
|
|
OpenLinkCommand string
|
|
|
|
}
|
|
|
|
|
2018-08-14 11:05:26 +02:00
|
|
|
// NewOSCommand os command runner
|
2022-04-01 16:38:41 +02:00
|
|
|
func NewOSCommand(common *common.Common, config config.AppConfigurer, platform *Platform, guiIO *guiIO) *OSCommand {
|
2021-12-07 12:59:36 +02:00
|
|
|
c := &OSCommand{
|
2022-01-08 04:22:29 +02:00
|
|
|
Common: common,
|
|
|
|
Platform: platform,
|
|
|
|
getenvFn: os.Getenv,
|
|
|
|
removeFileFn: os.RemoveAll,
|
|
|
|
guiIO: guiIO,
|
2022-04-01 16:38:41 +02:00
|
|
|
tempDir: config.GetTempDir(),
|
2021-04-10 08:01:46 +02:00
|
|
|
}
|
2021-12-07 12:59:36 +02:00
|
|
|
|
2022-01-02 01:34:33 +02:00
|
|
|
runner := &cmdObjRunner{log: common.Log, guiIO: guiIO}
|
2022-01-05 03:01:59 +02:00
|
|
|
c.Cmd = &CmdObjBuilder{runner: runner, platform: platform}
|
2021-12-29 05:33:38 +02:00
|
|
|
|
2021-12-07 12:59:36 +02:00
|
|
|
return c
|
2021-04-10 08:01:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *OSCommand) LogCommand(cmdStr string, commandLine bool) {
|
|
|
|
c.Log.WithField("command", cmdStr).Info("RunCommand")
|
|
|
|
|
2022-01-07 05:45:18 +02:00
|
|
|
c.guiIO.logCommandFn(cmdStr, commandLine)
|
2021-04-10 05:08:51 +02:00
|
|
|
}
|
|
|
|
|
2018-08-28 11:12:35 +02:00
|
|
|
// FileType tells us if the file is a file, directory or other
|
2021-12-30 08:19:01 +02:00
|
|
|
func FileType(path string) string {
|
2018-08-28 11:12:35 +02:00
|
|
|
fileInfo, err := os.Stat(path)
|
|
|
|
if err != nil {
|
|
|
|
return "other"
|
|
|
|
}
|
|
|
|
if fileInfo.IsDir() {
|
|
|
|
return "directory"
|
|
|
|
}
|
|
|
|
return "file"
|
|
|
|
}
|
|
|
|
|
2018-08-21 22:17:34 +02:00
|
|
|
func (c *OSCommand) OpenFile(filename string) error {
|
2022-02-06 04:42:17 +02:00
|
|
|
return c.OpenFileAtLine(filename, 1)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *OSCommand) OpenFileAtLine(filename string, lineNumber int) error {
|
2021-12-29 02:37:15 +02:00
|
|
|
commandTemplate := c.UserConfig.OS.OpenCommand
|
2018-08-31 10:43:54 +02:00
|
|
|
templateValues := map[string]string{
|
2021-10-09 09:32:36 +02:00
|
|
|
"filename": c.Quote(filename),
|
2022-02-06 04:42:17 +02:00
|
|
|
"line": fmt.Sprintf("%d", lineNumber),
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
2018-08-31 10:43:54 +02:00
|
|
|
command := utils.ResolvePlaceholderString(commandTemplate, templateValues)
|
2021-12-29 05:33:38 +02:00
|
|
|
return c.Cmd.NewShell(command).Run()
|
2018-08-14 11:05:26 +02:00
|
|
|
}
|
|
|
|
|
2018-10-17 14:20:15 +02:00
|
|
|
func (c *OSCommand) OpenLink(link string) error {
|
2021-12-29 02:37:15 +02:00
|
|
|
commandTemplate := c.UserConfig.OS.OpenLinkCommand
|
2018-10-17 14:20:15 +02:00
|
|
|
templateValues := map[string]string{
|
|
|
|
"link": c.Quote(link),
|
|
|
|
}
|
|
|
|
|
|
|
|
command := utils.ResolvePlaceholderString(commandTemplate, templateValues)
|
2021-12-29 05:33:38 +02:00
|
|
|
return c.Cmd.NewShell(command).Run()
|
2018-10-17 14:20:15 +02:00
|
|
|
}
|
|
|
|
|
2018-08-14 11:05:26 +02:00
|
|
|
// Quote wraps a message in platform-specific quotation marks
|
|
|
|
func (c *OSCommand) Quote(message string) string {
|
2021-12-29 05:33:38 +02:00
|
|
|
return c.Cmd.Quote(message)
|
|
|
|
}
|
|
|
|
|
2018-08-21 08:40:51 +02:00
|
|
|
// AppendLineToFile adds a new line in file
|
|
|
|
func (c *OSCommand) AppendLineToFile(filename, line string) error {
|
2021-04-10 08:01:46 +02:00
|
|
|
c.LogCommand(fmt.Sprintf("Appending '%s' to file '%s'", line, filename), false)
|
2022-03-19 00:38:49 +02:00
|
|
|
f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o600)
|
2018-08-19 12:41:04 +02:00
|
|
|
if err != nil {
|
2020-09-29 11:10:57 +02:00
|
|
|
return utils.WrapError(err)
|
2018-08-19 12:41:04 +02:00
|
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
|
|
|
|
_, err = f.WriteString("\n" + line)
|
2019-02-20 11:01:29 +02:00
|
|
|
if err != nil {
|
2020-09-29 11:10:57 +02:00
|
|
|
return utils.WrapError(err)
|
2019-02-20 11:01:29 +02:00
|
|
|
}
|
|
|
|
return nil
|
2018-08-19 12:41:04 +02:00
|
|
|
}
|
2018-12-05 10:33:46 +02:00
|
|
|
|
2019-11-05 03:42:07 +02:00
|
|
|
// CreateFileWithContent creates a file with the given content
|
|
|
|
func (c *OSCommand) CreateFileWithContent(path string, content string) error {
|
2021-04-10 08:01:46 +02:00
|
|
|
c.LogCommand(fmt.Sprintf("Creating file '%s'", path), false)
|
2019-11-05 03:42:07 +02:00
|
|
|
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
|
|
|
|
c.Log.Error(err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-03-19 00:38:49 +02:00
|
|
|
if err := ioutil.WriteFile(path, []byte(content), 0o644); err != nil {
|
2019-11-05 03:42:07 +02:00
|
|
|
c.Log.Error(err)
|
2020-09-29 11:10:57 +02:00
|
|
|
return utils.WrapError(err)
|
2019-11-05 03:42:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-03-18 11:44:33 +02:00
|
|
|
// Remove removes a file or directory at the specified path
|
|
|
|
func (c *OSCommand) Remove(filename string) error {
|
2021-04-10 08:01:46 +02:00
|
|
|
c.LogCommand(fmt.Sprintf("Removing '%s'", filename), false)
|
2019-03-18 11:44:33 +02:00
|
|
|
err := os.RemoveAll(filename)
|
2020-09-29 11:10:57 +02:00
|
|
|
return utils.WrapError(err)
|
2018-12-05 10:33:46 +02:00
|
|
|
}
|
2018-12-05 13:30:10 +02:00
|
|
|
|
|
|
|
// FileExists checks whether a file exists at the specified path
|
|
|
|
func (c *OSCommand) FileExists(path string) (bool, error) {
|
|
|
|
if _, err := os.Stat(path); err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}
|
2019-02-18 14:27:54 +02:00
|
|
|
|
2019-05-30 14:45:56 +02:00
|
|
|
// PipeCommands runs a heap of commands and pipes their inputs/outputs together like A | B | C
|
|
|
|
func (c *OSCommand) PipeCommands(commandStrings ...string) error {
|
2022-03-19 07:34:46 +02:00
|
|
|
cmds := slices.Map(commandStrings, func(cmdString string) *exec.Cmd {
|
|
|
|
return c.Cmd.New(cmdString).GetCmd()
|
|
|
|
})
|
|
|
|
|
|
|
|
logCmdStr := strings.Join(commandStrings, " | ")
|
2021-04-10 08:01:46 +02:00
|
|
|
c.LogCommand(logCmdStr, true)
|
2019-05-30 14:45:56 +02:00
|
|
|
|
|
|
|
for i := 0; i < len(cmds)-1; i++ {
|
|
|
|
stdout, err := cmds[i].StdoutPipe()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
cmds[i+1].Stdin = stdout
|
|
|
|
}
|
|
|
|
|
|
|
|
// keeping this here in case I adapt this code for some other purpose in the future
|
|
|
|
// cmds[len(cmds)-1].Stdout = os.Stdout
|
|
|
|
|
|
|
|
finalErrors := []string{}
|
|
|
|
|
|
|
|
wg := sync.WaitGroup{}
|
|
|
|
wg.Add(len(cmds))
|
|
|
|
|
|
|
|
for _, cmd := range cmds {
|
|
|
|
currentCmd := cmd
|
2020-10-07 12:19:38 +02:00
|
|
|
go utils.Safe(func() {
|
2019-05-30 14:45:56 +02:00
|
|
|
stderr, err := currentCmd.StderrPipe()
|
|
|
|
if err != nil {
|
|
|
|
c.Log.Error(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := currentCmd.Start(); err != nil {
|
|
|
|
c.Log.Error(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if b, err := ioutil.ReadAll(stderr); err == nil {
|
|
|
|
if len(b) > 0 {
|
|
|
|
finalErrors = append(finalErrors, string(b))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := currentCmd.Wait(); err != nil {
|
|
|
|
c.Log.Error(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
wg.Done()
|
2020-10-07 12:19:38 +02:00
|
|
|
})
|
2019-05-30 14:45:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
wg.Wait()
|
|
|
|
|
|
|
|
if len(finalErrors) > 0 {
|
|
|
|
return errors.New(strings.Join(finalErrors, "\n"))
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2020-03-01 03:30:48 +02:00
|
|
|
|
|
|
|
func Kill(cmd *exec.Cmd) error {
|
|
|
|
if cmd.Process == nil {
|
|
|
|
// somebody got to it before we were able to, poor bastard
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return cmd.Process.Kill()
|
|
|
|
}
|
2020-03-26 12:11:21 +02:00
|
|
|
|
2020-04-15 12:30:24 +02:00
|
|
|
func (c *OSCommand) CopyToClipboard(str string) error {
|
2021-10-16 04:49:40 +02:00
|
|
|
escaped := strings.Replace(str, "\n", "\\n", -1)
|
|
|
|
truncated := utils.TruncateWithEllipsis(escaped, 40)
|
|
|
|
c.LogCommand(fmt.Sprintf("Copying '%s' to clipboard", truncated), false)
|
2020-08-23 14:04:24 +02:00
|
|
|
return clipboard.WriteAll(str)
|
2020-04-15 12:30:24 +02:00
|
|
|
}
|
2021-04-10 08:01:46 +02:00
|
|
|
|
|
|
|
func (c *OSCommand) RemoveFile(path string) error {
|
|
|
|
c.LogCommand(fmt.Sprintf("Deleting path '%s'", path), false)
|
|
|
|
|
2022-01-08 04:22:29 +02:00
|
|
|
return c.removeFileFn(path)
|
2021-04-10 08:01:46 +02:00
|
|
|
}
|
2021-10-20 13:21:16 +02:00
|
|
|
|
2022-01-02 01:34:33 +02:00
|
|
|
func (c *OSCommand) Getenv(key string) string {
|
2022-01-08 04:22:29 +02:00
|
|
|
return c.getenvFn(key)
|
2022-01-02 01:34:33 +02:00
|
|
|
}
|
|
|
|
|
2022-04-01 16:38:41 +02:00
|
|
|
func (c *OSCommand) GetTempDir() string {
|
|
|
|
return c.tempDir
|
2021-12-29 02:41:33 +02:00
|
|
|
}
|
2022-01-05 03:01:59 +02:00
|
|
|
|
|
|
|
// GetLazygitPath returns the path of the currently executed file
|
|
|
|
func GetLazygitPath() string {
|
|
|
|
ex, err := os.Executable() // get the executable path for git to use
|
|
|
|
if err != nil {
|
|
|
|
ex = os.Args[0] // fallback to the first call argument if needed
|
|
|
|
}
|
|
|
|
return `"` + filepath.ToSlash(ex) + `"`
|
|
|
|
}
|
2022-01-19 11:42:32 +02:00
|
|
|
|
2022-01-19 13:29:40 +02:00
|
|
|
func (c *OSCommand) UpdateWindowTitle() error {
|
2022-01-20 09:36:07 +02:00
|
|
|
if c.Platform.OS != "windows" {
|
2022-01-19 11:42:32 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
path, getWdErr := os.Getwd()
|
|
|
|
if getWdErr != nil {
|
|
|
|
return getWdErr
|
|
|
|
}
|
2022-01-20 09:36:07 +02:00
|
|
|
argString := fmt.Sprint("title ", filepath.Base(path), " - Lazygit")
|
|
|
|
return c.Cmd.NewShell(argString).Run()
|
2022-01-19 11:42:32 +02:00
|
|
|
}
|