diff --git a/pkg/app/app.go b/pkg/app/app.go index f24af6e58..d4e3e15f7 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -146,7 +146,7 @@ func NewApp(config config.AppConfigurer, filterPath string) (*App, error) { } func (app *App) validateGitVersion() error { - output, err := app.OSCommand.RunCommandWithOutput("git --version") + output, err := app.OSCommand.RunWithOutput(app.OSCommand.NewCmdObj("git --version")) // if we get an error anywhere here we'll show the same status minVersionError := errors.New(app.Tr.MinGitVersionError) if err != nil { @@ -231,7 +231,7 @@ func (app *App) setupRepo() (bool, error) { os.Exit(1) } - if err := app.OSCommand.RunCommand("git init"); err != nil { + if err := app.OSCommand.Run(app.OSCommand.NewCmdObj("git init")); err != nil { return false, err } } diff --git a/pkg/commands/branches.go b/pkg/commands/branches.go index 3f6820c09..cc513a974 100644 --- a/pkg/commands/branches.go +++ b/pkg/commands/branches.go @@ -11,19 +11,19 @@ import ( // NewBranch create new branch func (c *GitCommand) NewBranch(name string, base string) error { - return c.RunCommand("git checkout -b %s %s", c.OSCommand.Quote(name), c.OSCommand.Quote(base)) + return c.Run(c.NewCmdObj(fmt.Sprintf("git checkout -b %s %s", c.OSCommand.Quote(name), c.OSCommand.Quote(base)))) } // CurrentBranchName get the current branch name and displayname. // the first returned string is the name and the second is the displayname // e.g. name is 123asdf and displayname is '(HEAD detached at 123asdf)' func (c *GitCommand) CurrentBranchName() (string, string, error) { - branchName, err := c.RunCommandWithOutput("git symbolic-ref --short HEAD") + branchName, err := c.RunWithOutput(c.NewCmdObj("git symbolic-ref --short HEAD")) if err == nil && branchName != "HEAD\n" { trimmedBranchName := strings.TrimSpace(branchName) return trimmedBranchName, trimmedBranchName, nil } - output, err := c.RunCommandWithOutput("git branch --contains") + output, err := c.RunWithOutput(c.NewCmdObj("git branch --contains")) if err != nil { return "", "", err } @@ -47,7 +47,7 @@ func (c *GitCommand) DeleteBranch(branch string, force bool) error { command = "git branch -D" } - return c.OSCommand.RunCommand("%s %s", command, c.OSCommand.Quote(branch)) + return c.OSCommand.Run(c.OSCommand.NewCmdObj(fmt.Sprintf("%s %s", command, c.OSCommand.Quote(branch)))) } // Checkout checks out a branch (or commit), with --force if you set the force arg to true @@ -61,36 +61,42 @@ func (c *GitCommand) Checkout(branch string, options CheckoutOptions) error { if options.Force { forceArg = " --force" } - return c.OSCommand.RunCommandWithOptions(fmt.Sprintf("git checkout%s %s", forceArg, c.OSCommand.Quote(branch)), oscommands.RunCommandOptions{EnvVars: options.EnvVars}) + + cmdObj := c.NewCmdObj(fmt.Sprintf("git checkout%s %s", forceArg, c.OSCommand.Quote(branch))). + // prevents git from prompting us for input which would freeze the program + // TODO: see if this is actually needed here + AddEnvVars("GIT_TERMINAL_PROMPT=0"). + AddEnvVars(options.EnvVars...) + + return c.OSCommand.Run(cmdObj) } // GetBranchGraph gets the color-formatted graph of the log for the given branch // Currently it limits the result to 100 commits, but when we get async stuff // working we can do lazy loading func (c *GitCommand) GetBranchGraph(branchName string) (string, error) { - cmdStr := c.GetBranchGraphCmdStr(branchName) - return c.OSCommand.RunCommandWithOutput(cmdStr) + return c.OSCommand.RunWithOutput(c.GetBranchGraphCmdObj(branchName)) } func (c *GitCommand) GetUpstreamForBranch(branchName string) (string, error) { - output, err := c.RunCommandWithOutput("git rev-parse --abbrev-ref --symbolic-full-name %s@{u}", c.OSCommand.Quote(branchName)) + output, err := c.RunWithOutput(c.NewCmdObj(fmt.Sprintf("git rev-parse --abbrev-ref --symbolic-full-name %s@{u}", c.OSCommand.Quote(branchName)))) return strings.TrimSpace(output), err } -func (c *GitCommand) GetBranchGraphCmdStr(branchName string) string { +func (c *GitCommand) GetBranchGraphCmdObj(branchName string) oscommands.ICmdObj { branchLogCmdTemplate := c.Config.GetUserConfig().Git.BranchLogCmd templateValues := map[string]string{ "branchName": c.OSCommand.Quote(branchName), } - return utils.ResolvePlaceholderString(branchLogCmdTemplate, templateValues) + return c.NewCmdObj(utils.ResolvePlaceholderString(branchLogCmdTemplate, templateValues)) } func (c *GitCommand) SetUpstreamBranch(upstream string) error { - return c.RunCommand("git branch -u %s", c.OSCommand.Quote(upstream)) + return c.Run(c.NewCmdObj("git branch -u " + c.OSCommand.Quote(upstream))) } func (c *GitCommand) SetBranchUpstream(remoteName string, remoteBranchName string, branchName string) error { - return c.RunCommand("git branch --set-upstream-to=%s/%s %s", c.OSCommand.Quote(remoteName), c.OSCommand.Quote(remoteBranchName), c.OSCommand.Quote(branchName)) + return c.Run(c.NewCmdObj(fmt.Sprintf("git branch --set-upstream-to=%s/%s %s", c.OSCommand.Quote(remoteName), c.OSCommand.Quote(remoteBranchName), c.OSCommand.Quote(branchName)))) } func (c *GitCommand) GetCurrentBranchUpstreamDifferenceCount() (string, string) { @@ -105,11 +111,11 @@ func (c *GitCommand) GetBranchUpstreamDifferenceCount(branchName string) (string // current branch func (c *GitCommand) GetCommitDifferences(from, to string) (string, string) { command := "git rev-list %s..%s --count" - pushableCount, err := c.OSCommand.RunCommandWithOutput(command, to, from) + pushableCount, err := c.RunWithOutput(c.NewCmdObj(fmt.Sprintf(command, to, from))) if err != nil { return "?", "?" } - pullableCount, err := c.OSCommand.RunCommandWithOutput(command, from, to) + pullableCount, err := c.RunWithOutput(c.NewCmdObj(fmt.Sprintf(command, from, to))) if err != nil { return "?", "?" } @@ -129,33 +135,33 @@ func (c *GitCommand) Merge(branchName string, opts MergeOpts) error { command = fmt.Sprintf("%s --ff-only", command) } - return c.OSCommand.RunCommand(command) + return c.OSCommand.Run(c.OSCommand.NewCmdObj(command)) } // AbortMerge abort merge func (c *GitCommand) AbortMerge() error { - return c.RunCommand("git merge --abort") + return c.Run(c.NewCmdObj("git merge --abort")) } func (c *GitCommand) IsHeadDetached() bool { - err := c.RunCommand("git symbolic-ref -q HEAD") + err := c.Run(c.NewCmdObj("git symbolic-ref -q HEAD")) return err != nil } // ResetHardHead runs `git reset --hard` func (c *GitCommand) ResetHard(ref string) error { - return c.RunCommand("git reset --hard " + c.OSCommand.Quote(ref)) + return c.Run(c.NewCmdObj("git reset --hard " + c.OSCommand.Quote(ref))) } // ResetSoft runs `git reset --soft HEAD` func (c *GitCommand) ResetSoft(ref string) error { - return c.RunCommand("git reset --soft " + c.OSCommand.Quote(ref)) + return c.Run(c.NewCmdObj("git reset --soft " + c.OSCommand.Quote(ref))) } func (c *GitCommand) ResetMixed(ref string) error { - return c.RunCommand("git reset --mixed " + c.OSCommand.Quote(ref)) + return c.Run(c.NewCmdObj("git reset --mixed " + c.OSCommand.Quote(ref))) } func (c *GitCommand) RenameBranch(oldName string, newName string) error { - return c.RunCommand("git branch --move %s %s", c.OSCommand.Quote(oldName), c.OSCommand.Quote(newName)) + return c.Run(c.NewCmdObj(fmt.Sprintf("git branch --move %s %s", c.OSCommand.Quote(oldName), c.OSCommand.Quote(newName)))) } diff --git a/pkg/commands/branches_test.go b/pkg/commands/branches_test.go index 85c477523..ee050e5be 100644 --- a/pkg/commands/branches_test.go +++ b/pkg/commands/branches_test.go @@ -210,7 +210,7 @@ func TestGitCommandGetAllBranchGraph(t *testing.T) { return secureexec.Command("echo") } cmdStr := gitCmd.Config.GetUserConfig().Git.AllBranchesLogCmd - _, err := gitCmd.OSCommand.RunCommandWithOutput(cmdStr) + _, err := gitCmd.OSCommand.RunWithOutput(gitCmd.NewCmdObj(cmdStr)) assert.NoError(t, err) } diff --git a/pkg/commands/commits.go b/pkg/commands/commits.go index 56b8533ac..269eef073 100644 --- a/pkg/commands/commits.go +++ b/pkg/commands/commits.go @@ -10,15 +10,21 @@ import ( // RenameCommit renames the topmost commit with the given name func (c *GitCommand) RenameCommit(name string) error { - return c.RunCommand("git commit --allow-empty --amend --only -m %s", c.OSCommand.Quote(name)) + return c.Run(c.NewCmdObj("git commit --allow-empty --amend --only -m " + c.OSCommand.Quote(name))) } // ResetToCommit reset to commit -func (c *GitCommand) ResetToCommit(sha string, strength string, options oscommands.RunCommandOptions) error { - return c.OSCommand.RunCommandWithOptions(fmt.Sprintf("git reset --%s %s", strength, sha), options) +func (c *GitCommand) ResetToCommit(sha string, strength string, envVars []string) error { + cmdObj := c.NewCmdObj(fmt.Sprintf("git reset --%s %s", strength, sha)). + // prevents git from prompting us for input which would freeze the program + // TODO: see if this is actually needed here + AddEnvVars("GIT_TERMINAL_PROMPT=0"). + AddEnvVars(envVars...) + + return c.OSCommand.Run(cmdObj) } -func (c *GitCommand) CommitCmdStr(message string, flags string) string { +func (c *GitCommand) CommitCmdObj(message string, flags string) oscommands.ICmdObj { splitMessage := strings.Split(message, "\n") lineArgs := "" for _, line := range splitMessage { @@ -30,52 +36,53 @@ func (c *GitCommand) CommitCmdStr(message string, flags string) string { flagsStr = fmt.Sprintf(" %s", flags) } - return fmt.Sprintf("git commit%s%s", flagsStr, lineArgs) + return c.NewCmdObj(fmt.Sprintf("git commit%s%s", flagsStr, lineArgs)) } // Get the subject of the HEAD commit func (c *GitCommand) GetHeadCommitMessage() (string, error) { - cmdStr := "git log -1 --pretty=%s" - message, err := c.OSCommand.RunCommandWithOutput(cmdStr) + message, err := c.RunWithOutput(c.NewCmdObj("git log -1 --pretty=%s")) return strings.TrimSpace(message), err } func (c *GitCommand) GetCommitMessage(commitSha string) (string, error) { cmdStr := "git rev-list --format=%B --max-count=1 " + commitSha - messageWithHeader, err := c.OSCommand.RunCommandWithOutput(cmdStr) + messageWithHeader, err := c.RunWithOutput(c.NewCmdObj(cmdStr)) message := strings.Join(strings.SplitAfter(messageWithHeader, "\n")[1:], "\n") return strings.TrimSpace(message), err } func (c *GitCommand) GetCommitMessageFirstLine(sha string) (string, error) { - return c.RunCommandWithOutput("git show --no-patch --pretty=format:%%s %s", sha) + return c.RunWithOutput(c.NewCmdObj(fmt.Sprintf("git show --no-patch --pretty=format:%%s %s", sha))) } // AmendHead amends HEAD with whatever is staged in your working tree func (c *GitCommand) AmendHead() error { - return c.OSCommand.RunCommand(c.AmendHeadCmdStr()) + return c.OSCommand.Run(c.AmendHeadCmdObj()) } -func (c *GitCommand) AmendHeadCmdStr() string { - return "git commit --amend --no-edit --allow-empty" +func (c *GitCommand) AmendHeadCmdObj() oscommands.ICmdObj { + return c.NewCmdObj("git commit --amend --no-edit --allow-empty") } -func (c *GitCommand) ShowCmdStr(sha string, filterPath string) string { +func (c *GitCommand) ShowCmdObj(sha string, filterPath string) oscommands.ICmdObj { contextSize := c.Config.GetUserConfig().Git.DiffContextSize filterPathArg := "" if filterPath != "" { filterPathArg = fmt.Sprintf(" -- %s", c.OSCommand.Quote(filterPath)) } - return fmt.Sprintf("git show --submodule --color=%s --unified=%d --no-renames --stat -p %s %s", c.colorArg(), contextSize, sha, filterPathArg) + + cmdStr := fmt.Sprintf("git show --submodule --color=%s --unified=%d --no-renames --stat -p %s %s", c.colorArg(), contextSize, sha, filterPathArg) + return c.NewCmdObj(cmdStr) } // Revert reverts the selected commit by sha func (c *GitCommand) Revert(sha string) error { - return c.RunCommand("git revert %s", sha) + return c.Run(c.NewCmdObj(fmt.Sprintf("git revert %s", sha))) } func (c *GitCommand) RevertMerge(sha string, parentNumber int) error { - return c.RunCommand("git revert %s -m %d", sha, parentNumber) + return c.Run(c.NewCmdObj(fmt.Sprintf("git revert %s -m %d", sha, parentNumber))) } // CherryPickCommits begins an interactive rebase with the given shas being cherry picked onto HEAD @@ -90,10 +97,10 @@ func (c *GitCommand) CherryPickCommits(commits []*models.Commit) error { return err } - return c.OSCommand.RunPreparedCommand(cmd) + return c.OSCommand.Run(cmd) } // CreateFixupCommit creates a commit that fixes up a previous commit func (c *GitCommand) CreateFixupCommit(sha string) error { - return c.RunCommand("git commit --fixup=%s", sha) + return c.Run(c.NewCmdObj(fmt.Sprintf("git commit --fixup=%s", sha))) } diff --git a/pkg/commands/commits_test.go b/pkg/commands/commits_test.go index f6dd1f71b..04a5c01d7 100644 --- a/pkg/commands/commits_test.go +++ b/pkg/commands/commits_test.go @@ -4,7 +4,6 @@ import ( "os/exec" "testing" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/secureexec" "github.com/jesseduffield/lazygit/pkg/test" "github.com/stretchr/testify/assert" @@ -33,11 +32,11 @@ func TestGitCommandResetToCommit(t *testing.T) { return secureexec.Command("echo") } - assert.NoError(t, gitCmd.ResetToCommit("78976bc", "hard", oscommands.RunCommandOptions{})) + assert.NoError(t, gitCmd.ResetToCommit("78976bc", "hard", []string{})) } -// TestGitCommandCommitStr is a function. -func TestGitCommandCommitStr(t *testing.T) { +// TestGitCommandCommitObj is a function. +func TestGitCommandCommitObj(t *testing.T) { gitCmd := NewDummyGitCommand() type scenario struct { @@ -70,7 +69,7 @@ func TestGitCommandCommitStr(t *testing.T) { for _, s := range scenarios { t.Run(s.testName, func(t *testing.T) { - cmdStr := gitCmd.CommitCmdStr(s.message, s.flags) + cmdStr := gitCmd.CommitCmdObj(s.message, s.flags).ToString() assert.Equal(t, s.expected, cmdStr) }) } @@ -111,8 +110,8 @@ func TestGitCommandCreateFixupCommit(t *testing.T) { } } -// TestGitCommandShowCmdStr is a function. -func TestGitCommandShowCmdStr(t *testing.T) { +// TestGitCommandShowCmdObj is a function. +func TestGitCommandShowCmdObj(t *testing.T) { type scenario struct { testName string filterPath string @@ -146,7 +145,7 @@ func TestGitCommandShowCmdStr(t *testing.T) { for _, s := range scenarios { t.Run(s.testName, func(t *testing.T) { gitCmd.Config.GetUserConfig().Git.DiffContextSize = s.contextSize - cmdStr := gitCmd.ShowCmdStr("1234567890", s.filterPath) + cmdStr := gitCmd.ShowCmdObj("1234567890", s.filterPath).ToString() assert.Equal(t, s.expected, cmdStr) }) } diff --git a/pkg/commands/files.go b/pkg/commands/files.go index 8eb154c30..289337b70 100644 --- a/pkg/commands/files.go +++ b/pkg/commands/files.go @@ -10,6 +10,7 @@ import ( "github.com/go-errors/errors" "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/gui/filetree" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -23,27 +24,27 @@ func (c *GitCommand) CatFile(fileName string) (string, error) { return string(buf), nil } -func (c *GitCommand) OpenMergeToolCmd() string { - return "git mergetool" +func (c *GitCommand) OpenMergeToolCmdObj() oscommands.ICmdObj { + return c.NewCmdObj("git mergetool") } func (c *GitCommand) OpenMergeTool() error { - return c.OSCommand.RunCommand("git mergetool") + return c.Run(c.OpenMergeToolCmdObj()) } // StageFile stages a file func (c *GitCommand) StageFile(fileName string) error { - return c.RunCommand("git add -- %s", c.OSCommand.Quote(fileName)) + return c.Run(c.NewCmdObj("git add -- " + c.OSCommand.Quote(fileName))) } // StageAll stages all files func (c *GitCommand) StageAll() error { - return c.RunCommand("git add -A") + return c.Run(c.NewCmdObj("git add -A")) } // UnstageAll unstages all files func (c *GitCommand) UnstageAll() error { - return c.RunCommand("git reset") + return c.Run(c.NewCmdObj("git reset")) } // UnStageFile unstages a file @@ -56,7 +57,8 @@ func (c *GitCommand) UnStageFile(fileNames []string, reset bool) error { } for _, name := range fileNames { - if err := c.OSCommand.RunCommand(command, c.OSCommand.Quote(name)); err != nil { + cmdObj := c.NewCmdObj(fmt.Sprintf(command, c.OSCommand.Quote(name))) + if err := c.Run(cmdObj); err != nil { return err } } @@ -120,22 +122,22 @@ func (c *GitCommand) DiscardAllFileChanges(file *models.File) error { quotedFileName := c.OSCommand.Quote(file.Name) if file.ShortStatus == "AA" { - if err := c.RunCommand("git checkout --ours -- %s", quotedFileName); err != nil { + if err := c.Run(c.NewCmdObj("git checkout --ours -- " + quotedFileName)); err != nil { return err } - if err := c.RunCommand("git add -- %s", quotedFileName); err != nil { + if err := c.Run(c.NewCmdObj("git add -- " + quotedFileName)); err != nil { return err } return nil } if file.ShortStatus == "DU" { - return c.RunCommand("git rm -- %s", quotedFileName) + return c.Run(c.NewCmdObj("git rm -- " + quotedFileName)) } // if the file isn't tracked, we assume you want to delete it if file.HasStagedChanges || file.HasMergeConflicts { - if err := c.RunCommand("git reset -- %s", quotedFileName); err != nil { + if err := c.Run(c.NewCmdObj("git reset -- " + quotedFileName)); err != nil { return err } } @@ -161,7 +163,7 @@ func (c *GitCommand) DiscardUnstagedDirChanges(node *filetree.FileNode) error { } quotedPath := c.OSCommand.Quote(node.GetPath()) - if err := c.RunCommand("git checkout -- %s", quotedPath); err != nil { + if err := c.Run(c.NewCmdObj("git checkout -- " + quotedPath)); err != nil { return err } @@ -186,7 +188,7 @@ func (c *GitCommand) RemoveUntrackedDirFiles(node *filetree.FileNode) error { // DiscardUnstagedFileChanges directly func (c *GitCommand) DiscardUnstagedFileChanges(file *models.File) error { quotedFileName := c.OSCommand.Quote(file.Name) - return c.RunCommand("git checkout -- %s", quotedFileName) + return c.Run(c.NewCmdObj("git checkout -- " + quotedFileName)) } // Ignore adds a file to the gitignore for the repo @@ -197,11 +199,11 @@ func (c *GitCommand) Ignore(filename string) error { // WorktreeFileDiff returns the diff of a file func (c *GitCommand) WorktreeFileDiff(file *models.File, plain bool, cached bool, ignoreWhitespace bool) string { // for now we assume an error means the file was deleted - s, _ := c.OSCommand.RunCommandWithOutput(c.WorktreeFileDiffCmdStr(file, plain, cached, ignoreWhitespace)) + s, _ := c.OSCommand.RunWithOutput(c.WorktreeFileDiffCmdObj(file, plain, cached, ignoreWhitespace)) return s } -func (c *GitCommand) WorktreeFileDiffCmdStr(node models.IFile, plain bool, cached bool, ignoreWhitespace bool) string { +func (c *GitCommand) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool, ignoreWhitespace bool) oscommands.ICmdObj { cachedArg := "" trackedArg := "--" colorArg := c.colorArg() @@ -221,7 +223,9 @@ func (c *GitCommand) WorktreeFileDiffCmdStr(node models.IFile, plain bool, cache ignoreWhitespaceArg = "--ignore-all-space" } - return fmt.Sprintf("git diff --submodule --no-ext-diff --unified=%d --color=%s %s %s %s %s", contextSize, colorArg, ignoreWhitespaceArg, cachedArg, trackedArg, quotedPath) + cmdStr := fmt.Sprintf("git diff --submodule --no-ext-diff --unified=%d --color=%s %s %s %s %s", contextSize, colorArg, ignoreWhitespaceArg, cachedArg, trackedArg, quotedPath) + + return c.NewCmdObj(cmdStr) } func (c *GitCommand) ApplyPatch(patch string, flags ...string) error { @@ -236,17 +240,17 @@ func (c *GitCommand) ApplyPatch(patch string, flags ...string) error { flagStr += " --" + flag } - return c.RunCommand("git apply %s %s", flagStr, c.OSCommand.Quote(filepath)) + return c.Run(c.NewCmdObj(fmt.Sprintf("git apply %s %s", flagStr, c.OSCommand.Quote(filepath)))) } // ShowFileDiff get the diff of specified from and to. Typically this will be used for a single commit so it'll be 123abc^..123abc // but when we're in diff mode it could be any 'from' to any 'to'. The reverse flag is also here thanks to diff mode. func (c *GitCommand) ShowFileDiff(from string, to string, reverse bool, fileName string, plain bool) (string, error) { - cmdStr := c.ShowFileDiffCmdStr(from, to, reverse, fileName, plain) - return c.OSCommand.RunCommandWithOutput(cmdStr) + cmdObj := c.ShowFileDiffCmdObj(from, to, reverse, fileName, plain) + return c.RunWithOutput(cmdObj) } -func (c *GitCommand) ShowFileDiffCmdStr(from string, to string, reverse bool, fileName string, plain bool) string { +func (c *GitCommand) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool) oscommands.ICmdObj { colorArg := c.colorArg() contextSize := c.Config.GetUserConfig().Git.DiffContextSize if plain { @@ -258,12 +262,12 @@ func (c *GitCommand) ShowFileDiffCmdStr(from string, to string, reverse bool, fi reverseFlag = " -R " } - return fmt.Sprintf("git diff --submodule --no-ext-diff --unified=%d --no-renames --color=%s %s %s %s -- %s", contextSize, colorArg, from, to, reverseFlag, c.OSCommand.Quote(fileName)) + return c.NewCmdObj(fmt.Sprintf("git diff --submodule --no-ext-diff --unified=%d --no-renames --color=%s %s %s %s -- %s", contextSize, colorArg, from, to, reverseFlag, c.OSCommand.Quote(fileName))) } // CheckoutFile checks out the file for the given commit func (c *GitCommand) CheckoutFile(commitSha, fileName string) error { - return c.RunCommand("git checkout %s -- %s", commitSha, c.OSCommand.Quote(fileName)) + return c.Run(c.NewCmdObj(fmt.Sprintf("git checkout %s -- %s", commitSha, c.OSCommand.Quote(fileName)))) } // DiscardOldFileChanges discards changes to a file from an old commit @@ -273,7 +277,7 @@ func (c *GitCommand) DiscardOldFileChanges(commits []*models.Commit, commitIndex } // check if file exists in previous commit (this command returns an error if the file doesn't exist) - if err := c.RunCommand("git cat-file -e HEAD^:%s", c.OSCommand.Quote(fileName)); err != nil { + if err := c.Run(c.NewCmdObj("git cat-file -e HEAD^:" + c.OSCommand.Quote(fileName))); err != nil { if err := c.OSCommand.Remove(fileName); err != nil { return err } @@ -296,17 +300,17 @@ func (c *GitCommand) DiscardOldFileChanges(commits []*models.Commit, commitIndex // DiscardAnyUnstagedFileChanges discards any unstages file changes via `git checkout -- .` func (c *GitCommand) DiscardAnyUnstagedFileChanges() error { - return c.RunCommand("git checkout -- .") + return c.Run(c.NewCmdObj("git checkout -- .")) } // RemoveTrackedFiles will delete the given file(s) even if they are currently tracked func (c *GitCommand) RemoveTrackedFiles(name string) error { - return c.RunCommand("git rm -r --cached -- %s", c.OSCommand.Quote(name)) + return c.Run(c.NewCmdObj("git rm -r --cached -- " + c.OSCommand.Quote(name))) } // RemoveUntrackedFiles runs `git clean -fd` func (c *GitCommand) RemoveUntrackedFiles() error { - return c.RunCommand("git clean -fd") + return c.Run(c.NewCmdObj("git clean -fd")) } // ResetAndClean removes all unstaged changes and removes all untracked files @@ -346,7 +350,7 @@ func (c *GitCommand) EditFileCmdStr(filename string, lineNumber int) (string, er editor = c.OSCommand.Getenv("EDITOR") } if editor == "" { - if err := c.OSCommand.RunCommand("which vi"); err == nil { + if err := c.OSCommand.Run(c.NewCmdObj("which vi")); err == nil { editor = "vi" } } diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 8750ecf51..7d4cc6c86 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -223,22 +223,22 @@ func findDotGitDir(stat func(string) (os.FileInfo, error), readFile func(filenam } func VerifyInGitRepo(osCommand *oscommands.OSCommand) error { - return osCommand.RunCommand("git rev-parse --git-dir") + return osCommand.Run(osCommand.NewCmdObj("git rev-parse --git-dir")) } -func (c *GitCommand) RunCommand(formatString string, formatArgs ...interface{}) error { - _, err := c.RunCommandWithOutput(formatString, formatArgs...) +func (c *GitCommand) Run(cmdObj oscommands.ICmdObj) error { + _, err := c.RunWithOutput(cmdObj) return err } -func (c *GitCommand) RunCommandWithOutput(formatString string, formatArgs ...interface{}) (string, error) { +func (c *GitCommand) RunWithOutput(cmdObj oscommands.ICmdObj) (string, error) { // TODO: have this retry logic in other places we run the command waitTime := 50 * time.Millisecond retryCount := 5 attempt := 0 for { - output, err := c.OSCommand.RunCommandWithOutput(formatString, formatArgs...) + output, err := c.OSCommand.RunWithOutput(cmdObj) if err != nil { // if we have an error based on the index lock, we should wait a bit and then retry if strings.Contains(output, ".git/index.lock") { @@ -255,6 +255,12 @@ func (c *GitCommand) RunCommandWithOutput(formatString string, formatArgs ...int } } -func (c *GitCommand) NewCmdObjFromStr(cmdStr string) oscommands.ICmdObj { - return c.OSCommand.NewCmdObjFromStr(cmdStr).AddEnvVars("GIT_OPTIONAL_LOCKS=0") +func (c *GitCommand) NewCmdObj(cmdStr string) oscommands.ICmdObj { + return c.OSCommand.NewCmdObj(cmdStr).AddEnvVars("GIT_OPTIONAL_LOCKS=0") +} + +func (c *GitCommand) NewCmdObjWithLog(cmdStr string) oscommands.ICmdObj { + cmdObj := c.NewCmdObj(cmdStr) + c.OSCommand.LogCmdObj(cmdObj) + return cmdObj } diff --git a/pkg/commands/loading_branches.go b/pkg/commands/loading_branches.go index 7565a9aee..5453db2de 100644 --- a/pkg/commands/loading_branches.go +++ b/pkg/commands/loading_branches.go @@ -38,7 +38,7 @@ func NewBranchListBuilder(log *logrus.Entry, gitCommand *GitCommand, reflogCommi func (b *BranchListBuilder) obtainBranches() []*models.Branch { cmdStr := `git for-each-ref --sort=-committerdate --format="%(HEAD)|%(refname:short)|%(upstream:short)|%(upstream:track)" refs/heads` - output, err := b.GitCommand.OSCommand.RunCommandWithOutput(cmdStr) + output, err := b.GitCommand.RunWithOutput(b.GitCommand.NewCmdObj(cmdStr)) if err != nil { panic(err) } diff --git a/pkg/commands/loading_commit_files.go b/pkg/commands/loading_commit_files.go index bddddc761..95d98d668 100644 --- a/pkg/commands/loading_commit_files.go +++ b/pkg/commands/loading_commit_files.go @@ -1,6 +1,7 @@ package commands import ( + "fmt" "strings" "github.com/jesseduffield/lazygit/pkg/commands/models" @@ -13,7 +14,7 @@ func (c *GitCommand) GetFilesInDiff(from string, to string, reverse bool) ([]*mo reverseFlag = " -R " } - filenames, err := c.RunCommandWithOutput("git diff --submodule --no-ext-diff --name-status -z --no-renames %s %s %s", reverseFlag, from, to) + filenames, err := c.RunWithOutput(c.NewCmdObj(fmt.Sprintf("git diff --submodule --no-ext-diff --name-status -z --no-renames %s %s %s", reverseFlag, from, to))) if err != nil { return nil, err } diff --git a/pkg/commands/loading_commits.go b/pkg/commands/loading_commits.go index 204e4c9da..6f0935054 100644 --- a/pkg/commands/loading_commits.go +++ b/pkg/commands/loading_commits.go @@ -4,7 +4,6 @@ import ( "fmt" "io/ioutil" "os" - "os/exec" "path/filepath" "regexp" "strconv" @@ -153,9 +152,9 @@ func (c *CommitListBuilder) GetCommits(opts GetCommitsOptions) ([]*models.Commit passedFirstPushedCommit = true } - cmd := c.getLogCmd(opts) + cmdObj := c.getLogCmd(opts) - err = oscommands.RunLineOutputCmd(cmd, func(line string) (bool, error) { + err = c.OSCommand.RunLineOutputCmd(cmdObj, func(line string) (bool, error) { if canExtractCommit(line) { commit := c.extractCommitFromLine(line) if commit.Sha == firstPushedCommit { @@ -201,7 +200,7 @@ func (c *CommitListBuilder) getHydratedRebasingCommits(rebaseMode string) ([]*mo // note that we're not filtering these as we do non-rebasing commits just because // I suspect that will cause some damage - cmd := c.OSCommand.ExecutableFromString( + cmdObj := c.OSCommand.NewCmdObj( fmt.Sprintf( "git show %s --no-patch --oneline %s --abbrev=%d", strings.Join(commitShas, " "), @@ -212,7 +211,7 @@ func (c *CommitListBuilder) getHydratedRebasingCommits(rebaseMode string) ([]*mo hydratedCommits := make([]*models.Commit, 0, len(commits)) i := 0 - err = oscommands.RunLineOutputCmd(cmd, func(line string) (bool, error) { + err = c.OSCommand.RunLineOutputCmd(cmdObj, func(line string) (bool, error) { if canExtractCommit(line) { commit := c.extractCommitFromLine(line) matchingCommit := commits[i] @@ -374,7 +373,7 @@ func (c *CommitListBuilder) getMergeBase(refName string) (string, error) { } // swallowing error because it's not a big deal; probably because there are no commits yet - output, _ := c.OSCommand.RunCommandWithOutput("git merge-base %s %s", c.OSCommand.Quote(refName), c.OSCommand.Quote(baseBranch)) + output, _ := c.OSCommand.RunWithOutput(c.OSCommand.NewCmdObj(fmt.Sprintf("git merge-base %s %s", c.OSCommand.Quote(refName), c.OSCommand.Quote(baseBranch)))) return ignoringWarnings(output), nil } @@ -391,7 +390,7 @@ func ignoringWarnings(commandOutput string) string { // getFirstPushedCommit returns the first commit SHA which has been pushed to the ref's upstream. // all commits above this are deemed unpushed and marked as such. func (c *CommitListBuilder) getFirstPushedCommit(refName string) (string, error) { - output, err := c.OSCommand.RunCommandWithOutput("git merge-base %s %s@{u}", c.OSCommand.Quote(refName), c.OSCommand.Quote(refName)) + output, err := c.OSCommand.RunWithOutput(c.OSCommand.NewCmdObj(fmt.Sprintf("git merge-base %s %s@{u}", c.OSCommand.Quote(refName), c.OSCommand.Quote(refName)))) if err != nil { return "", err } @@ -400,7 +399,7 @@ func (c *CommitListBuilder) getFirstPushedCommit(refName string) (string, error) } // getLog gets the git log. -func (c *CommitListBuilder) getLogCmd(opts GetCommitsOptions) *exec.Cmd { +func (c *CommitListBuilder) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj { limitFlag := "" if opts.Limit { limitFlag = "-300" @@ -419,7 +418,7 @@ func (c *CommitListBuilder) getLogCmd(opts GetCommitsOptions) *exec.Cmd { allFlag = " --all" } - return c.OSCommand.ExecutableFromString( + return c.OSCommand.NewCmdObj( fmt.Sprintf( "git log %s %s %s --oneline %s %s --abbrev=%d %s", c.OSCommand.Quote(opts.RefName), diff --git a/pkg/commands/loading_files.go b/pkg/commands/loading_files.go index 098948ed4..df7be0dfe 100644 --- a/pkg/commands/loading_files.go +++ b/pkg/commands/loading_files.go @@ -80,7 +80,7 @@ func (c *GitCommand) GitStatus(opts GitStatusOptions) ([]FileStatus, error) { noRenamesFlag = "--no-renames" } - statusLines, err := c.RunCommandWithOutput("git status %s --porcelain -z %s", opts.UntrackedFilesArg, noRenamesFlag) + statusLines, err := c.RunWithOutput(c.NewCmdObj(fmt.Sprintf("git status %s --porcelain -z %s", opts.UntrackedFilesArg, noRenamesFlag))) if err != nil { return []FileStatus{}, err } diff --git a/pkg/commands/loading_reflog_commits.go b/pkg/commands/loading_reflog_commits.go index 619e5c225..80c2e815d 100644 --- a/pkg/commands/loading_reflog_commits.go +++ b/pkg/commands/loading_reflog_commits.go @@ -6,7 +6,6 @@ import ( "strings" "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" ) // GetReflogCommits only returns the new reflog commits since the given lastReflogCommit @@ -19,9 +18,9 @@ func (c *GitCommand) GetReflogCommits(lastReflogCommit *models.Commit, filterPat filterPathArg = fmt.Sprintf(" --follow -- %s", c.OSCommand.Quote(filterPath)) } - cmd := c.OSCommand.ExecutableFromString(fmt.Sprintf(`git log -g --abbrev=20 --format="%%h %%ct %%gs" %s`, filterPathArg)) + cmdObj := c.OSCommand.NewCmdObj(fmt.Sprintf(`git log -g --abbrev=20 --format="%%h %%ct %%gs" %s`, filterPathArg)) onlyObtainedNewReflogCommits := false - err := oscommands.RunLineOutputCmd(cmd, func(line string) (bool, error) { + err := c.OSCommand.RunLineOutputCmd(cmdObj, func(line string) (bool, error) { fields := strings.SplitN(line, " ", 3) if len(fields) <= 2 { return false, nil diff --git a/pkg/commands/loading_remotes.go b/pkg/commands/loading_remotes.go index 096deb71c..af460034a 100644 --- a/pkg/commands/loading_remotes.go +++ b/pkg/commands/loading_remotes.go @@ -10,9 +10,7 @@ import ( ) func (c *GitCommand) GetRemotes() ([]*models.Remote, error) { - // get remote branches - unescaped := "git branch -r" - remoteBranchesStr, err := c.OSCommand.RunCommandWithOutput(unescaped) + remoteBranchesStr, err := c.RunWithOutput(c.NewCmdObj("git branch -r")) if err != nil { return nil, err } diff --git a/pkg/commands/loading_stash.go b/pkg/commands/loading_stash.go index e14b463dd..40867a26b 100644 --- a/pkg/commands/loading_stash.go +++ b/pkg/commands/loading_stash.go @@ -10,8 +10,7 @@ import ( ) func (c *GitCommand) getUnfilteredStashEntries() []*models.StashEntry { - unescaped := "git stash list --pretty='%gs'" - rawString, _ := c.OSCommand.RunCommandWithOutput(unescaped) + rawString, _ := c.RunWithOutput(c.NewCmdObj("git stash list --pretty='%gs'")) stashEntries := []*models.StashEntry{} for i, line := range utils.SplitLines(rawString) { stashEntries = append(stashEntries, stashEntryFromLine(line, i)) @@ -25,7 +24,7 @@ func (c *GitCommand) GetStashEntries(filterPath string) []*models.StashEntry { return c.getUnfilteredStashEntries() } - rawString, err := c.RunCommandWithOutput("git stash list --name-only") + rawString, err := c.RunWithOutput(c.NewCmdObj("git stash list --name-only")) if err != nil { return c.getUnfilteredStashEntries() } diff --git a/pkg/commands/loading_tags.go b/pkg/commands/loading_tags.go index 71704f9c0..f97985ce9 100644 --- a/pkg/commands/loading_tags.go +++ b/pkg/commands/loading_tags.go @@ -10,7 +10,7 @@ import ( func (c *GitCommand) GetTags() ([]*models.Tag, error) { // get remote branches, sorted by creation date (descending) // see: https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---sortltkeygt - remoteBranchesStr, err := c.OSCommand.RunCommandWithOutput(`git tag --list --sort=-creatordate`) + remoteBranchesStr, err := c.OSCommand.RunWithOutput(c.NewCmdObj(`git tag --list --sort=-creatordate`)) if err != nil { return nil, err } diff --git a/pkg/commands/oscommands/cmd_obj.go b/pkg/commands/oscommands/cmd_obj.go index 592a4a61d..6381cf257 100644 --- a/pkg/commands/oscommands/cmd_obj.go +++ b/pkg/commands/oscommands/cmd_obj.go @@ -11,6 +11,7 @@ type ICmdObj interface { GetCmd() *exec.Cmd ToString() string AddEnvVars(...string) ICmdObj + GetEnvVars() []string } type CmdObj struct { @@ -31,3 +32,7 @@ func (self *CmdObj) AddEnvVars(vars ...string) ICmdObj { return self } + +func (self *CmdObj) GetEnvVars() []string { + return self.cmd.Env +} diff --git a/pkg/commands/oscommands/os.go b/pkg/commands/oscommands/os.go index cb41edff4..15e551133 100644 --- a/pkg/commands/oscommands/os.go +++ b/pkg/commands/oscommands/os.go @@ -9,6 +9,7 @@ import ( "path/filepath" "strings" "sync" + "testing" "github.com/go-errors/errors" @@ -29,14 +30,25 @@ type Platform struct { OpenLinkCommand string } +type ICommander interface { + Run(ICmdObj) error + RunWithOutput(ICmdObj) (string, error) +} + +type RealCommander struct { +} + +func (self *RealCommander) Run(cmdObj ICmdObj) error { + return cmdObj.GetCmd().Run() +} + // OSCommand holds all the os commands type OSCommand struct { - Log *logrus.Entry - Platform *Platform - Config config.AppConfigurer - Command func(string, ...string) *exec.Cmd - BeforeExecuteCmd func(*exec.Cmd) - Getenv func(string) string + Log *logrus.Entry + Platform *Platform + Config config.AppConfigurer + Command func(string, ...string) *exec.Cmd + Getenv func(string) string // callback to run before running a command, i.e. for the purposes of logging onRunCommand func(CmdLogEntry) @@ -45,6 +57,8 @@ type OSCommand struct { CmdLogSpan string removeFile func(string) error + + IRunner } // TODO: make these fields private @@ -79,15 +93,17 @@ func NewCmdLogEntry(cmdStr string, span string, commandLine bool) CmdLogEntry { // NewOSCommand os command runner func NewOSCommand(log *logrus.Entry, config config.AppConfigurer) *OSCommand { - return &OSCommand{ - Log: log, - Platform: getPlatform(), - Config: config, - Command: secureexec.Command, - BeforeExecuteCmd: func(*exec.Cmd) {}, - Getenv: os.Getenv, - removeFile: os.RemoveAll, + c := &OSCommand{ + Log: log, + Platform: getPlatform(), + Config: config, + Command: secureexec.Command, + Getenv: os.Getenv, + removeFile: os.RemoveAll, } + + c.IRunner = &RealRunner{c: c} + return c } func (c *OSCommand) WithSpan(span string) *OSCommand { @@ -104,8 +120,8 @@ func (c *OSCommand) WithSpan(span string) *OSCommand { return newOSCommand } -func (c *OSCommand) LogExecCmd(cmd *exec.Cmd) { - c.LogCommand(strings.Join(cmd.Args, " "), true) +func (c *OSCommand) LogCmdObj(cmdObj ICmdObj) { + c.LogCommand(cmdObj.ToString(), true) } func (c *OSCommand) LogCommand(cmdStr string, commandLine bool) { @@ -131,108 +147,6 @@ func (c *OSCommand) SetRemoveFile(f func(string) error) { c.removeFile = f } -func (c *OSCommand) SetBeforeExecuteCmd(cmd func(*exec.Cmd)) { - c.BeforeExecuteCmd = cmd -} - -type RunCommandOptions struct { - EnvVars []string -} - -func (c *OSCommand) RunCommandWithOutputWithOptions(command string, options RunCommandOptions) (string, error) { - c.LogCommand(command, true) - cmd := c.ExecutableFromString(command) - - cmd.Env = append(cmd.Env, "GIT_TERMINAL_PROMPT=0") // prevents git from prompting us for input which would freeze the program - cmd.Env = append(cmd.Env, options.EnvVars...) - - return sanitisedCommandOutput(cmd.CombinedOutput()) -} - -func (c *OSCommand) RunCommandWithOptions(command string, options RunCommandOptions) error { - _, err := c.RunCommandWithOutputWithOptions(command, options) - return err -} - -// RunCommandWithOutput wrapper around commands returning their output and error -// NOTE: If you don't pass any formatArgs we'll just use the command directly, -// however there's a bizarre compiler error/warning when you pass in a formatString -// with a percent sign because it thinks it's supposed to be a formatString when -// in that case it's not. To get around that error you'll need to define the string -// in a variable and pass the variable into RunCommandWithOutput. -func (c *OSCommand) RunCommandWithOutput(formatString string, formatArgs ...interface{}) (string, error) { - command := formatString - if formatArgs != nil { - command = fmt.Sprintf(formatString, formatArgs...) - } - cmd := c.ExecutableFromString(command) - c.LogExecCmd(cmd) - output, err := sanitisedCommandOutput(cmd.CombinedOutput()) - if err != nil { - c.Log.WithField("command", command).Error(output) - } - return output, err -} - -// RunExecutableWithOutput runs an executable file and returns its output -func (c *OSCommand) RunExecutableWithOutput(cmd *exec.Cmd) (string, error) { - c.LogExecCmd(cmd) - c.BeforeExecuteCmd(cmd) - return sanitisedCommandOutput(cmd.CombinedOutput()) -} - -// RunExecutable runs an executable file and returns an error if there was one -func (c *OSCommand) RunExecutable(cmd *exec.Cmd) error { - _, err := c.RunExecutableWithOutput(cmd) - return err -} - -// ExecutableFromString takes a string like `git status` and returns an executable command for it -func (c *OSCommand) ExecutableFromString(commandStr string) *exec.Cmd { - splitCmd := str.ToArgv(commandStr) - cmd := c.Command(splitCmd[0], splitCmd[1:]...) - cmd.Env = append(os.Environ(), "GIT_OPTIONAL_LOCKS=0") - return cmd -} - -// ShellCommandFromString takes a string like `git commit` and returns an executable shell command for it -func (c *OSCommand) ShellCommandFromString(commandStr string) *exec.Cmd { - quotedCommand := "" - // Windows does not seem to like quotes around the command - if c.Platform.OS == "windows" { - quotedCommand = strings.NewReplacer( - "^", "^^", - "&", "^&", - "|", "^|", - "<", "^<", - ">", "^>", - "%", "^%", - ).Replace(commandStr) - } else { - quotedCommand = c.Quote(commandStr) - } - - shellCommand := fmt.Sprintf("%s %s %s", c.Platform.Shell, c.Platform.ShellArg, quotedCommand) - return c.ExecutableFromString(shellCommand) -} - -// RunCommand runs a command and just returns the error -func (c *OSCommand) RunCommand(formatString string, formatArgs ...interface{}) error { - _, err := c.RunCommandWithOutput(formatString, formatArgs...) - return err -} - -// RunShellCommand runs shell commands i.e. 'sh -c '. Good for when you -// need access to the shell -func (c *OSCommand) RunShellCommand(command string) error { - cmd := c.ShellCommandFromString(command) - c.LogExecCmd(cmd) - - _, err := sanitisedCommandOutput(cmd.CombinedOutput()) - - return err -} - // FileType tells us if the file is a file, directory or other func (c *OSCommand) FileType(path string) string { fileInfo, err := os.Stat(path) @@ -245,19 +159,6 @@ func (c *OSCommand) FileType(path string) string { return "file" } -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 - if outputString == "" { - return "", utils.WrapError(err) - } - return outputString, errors.New(outputString) - } - return outputString, nil -} - // OpenFile opens a file with the given func (c *OSCommand) OpenFile(filename string) error { commandTemplate := c.Config.GetUserConfig().OS.OpenCommand @@ -265,7 +166,7 @@ func (c *OSCommand) OpenFile(filename string) error { "filename": c.Quote(filename), } command := utils.ResolvePlaceholderString(commandTemplate, templateValues) - err := c.RunShellCommand(command) + err := c.Run(c.NewShellCmdObjFromString(command)) return err } @@ -278,26 +179,10 @@ func (c *OSCommand) OpenLink(link string) error { } command := utils.ResolvePlaceholderString(commandTemplate, templateValues) - err := c.RunShellCommand(command) + err := c.Run(c.NewShellCmdObjFromString(command)) return err } -// PrepareSubProcess iniPrepareSubProcessrocess then tells the Gui to switch to it -// TODO: see if this needs to exist, given that ExecutableFromString does the same things -func (c *OSCommand) PrepareSubProcess(cmdName string, commandArgs ...string) *exec.Cmd { - cmd := c.Command(cmdName, commandArgs...) - if cmd != nil { - cmd.Env = append(os.Environ(), "GIT_OPTIONAL_LOCKS=0") - } - c.LogExecCmd(cmd) - return cmd -} - -// PrepareShellSubProcess returns the pointer to a custom command -func (c *OSCommand) PrepareShellSubProcess(command string) *exec.Cmd { - return c.PrepareSubProcess(c.Platform.Shell, c.Platform.ShellArg, command) -} - // Quote wraps a message in platform-specific quotation marks func (c *OSCommand) Quote(message string) string { var quote string @@ -390,24 +275,6 @@ func (c *OSCommand) FileExists(path string) (bool, error) { return true, nil } -// RunPreparedCommand takes a pointer to an exec.Cmd and runs it -// this is useful if you need to give your command some environment variables -// before running it -func (c *OSCommand) RunPreparedCommand(cmd *exec.Cmd) error { - c.BeforeExecuteCmd(cmd) - c.LogExecCmd(cmd) - out, err := cmd.CombinedOutput() - outString := string(out) - c.Log.Info(outString) - if err != nil { - if len(outString) == 0 { - return err - } - return errors.New(outString) - } - return nil -} - // GetLazygitPath returns the path of the currently executed file func (c *OSCommand) GetLazygitPath() string { ex, err := os.Executable() // get the executable path for git to use @@ -426,7 +293,7 @@ func (c *OSCommand) PipeCommands(commandStrings ...string) error { logCmdStr += " | " } logCmdStr += str - cmds[i] = c.ExecutableFromString(str) + cmds[i] = c.NewCmdObj(str).GetCmd() } c.LogCommand(logCmdStr, true) @@ -489,7 +356,107 @@ func Kill(cmd *exec.Cmd) error { return cmd.Process.Kill() } -func RunLineOutputCmd(cmd *exec.Cmd, onLine func(line string) (bool, error)) error { +func (c *OSCommand) CopyToClipboard(str string) error { + escaped := strings.Replace(str, "\n", "\\n", -1) + truncated := utils.TruncateWithEllipsis(escaped, 40) + c.LogCommand(fmt.Sprintf("Copying '%s' to clipboard", truncated), false) + return clipboard.WriteAll(str) +} + +func (c *OSCommand) RemoveFile(path string) error { + c.LogCommand(fmt.Sprintf("Deleting path '%s'", path), false) + + return c.removeFile(path) +} + +// builders + +func (c *OSCommand) NewCmdObj(cmdStr string) ICmdObj { + args := str.ToArgv(cmdStr) + cmd := c.Command(args[0], args[1:]...) + cmd.Env = os.Environ() + + return &CmdObj{ + cmdStr: cmdStr, + cmd: cmd, + } +} + +func (c *OSCommand) NewCmdObjFromArgs(args []string) ICmdObj { + cmd := c.Command(args[0], args[1:]...) + cmd.Env = os.Environ() + + return &CmdObj{ + cmdStr: strings.Join(args, " "), + cmd: cmd, + } +} + +// NewShellCmdObjFromString takes a string like `git commit` and returns an executable shell command for it +func (c *OSCommand) NewShellCmdObjFromString(commandStr string) ICmdObj { + quotedCommand := "" + // Windows does not seem to like quotes around the command + if c.Platform.OS == "windows" { + quotedCommand = strings.NewReplacer( + "^", "^^", + "&", "^&", + "|", "^|", + "<", "^<", + ">", "^>", + "%", "^%", + ).Replace(commandStr) + } else { + quotedCommand = c.Quote(commandStr) + } + + shellCommand := fmt.Sprintf("%s %s %s", c.Platform.Shell, c.Platform.ShellArg, quotedCommand) + return c.NewCmdObj(shellCommand) +} + +// TODO: pick one of NewShellCmdObjFromString2 and ShellCommandFromString to use. I'm not sure +// which one actually is better, but I suspect it's NewShellCmdObjFromString2 +func (c *OSCommand) NewShellCmdObjFromString2(command string) ICmdObj { + return c.NewCmdObjFromArgs([]string{c.Platform.Shell, c.Platform.ShellArg, command}) +} + +// runners + +type IRunner interface { + Run(cmdObj ICmdObj) error + RunWithOutput(cmdObj ICmdObj) (string, error) + RunLineOutputCmd(cmdObj ICmdObj, onLine func(line string) (bool, error)) error +} + +type RunExpectation func(ICmdObj) (string, error) + +type FakeRunner struct { + expectations []RunExpectation +} + +func (self *RealRunner) Run(cmdObj ICmdObj) error { + +} + +type RealRunner struct { + c *OSCommand +} + +func (self *RealRunner) Run(cmdObj ICmdObj) error { + _, err := self.RunWithOutput(cmdObj) + return err +} + +func (self *RealRunner) RunWithOutput(cmdObj ICmdObj) (string, error) { + self.c.LogCmdObj(cmdObj) + output, err := sanitisedCommandOutput(cmdObj.GetCmd().CombinedOutput()) + if err != nil { + self.c.Log.WithField("command", cmdObj.ToString()).Error(output) + } + return output, err +} + +func (self *RealRunner) RunLineOutputCmd(cmdObj ICmdObj, onLine func(line string) (bool, error)) error { + cmd := cmdObj.GetCmd() stdoutPipe, err := cmd.StdoutPipe() if err != nil { return err @@ -518,42 +485,15 @@ func RunLineOutputCmd(cmd *exec.Cmd, onLine func(line string) (bool, error)) err return nil } -func (c *OSCommand) CopyToClipboard(str string) error { - escaped := strings.Replace(str, "\n", "\\n", -1) - truncated := utils.TruncateWithEllipsis(escaped, 40) - c.LogCommand(fmt.Sprintf("Copying '%s' to clipboard", truncated), false) - return clipboard.WriteAll(str) -} - -func (c *OSCommand) RemoveFile(path string) error { - c.LogCommand(fmt.Sprintf("Deleting path '%s'", path), false) - - return c.removeFile(path) -} - -func (c *OSCommand) NewCmdObjFromStr(cmdStr string) ICmdObj { - args := str.ToArgv(cmdStr) - cmd := c.Command(args[0], args[1:]...) - cmd.Env = os.Environ() - - return &CmdObj{ - cmdStr: cmdStr, - cmd: cmd, - } -} - -func (c *OSCommand) NewCmdObjFromArgs(args []string) ICmdObj { - cmd := c.Command(args[0], args[1:]...) - - return &CmdObj{ - cmdStr: strings.Join(args, " "), - cmd: cmd, - } -} - -func (c *OSCommand) NewCmdObj(cmd *exec.Cmd) ICmdObj { - return &CmdObj{ - cmdStr: strings.Join(cmd.Args, " "), - cmd: cmd, +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 + if outputString == "" { + return "", utils.WrapError(err) + } + return outputString, errors.New(outputString) } + return outputString, nil } diff --git a/pkg/commands/oscommands/os_test.go b/pkg/commands/oscommands/os_test.go index 4dcca3790..2c2a58802 100644 --- a/pkg/commands/oscommands/os_test.go +++ b/pkg/commands/oscommands/os_test.go @@ -8,8 +8,8 @@ import ( "github.com/stretchr/testify/assert" ) -// TestOSCommandRunCommandWithOutput is a function. -func TestOSCommandRunCommandWithOutput(t *testing.T) { +// TestOSCommandRunWithOutput is a function. +func TestOSCommandRunWithOutput(t *testing.T) { type scenario struct { command string test func(string, error) @@ -32,12 +32,13 @@ func TestOSCommandRunCommandWithOutput(t *testing.T) { } for _, s := range scenarios { - s.test(NewDummyOSCommand().RunCommandWithOutput(s.command)) + c := NewDummyOSCommand() + s.test(NewDummyOSCommand().RunWithOutput(c.NewCmdObj(s.command))) } } -// TestOSCommandRunCommand is a function. -func TestOSCommandRunCommand(t *testing.T) { +// TestOSCommandRun is a function. +func TestOSCommandRun(t *testing.T) { type scenario struct { command string test func(error) @@ -53,7 +54,8 @@ func TestOSCommandRunCommand(t *testing.T) { } for _, s := range scenarios { - s.test(NewDummyOSCommand().RunCommand(s.command)) + c := NewDummyOSCommand() + s.test(c.Run(c.NewCmdObj(s.command))) } } diff --git a/pkg/commands/patch_rebases.go b/pkg/commands/patch_rebases.go index a6901dc3f..d9947b18e 100644 --- a/pkg/commands/patch_rebases.go +++ b/pkg/commands/patch_rebases.go @@ -90,7 +90,7 @@ func (c *GitCommand) MovePatchToSelectedCommit(commits []*models.Commit, sourceC return err } - if err := c.OSCommand.RunPreparedCommand(cmd); err != nil { + if err := c.OSCommand.Run(cmd); err != nil { return err } @@ -217,7 +217,7 @@ func (c *GitCommand) PullPatchIntoNewCommit(commits []*models.Commit, commitIdx head_message, _ := c.GetHeadCommitMessage() new_message := fmt.Sprintf("Split from \"%s\"", head_message) - err := c.OSCommand.RunCommand(c.CommitCmdStr(new_message, "")) + err := c.OSCommand.Run(c.CommitCmdObj(new_message, "")) if err != nil { return err } diff --git a/pkg/commands/rebasing.go b/pkg/commands/rebasing.go index 63c568036..8e00f4283 100644 --- a/pkg/commands/rebasing.go +++ b/pkg/commands/rebasing.go @@ -3,17 +3,15 @@ package commands import ( "fmt" "io/ioutil" - "os" - "os/exec" "path/filepath" "strings" "github.com/go-errors/errors" "github.com/jesseduffield/lazygit/pkg/commands/models" - "github.com/mgutz/str" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" ) -func (c *GitCommand) RewordCommit(commits []*models.Commit, index int) (*exec.Cmd, error) { +func (c *GitCommand) RewordCommit(commits []*models.Commit, index int) (oscommands.ICmdObj, error) { todo, sha, err := c.GenerateGenericRebaseTodo(commits, index, "reword") if err != nil { return nil, err @@ -40,7 +38,7 @@ func (c *GitCommand) MoveCommitDown(commits []*models.Commit, index int) error { return err } - return c.OSCommand.RunPreparedCommand(cmd) + return c.OSCommand.Run(cmd) } func (c *GitCommand) InteractiveRebase(commits []*models.Commit, index int, action string) error { @@ -54,13 +52,13 @@ func (c *GitCommand) InteractiveRebase(commits []*models.Commit, index int, acti return err } - return c.OSCommand.RunPreparedCommand(cmd) + return c.OSCommand.Run(cmd) } // PrepareInteractiveRebaseCommand returns the cmd for an interactive rebase // we tell git to run lazygit to edit the todo list, and we pass the client // lazygit a todo string to write to the todo file -func (c *GitCommand) PrepareInteractiveRebaseCommand(baseSha string, todo string, overrideEditor bool) (*exec.Cmd, error) { +func (c *GitCommand) PrepareInteractiveRebaseCommand(baseSha string, todo string, overrideEditor bool) (oscommands.ICmdObj, error) { ex := c.OSCommand.GetLazygitPath() debug := "FALSE" @@ -70,9 +68,8 @@ func (c *GitCommand) PrepareInteractiveRebaseCommand(baseSha string, todo string cmdStr := fmt.Sprintf("git rebase --interactive --autostash --keep-empty %s", baseSha) c.Log.WithField("command", cmdStr).Info("RunCommand") - splitCmd := str.ToArgv(cmdStr) - cmd := c.OSCommand.Command(splitCmd[0], splitCmd[1:]...) + cmdObj := c.NewCmdObj(cmdStr) gitSequenceEditor := ex if todo == "" { @@ -81,9 +78,7 @@ func (c *GitCommand) PrepareInteractiveRebaseCommand(baseSha string, todo string c.OSCommand.LogCommand(fmt.Sprintf("Creating TODO file for interactive rebase: \n\n%s", todo), false) } - cmd.Env = os.Environ() - cmd.Env = append( - cmd.Env, + cmdObj.AddEnvVars( "LAZYGIT_CLIENT_COMMAND=INTERACTIVE_REBASE", "LAZYGIT_REBASE_TODO="+todo, "DEBUG="+debug, @@ -93,10 +88,10 @@ func (c *GitCommand) PrepareInteractiveRebaseCommand(baseSha string, todo string ) if overrideEditor { - cmd.Env = append(cmd.Env, "GIT_EDITOR="+ex) + cmdObj.AddEnvVars("GIT_EDITOR=" + ex) } - return cmd, nil + return cmdObj, nil } func (c *GitCommand) GenerateGenericRebaseTodo(commits []*models.Commit, actionIndex int, action string) (string, string, error) { @@ -227,7 +222,7 @@ func (c *GitCommand) BeginInteractiveRebaseForCommit(commits []*models.Commit, c return err } - if err := c.OSCommand.RunPreparedCommand(cmd); err != nil { + if err := c.OSCommand.Run(cmd); err != nil { return err } @@ -241,7 +236,7 @@ func (c *GitCommand) RebaseBranch(branchName string) error { return err } - return c.OSCommand.RunPreparedCommand(cmd) + return c.OSCommand.Run(cmd) } // GenericMerge takes a commandType of "merge" or "rebase" and a command of "abort", "skip" or "continue" @@ -276,14 +271,13 @@ func (c *GitCommand) GenericMergeOrRebaseAction(commandType string, command stri } func (c *GitCommand) runSkipEditorCommand(command string) error { - cmd := c.OSCommand.ExecutableFromString(command) + cmdObj := c.OSCommand.NewCmdObj(command) lazyGitPath := c.OSCommand.GetLazygitPath() - cmd.Env = append( - cmd.Env, + cmdObj.AddEnvVars( "LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY", "GIT_EDITOR="+lazyGitPath, "EDITOR="+lazyGitPath, "VISUAL="+lazyGitPath, ) - return c.OSCommand.RunExecutable(cmd) + return c.OSCommand.Run(cmdObj) } diff --git a/pkg/commands/rebasing_test.go b/pkg/commands/rebasing_test.go index 7c4a954c9..2527cacd9 100644 --- a/pkg/commands/rebasing_test.go +++ b/pkg/commands/rebasing_test.go @@ -5,6 +5,7 @@ import ( "regexp" "testing" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/test" "github.com/stretchr/testify/assert" ) @@ -62,31 +63,31 @@ func TestGitCommandRebaseBranch(t *testing.T) { func TestGitCommandSkipEditorCommand(t *testing.T) { cmd := NewDummyGitCommand() - cmd.OSCommand.SetBeforeExecuteCmd(func(cmd *exec.Cmd) { + cmd.OSCommand.SetBeforeExecuteCmd(func(cmdObj oscommands.ICmdObj) { test.AssertContainsMatch( t, - cmd.Env, + cmdObj.GetEnvVars(), regexp.MustCompile("^VISUAL="), "expected VISUAL to be set for a non-interactive external command", ) test.AssertContainsMatch( t, - cmd.Env, + cmdObj.GetEnvVars(), regexp.MustCompile("^EDITOR="), "expected EDITOR to be set for a non-interactive external command", ) test.AssertContainsMatch( t, - cmd.Env, + cmdObj.GetEnvVars(), regexp.MustCompile("^GIT_EDITOR="), "expected GIT_EDITOR to be set for a non-interactive external command", ) test.AssertContainsMatch( t, - cmd.Env, + cmdObj.GetEnvVars(), regexp.MustCompile("^LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY$"), "expected LAZYGIT_CLIENT_COMMAND to be set for a non-interactive external command", ) diff --git a/pkg/commands/remotes.go b/pkg/commands/remotes.go index 11e5dc26f..553f222ad 100644 --- a/pkg/commands/remotes.go +++ b/pkg/commands/remotes.go @@ -7,24 +7,24 @@ import ( ) func (c *GitCommand) AddRemote(name string, url string) error { - return c.RunCommand("git remote add %s %s", c.OSCommand.Quote(name), c.OSCommand.Quote(url)) + return c.Run(c.NewCmdObj(fmt.Sprintf("git remote add %s %s", c.OSCommand.Quote(name), c.OSCommand.Quote(url)))) } func (c *GitCommand) RemoveRemote(name string) error { - return c.RunCommand("git remote remove %s", c.OSCommand.Quote(name)) + return c.Run(c.NewCmdObj(fmt.Sprintf("git remote remove %s", c.OSCommand.Quote(name)))) } func (c *GitCommand) RenameRemote(oldRemoteName string, newRemoteName string) error { - return c.RunCommand("git remote rename %s %s", c.OSCommand.Quote(oldRemoteName), c.OSCommand.Quote(newRemoteName)) + return c.Run(c.NewCmdObj(fmt.Sprintf("git remote rename %s %s", c.OSCommand.Quote(oldRemoteName), c.OSCommand.Quote(newRemoteName)))) } func (c *GitCommand) UpdateRemoteUrl(remoteName string, updatedUrl string) error { - return c.RunCommand("git remote set-url %s %s", c.OSCommand.Quote(remoteName), c.OSCommand.Quote(updatedUrl)) + return c.Run(c.NewCmdObj(fmt.Sprintf("git remote set-url %s %s", c.OSCommand.Quote(remoteName), c.OSCommand.Quote(updatedUrl)))) } func (c *GitCommand) DeleteRemoteBranch(remoteName string, branchName string, promptUserForCredential func(string) string) error { command := fmt.Sprintf("git push %s --delete %s", c.OSCommand.Quote(remoteName), c.OSCommand.Quote(branchName)) - cmdObj := c.NewCmdObjFromStr(command) + cmdObj := c.NewCmdObj(command) return c.DetectUnamePass(cmdObj, promptUserForCredential) } @@ -34,10 +34,10 @@ func (c *GitCommand) DetectUnamePass(cmdObj oscommands.ICmdObj, promptUserForCre // CheckRemoteBranchExists Returns remote branch func (c *GitCommand) CheckRemoteBranchExists(branchName string) bool { - _, err := c.OSCommand.RunCommandWithOutput( - "git show-ref --verify -- refs/remotes/origin/%s", - c.OSCommand.Quote(branchName), - ) + _, err := c.RunWithOutput(c.NewCmdObj( + fmt.Sprintf("git show-ref --verify -- refs/remotes/origin/%s", + c.OSCommand.Quote(branchName), + ))) return err == nil } diff --git a/pkg/commands/stash_entries.go b/pkg/commands/stash_entries.go index 9c56c78dc..e557ba529 100644 --- a/pkg/commands/stash_entries.go +++ b/pkg/commands/stash_entries.go @@ -4,13 +4,13 @@ import "fmt" // StashDo modify stash func (c *GitCommand) StashDo(index int, method string) error { - return c.RunCommand("git stash %s stash@{%d}", method, index) + return c.Run(c.NewCmdObj(fmt.Sprintf("git stash %s stash@{%d}", method, index))) } // StashSave save stash // TODO: before calling this, check if there is anything to save func (c *GitCommand) StashSave(message string) error { - return c.RunCommand("git stash save %s", c.OSCommand.Quote(message)) + return c.Run(c.NewCmdObj("git stash save " + c.OSCommand.Quote(message))) } // GetStashEntryDiff stash diff @@ -22,7 +22,7 @@ func (c *GitCommand) ShowStashEntryCmdStr(index int) string { // shoutouts to Joe on https://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible func (c *GitCommand) StashSaveStagedChanges(message string) error { // wrap in 'writing', which uses a mutex - if err := c.RunCommand("git stash --keep-index"); err != nil { + if err := c.Run(c.NewCmdObj("git stash --keep-index")); err != nil { return err } @@ -30,7 +30,7 @@ func (c *GitCommand) StashSaveStagedChanges(message string) error { return err } - if err := c.RunCommand("git stash apply stash@{1}"); err != nil { + if err := c.Run(c.NewCmdObj("git stash apply stash@{1}")); err != nil { return err } @@ -38,7 +38,7 @@ func (c *GitCommand) StashSaveStagedChanges(message string) error { return err } - if err := c.RunCommand("git stash drop stash@{1}"); err != nil { + if err := c.Run(c.NewCmdObj("git stash drop stash@{1}")); err != nil { return err } diff --git a/pkg/commands/submodules.go b/pkg/commands/submodules.go index 45e830d10..d4343ae19 100644 --- a/pkg/commands/submodules.go +++ b/pkg/commands/submodules.go @@ -2,12 +2,14 @@ package commands import ( "bufio" + "fmt" "os" "path/filepath" "regexp" "strings" "github.com/jesseduffield/lazygit/pkg/commands/models" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" ) // .gitmodules looks like this: @@ -69,28 +71,28 @@ func (c *GitCommand) SubmoduleStash(submodule *models.SubmoduleConfig) error { return nil } - return c.RunCommand("git -C %s stash --include-untracked", c.OSCommand.Quote(submodule.Path)) + return c.Run(c.NewCmdObj("git -C " + c.OSCommand.Quote(submodule.Path) + " stash --include-untracked")) } func (c *GitCommand) SubmoduleReset(submodule *models.SubmoduleConfig) error { - return c.RunCommand("git submodule update --init --force -- %s", c.OSCommand.Quote(submodule.Path)) + return c.Run(c.NewCmdObj("git submodule update --init --force -- " + c.OSCommand.Quote(submodule.Path))) } func (c *GitCommand) SubmoduleUpdateAll() error { // not doing an --init here because the user probably doesn't want that - return c.RunCommand("git submodule update --force") + return c.Run(c.NewCmdObj("git submodule update --force")) } func (c *GitCommand) SubmoduleDelete(submodule *models.SubmoduleConfig) error { // based on https://gist.github.com/myusuf3/7f645819ded92bda6677 - if err := c.RunCommand("git submodule deinit --force -- %s", c.OSCommand.Quote(submodule.Path)); err != nil { + if err := c.Run(c.NewCmdObj("git submodule deinit --force -- " + c.OSCommand.Quote(submodule.Path))); err != nil { if strings.Contains(err.Error(), "did not match any file(s) known to git") { - if err := c.RunCommand("git config --file .gitmodules --remove-section submodule.%s", c.OSCommand.Quote(submodule.Name)); err != nil { + if err := c.Run(c.NewCmdObj("git config --file .gitmodules --remove-section submodule." + c.OSCommand.Quote(submodule.Name))); err != nil { return err } - if err := c.RunCommand("git config --remove-section submodule.%s", c.OSCommand.Quote(submodule.Name)); err != nil { + if err := c.Run(c.NewCmdObj("git config --remove-section submodule." + c.OSCommand.Quote(submodule.Name))); err != nil { return err } @@ -100,7 +102,7 @@ func (c *GitCommand) SubmoduleDelete(submodule *models.SubmoduleConfig) error { } } - if err := c.RunCommand("git rm --force -r %s", submodule.Path); err != nil { + if err := c.Run(c.NewCmdObj("git rm --force -r " + submodule.Path)); err != nil { // if the directory isn't there then that's fine c.Log.Error(err) } @@ -109,21 +111,23 @@ func (c *GitCommand) SubmoduleDelete(submodule *models.SubmoduleConfig) error { } func (c *GitCommand) SubmoduleAdd(name string, path string, url string) error { - return c.OSCommand.RunCommand( - "git submodule add --force --name %s -- %s %s ", - c.OSCommand.Quote(name), - c.OSCommand.Quote(url), - c.OSCommand.Quote(path), - ) + return c.OSCommand.Run( + c.OSCommand.NewCmdObj( + fmt.Sprintf( + "git submodule add --force --name %s -- %s %s ", + c.OSCommand.Quote(name), + c.OSCommand.Quote(url), + c.OSCommand.Quote(path), + ))) } func (c *GitCommand) SubmoduleUpdateUrl(name string, path string, newUrl string) error { // the set-url command is only for later git versions so we're doing it manually here - if err := c.RunCommand("git config --file .gitmodules submodule.%s.url %s", c.OSCommand.Quote(name), c.OSCommand.Quote(newUrl)); err != nil { + if err := c.Run(c.NewCmdObj("git config --file .gitmodules submodule." + c.OSCommand.Quote(name) + ".url " + c.OSCommand.Quote(newUrl))); err != nil { return err } - if err := c.RunCommand("git submodule sync -- %s", c.OSCommand.Quote(path)); err != nil { + if err := c.Run(c.NewCmdObj("git submodule sync -- " + c.OSCommand.Quote(path))); err != nil { return err } @@ -131,27 +135,27 @@ func (c *GitCommand) SubmoduleUpdateUrl(name string, path string, newUrl string) } func (c *GitCommand) SubmoduleInit(path string) error { - return c.RunCommand("git submodule init -- %s", c.OSCommand.Quote(path)) + return c.Run(c.NewCmdObj("git submodule init -- " + c.OSCommand.Quote(path))) } func (c *GitCommand) SubmoduleUpdate(path string) error { - return c.RunCommand("git submodule update --init -- %s", c.OSCommand.Quote(path)) + return c.Run(c.NewCmdObj("git submodule update --init -- " + c.OSCommand.Quote(path))) } -func (c *GitCommand) SubmoduleBulkInitCmdStr() string { - return "git submodule init" +func (c *GitCommand) SubmoduleBulkInitCmdObj() oscommands.ICmdObj { + return c.NewCmdObj("git submodule init") } -func (c *GitCommand) SubmoduleBulkUpdateCmdStr() string { - return "git submodule update" +func (c *GitCommand) SubmoduleBulkUpdateCmdObj() oscommands.ICmdObj { + return c.NewCmdObj("git submodule update") } -func (c *GitCommand) SubmoduleForceBulkUpdateCmdStr() string { - return "git submodule update --force" +func (c *GitCommand) SubmoduleForceBulkUpdateCmdObj() oscommands.ICmdObj { + return c.NewCmdObj("git submodule update --force") } -func (c *GitCommand) SubmoduleBulkDeinitCmdStr() string { - return "git submodule deinit --all --force" +func (c *GitCommand) SubmoduleBulkDeinitCmdObj() oscommands.ICmdObj { + return c.NewCmdObj("git submodule deinit --all --force") } func (c *GitCommand) ResetSubmodules(submodules []*models.SubmoduleConfig) error { diff --git a/pkg/commands/sync.go b/pkg/commands/sync.go index 35f258f4b..65a70bd0d 100644 --- a/pkg/commands/sync.go +++ b/pkg/commands/sync.go @@ -37,7 +37,7 @@ func (c *GitCommand) Push(opts PushOpts) error { cmdStr += " " + c.OSCommand.Quote(opts.UpstreamBranch) } - cmdObj := c.NewCmdObjFromStr(cmdStr) + cmdObj := c.NewCmdObj(cmdStr) return c.DetectUnamePass(cmdObj, opts.PromptUserForCredential) } @@ -58,7 +58,7 @@ func (c *GitCommand) Fetch(opts FetchOptions) error { cmdStr = fmt.Sprintf("%s %s", cmdStr, c.OSCommand.Quote(opts.BranchName)) } - cmdObj := c.NewCmdObjFromStr(cmdStr) + cmdObj := c.NewCmdObj(cmdStr) return c.DetectUnamePass(cmdObj, func(question string) string { if opts.PromptUserForCredential != nil { return opts.PromptUserForCredential(question) @@ -94,18 +94,18 @@ func (c *GitCommand) Pull(opts PullOptions) error { // setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user // has 'pull.rebase = interactive' configured. - cmdObj := c.NewCmdObjFromStr(cmdStr).AddEnvVars("GIT_SEQUENCE_EDITOR=:") + cmdObj := c.NewCmdObj(cmdStr).AddEnvVars("GIT_SEQUENCE_EDITOR=:") return c.DetectUnamePass(cmdObj, opts.PromptUserForCredential) } func (c *GitCommand) FastForward(branchName string, remoteName string, remoteBranchName string, promptUserForCredential func(string) string) error { cmdStr := fmt.Sprintf("git fetch %s %s:%s", c.OSCommand.Quote(remoteName), c.OSCommand.Quote(remoteBranchName), c.OSCommand.Quote(branchName)) - cmdObj := c.NewCmdObjFromStr(cmdStr) + cmdObj := c.NewCmdObj(cmdStr) return c.DetectUnamePass(cmdObj, promptUserForCredential) } func (c *GitCommand) FetchRemote(remoteName string, promptUserForCredential func(string) string) error { cmdStr := fmt.Sprintf("git fetch %s", c.OSCommand.Quote(remoteName)) - cmdObj := c.NewCmdObjFromStr(cmdStr) + cmdObj := c.NewCmdObj(cmdStr) return c.DetectUnamePass(cmdObj, promptUserForCredential) } diff --git a/pkg/commands/tags.go b/pkg/commands/tags.go index bb04d38e6..bd1f55c65 100644 --- a/pkg/commands/tags.go +++ b/pkg/commands/tags.go @@ -5,7 +5,7 @@ import ( ) func (c *GitCommand) CreateLightweightTag(tagName string, commitSha string) error { - return c.RunCommand("git tag -- %s %s", c.OSCommand.Quote(tagName), commitSha) + return c.Run(c.NewCmdObj(fmt.Sprintf("git tag -- %s %s", c.OSCommand.Quote(tagName), commitSha))) } func (c *GitCommand) CreateAnnotatedTag(tagName, commitSha, msg string) error { @@ -13,11 +13,11 @@ func (c *GitCommand) CreateAnnotatedTag(tagName, commitSha, msg string) error { } func (c *GitCommand) DeleteTag(tagName string) error { - return c.RunCommand("git tag -d %s", c.OSCommand.Quote(tagName)) + return c.Run(c.NewCmdObj(fmt.Sprintf("git tag -d %s", c.OSCommand.Quote(tagName)))) } func (c *GitCommand) PushTag(remoteName string, tagName string, promptUserForCredential func(string) string) error { cmdStr := fmt.Sprintf("git push %s %s", c.OSCommand.Quote(remoteName), c.OSCommand.Quote(tagName)) - cmdObj := c.NewCmdObjFromStr(cmdStr) + cmdObj := c.NewCmdObj(cmdStr) return c.DetectUnamePass(cmdObj, promptUserForCredential) } diff --git a/pkg/gui/branches_panel.go b/pkg/gui/branches_panel.go index dee321979..7b712b2ba 100644 --- a/pkg/gui/branches_panel.go +++ b/pkg/gui/branches_panel.go @@ -32,11 +32,9 @@ func (gui *Gui) branchesRenderToMain() error { if branch == nil { task = NewRenderStringTask(gui.Tr.NoBranchesThisRepo) } else { - cmd := gui.OSCommand.ExecutableFromString( - gui.GitCommand.GetBranchGraphCmdStr(branch.Name), - ) + cmdObj := gui.GitCommand.GetBranchGraphCmdObj(branch.Name) - task = NewRunPtyTask(cmd) + task = NewRunPtyTask(cmdObj.GetCmd()) } return gui.refreshMainViews(refreshMainOpts{ diff --git a/pkg/gui/commit_files_panel.go b/pkg/gui/commit_files_panel.go index e89108e46..2b7e513df 100644 --- a/pkg/gui/commit_files_panel.go +++ b/pkg/gui/commit_files_panel.go @@ -45,10 +45,8 @@ func (gui *Gui) commitFilesRenderToMain() error { to := gui.State.CommitFileManager.GetParent() from, reverse := gui.getFromAndReverseArgsForDiff(to) - cmd := gui.OSCommand.ExecutableFromString( - gui.GitCommand.ShowFileDiffCmdStr(from, to, reverse, node.GetPath(), false), - ) - task := NewRunPtyTask(cmd) + cmdObj := gui.GitCommand.ShowFileDiffCmdObj(from, to, reverse, node.GetPath(), false) + task := NewRunPtyTask(cmdObj.GetCmd()) return gui.refreshMainViews(refreshMainOpts{ main: &viewUpdateOpts{ diff --git a/pkg/gui/commit_message_panel.go b/pkg/gui/commit_message_panel.go index b3c1d635a..9e74f26f9 100644 --- a/pkg/gui/commit_message_panel.go +++ b/pkg/gui/commit_message_panel.go @@ -24,10 +24,11 @@ func (gui *Gui) handleCommitConfirm() error { flags = append(flags, "--signoff") } - cmdStr := gui.GitCommand.CommitCmdStr(message, strings.Join(flags, " ")) - gui.OnRunCommand(oscommands.NewCmdLogEntry(cmdStr, gui.Tr.Spans.Commit, true)) + cmdObj := gui.GitCommand.CommitCmdObj(message, strings.Join(flags, " ")) + gui.OnRunCommand(oscommands.NewCmdLogEntry(cmdObj.ToString(), gui.Tr.Spans.Commit, true)) + _ = gui.returnFromContext() - return gui.withGpgHandling(cmdStr, gui.Tr.CommittingStatus, func() error { + return gui.withGpgHandling(cmdObj, gui.Tr.CommittingStatus, func() error { gui.Views.CommitMessage.ClearTextArea() return nil }) diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index a606ce952..8f65ab56e 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -46,10 +46,8 @@ func (gui *Gui) branchCommitsRenderToMain() error { if commit == nil { task = NewRenderStringTask(gui.Tr.NoCommitsThisBranch) } else { - cmd := gui.OSCommand.ExecutableFromString( - gui.GitCommand.ShowCmdStr(commit.Sha, gui.State.Modes.Filtering.GetPath()), - ) - task = NewRunPtyTask(cmd) + cmdObj := gui.GitCommand.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath()) + task = NewRunPtyTask(cmdObj.GetCmd()) } return gui.refreshMainViews(refreshMainOpts{ diff --git a/pkg/gui/custom_commands.go b/pkg/gui/custom_commands.go index 8f98d6597..06526b6f0 100644 --- a/pkg/gui/custom_commands.go +++ b/pkg/gui/custom_commands.go @@ -203,7 +203,7 @@ func (gui *Gui) menuPromptFromCommand(prompt config.CustomCommandPrompt, promptR } // Run and save output - message, err := gui.GitCommand.RunCommandWithOutput(cmdStr) + message, err := gui.GitCommand.RunWithOutput(gui.GitCommand.NewCmdObj(cmdStr)) if err != nil { return gui.surfaceError(err) } @@ -244,7 +244,7 @@ func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand } if customCommand.Subprocess { - return gui.runSubprocessWithSuspenseAndRefresh(gui.OSCommand.PrepareShellSubProcess(cmdStr)) + return gui.runSubprocessWithSuspenseAndRefresh(gui.OSCommand.NewShellCmdObjFromString2(cmdStr)) } loadingText := customCommand.LoadingText @@ -252,7 +252,8 @@ func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand loadingText = gui.Tr.LcRunningCustomCommandStatus } return gui.WithWaitingStatus(loadingText, func() error { - if err := gui.OSCommand.WithSpan(gui.Tr.Spans.CustomCommand).RunShellCommand(cmdStr); err != nil { + cmdObj := gui.OSCommand.NewShellCmdObjFromString(cmdStr) + if err := gui.OSCommand.WithSpan(gui.Tr.Spans.CustomCommand).Run(cmdObj); err != nil { return gui.surfaceError(err) } return gui.refreshSidePanels(refreshOptions{}) diff --git a/pkg/gui/diffing.go b/pkg/gui/diffing.go index e1f3afa88..8b54e21af 100644 --- a/pkg/gui/diffing.go +++ b/pkg/gui/diffing.go @@ -13,10 +13,10 @@ func (gui *Gui) exitDiffMode() error { } func (gui *Gui) renderDiff() error { - cmd := gui.OSCommand.ExecutableFromString( + cmdObj := gui.OSCommand.NewCmdObj( fmt.Sprintf("git diff --submodule --no-ext-diff --color %s", gui.diffStr()), ) - task := NewRunPtyTask(cmd) + task := NewRunPtyTask(cmdObj.GetCmd()) return gui.refreshMainViews(refreshMainOpts{ main: &viewUpdateOpts{ diff --git a/pkg/gui/files_panel.go b/pkg/gui/files_panel.go index a0e71bc20..91415e1a7 100644 --- a/pkg/gui/files_panel.go +++ b/pkg/gui/files_panel.go @@ -58,22 +58,20 @@ func (gui *Gui) filesRenderToMain() error { return gui.refreshMergePanelWithLock() } - cmdStr := gui.GitCommand.WorktreeFileDiffCmdStr(node, false, !node.GetHasUnstagedChanges() && node.GetHasStagedChanges(), gui.State.IgnoreWhitespaceInDiffView) - cmd := gui.OSCommand.ExecutableFromString(cmdStr) + cmdObj := gui.GitCommand.WorktreeFileDiffCmdObj(node, false, !node.GetHasUnstagedChanges() && node.GetHasStagedChanges(), gui.State.IgnoreWhitespaceInDiffView) refreshOpts := refreshMainOpts{main: &viewUpdateOpts{ title: gui.Tr.UnstagedChanges, - task: NewRunPtyTask(cmd), + task: NewRunPtyTask(cmdObj.GetCmd()), }} if node.GetHasUnstagedChanges() { if node.GetHasStagedChanges() { - cmdStr := gui.GitCommand.WorktreeFileDiffCmdStr(node, false, true, gui.State.IgnoreWhitespaceInDiffView) - cmd := gui.OSCommand.ExecutableFromString(cmdStr) + cmdObj := gui.GitCommand.WorktreeFileDiffCmdObj(node, false, true, gui.State.IgnoreWhitespaceInDiffView) refreshOpts.secondary = &viewUpdateOpts{ title: gui.Tr.StagedChanges, - task: NewRunPtyTask(cmd), + task: NewRunPtyTask(cmdObj.GetCmd()), } } } else { @@ -440,9 +438,9 @@ func (gui *Gui) handleAmendCommitPress() error { title: strings.Title(gui.Tr.AmendLastCommit), prompt: gui.Tr.SureToAmend, handleConfirm: func() error { - cmdStr := gui.GitCommand.AmendHeadCmdStr() - gui.OnRunCommand(oscommands.NewCmdLogEntry(cmdStr, gui.Tr.Spans.AmendCommit, true)) - return gui.withGpgHandling(cmdStr, gui.Tr.AmendingStatus, nil) + cmdObj := gui.GitCommand.AmendHeadCmdObj() + gui.OnRunCommand(oscommands.NewCmdLogEntry(cmdObj.ToString(), gui.Tr.Spans.AmendCommit, true)) + return gui.withGpgHandling(cmdObj, gui.Tr.AmendingStatus, nil) }, }) } @@ -464,8 +462,10 @@ func (gui *Gui) handleCommitEditorPress() error { args = append(args, "--signoff") } + cmdStr := "git " + strings.Join(args, " ") + return gui.runSubprocessWithSuspenseAndRefresh( - gui.OSCommand.WithSpan(gui.Tr.Spans.Commit).PrepareSubProcess("git", args...), + gui.GitCommand.WithSpan(gui.Tr.Spans.Commit).NewCmdObjWithLog(cmdStr), ) } @@ -511,7 +511,7 @@ func (gui *Gui) editFileAtLine(filename string, lineNumber int) error { } return gui.runSubprocessWithSuspenseAndRefresh( - gui.OSCommand.WithSpan(gui.Tr.Spans.EditFile).ShellCommandFromString(cmdStr), + gui.OSCommand.WithSpan(gui.Tr.Spans.EditFile).NewShellCmdObjFromString(cmdStr), ) } @@ -923,7 +923,7 @@ func (gui *Gui) handleCustomCommand() error { gui.OnRunCommand(oscommands.NewCmdLogEntry(command, gui.Tr.Spans.CustomCommand, true)) return gui.runSubprocessWithSuspenseAndRefresh( - gui.OSCommand.PrepareShellSubProcess(command), + gui.OSCommand.NewShellCmdObjFromString2(command), ) }, }) @@ -1004,7 +1004,7 @@ func (gui *Gui) handleOpenMergeTool() error { prompt: gui.Tr.MergeToolPrompt, handleConfirm: func() error { return gui.runSubprocessWithSuspenseAndRefresh( - gui.OSCommand.ExecutableFromString(gui.GitCommand.OpenMergeToolCmd()), + gui.GitCommand.OpenMergeToolCmdObj(), ) }, }) diff --git a/pkg/gui/git_flow.go b/pkg/gui/git_flow.go index 31c64385b..29e0b2335 100644 --- a/pkg/gui/git_flow.go +++ b/pkg/gui/git_flow.go @@ -32,7 +32,7 @@ func (gui *Gui) gitFlowFinishBranch(gitFlowConfig string, branchName string) err } return gui.runSubprocessWithSuspenseAndRefresh( - gui.OSCommand.WithSpan(gui.Tr.Spans.GitFlowFinish).PrepareSubProcess("git", "flow", branchType, "finish", suffix), + gui.GitCommand.WithSpan(gui.Tr.Spans.GitFlowFinish).NewCmdObjWithLog("git flow " + branchType + " finish " + suffix), ) } @@ -43,7 +43,7 @@ func (gui *Gui) handleCreateGitFlowMenu() error { } // get config - gitFlowConfig, err := gui.GitCommand.RunCommandWithOutput("git config --local --get-regexp gitflow") + gitFlowConfig, err := gui.GitCommand.RunWithOutput(gui.GitCommand.NewCmdObj("git config --local --get-regexp gitflow")) if err != nil { return gui.createErrorPanel("You need to install git-flow and enable it in this repo to use git-flow features") } @@ -56,7 +56,7 @@ func (gui *Gui) handleCreateGitFlowMenu() error { title: title, handleConfirm: func(name string) error { return gui.runSubprocessWithSuspenseAndRefresh( - gui.OSCommand.WithSpan(gui.Tr.Spans.GitFlowStart).PrepareSubProcess("git", "flow", branchType, "start", name), + gui.GitCommand.WithSpan(gui.Tr.Spans.GitFlowStart).NewCmdObjWithLog("git flow " + branchType + " start " + name), ) }, }) diff --git a/pkg/gui/gpg.go b/pkg/gui/gpg.go index 1811e4fb5..9bd8b5d15 100644 --- a/pkg/gui/gpg.go +++ b/pkg/gui/gpg.go @@ -3,6 +3,7 @@ package gui import ( "fmt" + "github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/gui/style" ) @@ -10,12 +11,11 @@ import ( // WithWaitingStatus we get stuck there and can't return to lazygit. We could // fix this bug, or just stop running subprocesses from within there, given that // we don't need to see a loading status if we're in a subprocess. -func (gui *Gui) withGpgHandling(cmdStr string, waitingStatus string, onSuccess func() error) error { +// TODO: work out if we actually need to use a shell command here +func (gui *Gui) withGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error { useSubprocess := gui.GitCommand.UsingGpg() if useSubprocess { - // Need to remember why we use the shell for the subprocess but not in the other case - // Maybe there's no good reason - success, err := gui.runSubprocessWithSuspense(gui.OSCommand.ShellCommandFromString(cmdStr)) + success, err := gui.runSubprocessWithSuspense(gui.OSCommand.NewShellCmdObjFromString(cmdObj.ToString())) if success && onSuccess != nil { if err := onSuccess(); err != nil { return err @@ -27,15 +27,16 @@ func (gui *Gui) withGpgHandling(cmdStr string, waitingStatus string, onSuccess f return err } else { - return gui.RunAndStream(cmdStr, waitingStatus, onSuccess) + return gui.RunAndStream(cmdObj, waitingStatus, onSuccess) } } -func (gui *Gui) RunAndStream(cmdStr string, waitingStatus string, onSuccess func() error) error { +func (gui *Gui) RunAndStream(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error { return gui.WithWaitingStatus(waitingStatus, func() error { - cmd := gui.OSCommand.ShellCommandFromString(cmdStr) - cmd.Env = append(cmd.Env, "TERM=dumb") + cmdObj := gui.OSCommand.NewShellCmdObjFromString(cmdObj.ToString()) + cmdObj.AddEnvVars("TERM=dumb") cmdWriter := gui.getCmdWriter() + cmd := cmdObj.GetCmd() cmd.Stdout = cmdWriter cmd.Stderr = cmdWriter diff --git a/pkg/gui/gui.go b/pkg/gui/gui.go index 4a2248935..39d2a9763 100644 --- a/pkg/gui/gui.go +++ b/pkg/gui/gui.go @@ -7,7 +7,6 @@ import ( "os" "sync" - "os/exec" "strings" "time" @@ -578,7 +577,7 @@ func (gui *Gui) RunAndHandleError() error { } // returns whether command exited without error or not -func (gui *Gui) runSubprocessWithSuspenseAndRefresh(subprocess *exec.Cmd) error { +func (gui *Gui) runSubprocessWithSuspenseAndRefresh(subprocess oscommands.ICmdObj) error { _, err := gui.runSubprocessWithSuspense(subprocess) if err != nil { return err @@ -592,7 +591,7 @@ func (gui *Gui) runSubprocessWithSuspenseAndRefresh(subprocess *exec.Cmd) error } // returns whether command exited without error or not -func (gui *Gui) runSubprocessWithSuspense(subprocess *exec.Cmd) (bool, error) { +func (gui *Gui) runSubprocessWithSuspense(subprocess oscommands.ICmdObj) (bool, error) { gui.Mutexes.SubprocessMutex.Lock() defer gui.Mutexes.SubprocessMutex.Unlock() @@ -621,7 +620,8 @@ func (gui *Gui) runSubprocessWithSuspense(subprocess *exec.Cmd) (bool, error) { return cmdErr == nil, gui.surfaceError(cmdErr) } -func (gui *Gui) runSubprocess(subprocess *exec.Cmd) error { +func (gui *Gui) runSubprocess(cmdObj oscommands.ICmdObj) error { + subprocess := cmdObj.GetCmd() subprocess.Stdout = os.Stdout subprocess.Stderr = os.Stdout subprocess.Stdin = os.Stdin diff --git a/pkg/gui/rebase_options_panel.go b/pkg/gui/rebase_options_panel.go index 280022000..28241e57e 100644 --- a/pkg/gui/rebase_options_panel.go +++ b/pkg/gui/rebase_options_panel.go @@ -58,7 +58,7 @@ func (gui *Gui) genericMergeCommand(command string) error { // it's impossible for a rebase to require a commit so we'll use a subprocess only if it's a merge if status == commands.REBASE_MODE_MERGING && command != REBASE_OPTION_ABORT && gui.Config.GetUserConfig().Git.Merging.ManualCommit { - sub := gitCommand.OSCommand.PrepareSubProcess("git", commandType, fmt.Sprintf("--%s", command)) + sub := gitCommand.NewCmdObj("git " + commandType + " --" + command) if sub != nil { return gui.runSubprocessWithSuspenseAndRefresh(sub) } diff --git a/pkg/gui/recent_repos_panel.go b/pkg/gui/recent_repos_panel.go index 5460a2155..ae5d35948 100644 --- a/pkg/gui/recent_repos_panel.go +++ b/pkg/gui/recent_repos_panel.go @@ -38,10 +38,10 @@ func (gui *Gui) handleCreateRecentReposMenu() error { } func (gui *Gui) handleShowAllBranchLogs() error { - cmd := gui.OSCommand.ExecutableFromString( + cmdObj := gui.OSCommand.NewCmdObj( gui.Config.GetUserConfig().Git.AllBranchesLogCmd, ) - task := NewRunPtyTask(cmd) + task := NewRunPtyTask(cmdObj.GetCmd()) return gui.refreshMainViews(refreshMainOpts{ main: &viewUpdateOpts{ diff --git a/pkg/gui/reflog_panel.go b/pkg/gui/reflog_panel.go index a5f1d2614..9df0d2501 100644 --- a/pkg/gui/reflog_panel.go +++ b/pkg/gui/reflog_panel.go @@ -22,11 +22,9 @@ func (gui *Gui) reflogCommitsRenderToMain() error { if commit == nil { task = NewRenderStringTask("No reflog history") } else { - cmd := gui.OSCommand.ExecutableFromString( - gui.GitCommand.ShowCmdStr(commit.Sha, gui.State.Modes.Filtering.GetPath()), - ) + cmdObj := gui.GitCommand.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath()) - task = NewRunPtyTask(cmd) + task = NewRunPtyTask(cmdObj.GetCmd()) } return gui.refreshMainViews(refreshMainOpts{ diff --git a/pkg/gui/remote_branches_panel.go b/pkg/gui/remote_branches_panel.go index eba2e5d2b..a18479281 100644 --- a/pkg/gui/remote_branches_panel.go +++ b/pkg/gui/remote_branches_panel.go @@ -24,10 +24,8 @@ func (gui *Gui) remoteBranchesRenderToMain() error { if remoteBranch == nil { task = NewRenderStringTask("No branches for this remote") } else { - cmd := gui.OSCommand.ExecutableFromString( - gui.GitCommand.GetBranchGraphCmdStr(remoteBranch.FullName()), - ) - task = NewRunCommandTask(cmd) + cmdObj := gui.GitCommand.GetBranchGraphCmdObj(remoteBranch.FullName()) + task = NewRunCommandTask(cmdObj.GetCmd()) } return gui.refreshMainViews(refreshMainOpts{ diff --git a/pkg/gui/reset_menu_panel.go b/pkg/gui/reset_menu_panel.go index fc15d36de..609655571 100644 --- a/pkg/gui/reset_menu_panel.go +++ b/pkg/gui/reset_menu_panel.go @@ -3,12 +3,11 @@ package gui import ( "fmt" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/gui/style" ) -func (gui *Gui) resetToRef(ref string, strength string, span string, options oscommands.RunCommandOptions) error { - if err := gui.GitCommand.WithSpan(span).ResetToCommit(ref, strength, options); err != nil { +func (gui *Gui) resetToRef(ref string, strength string, span string, envVars []string) error { + if err := gui.GitCommand.WithSpan(span).ResetToCommit(ref, strength, envVars); err != nil { return gui.surfaceError(err) } @@ -39,7 +38,7 @@ func (gui *Gui) createResetMenu(ref string) error { style.FgRed.Sprintf("reset --%s %s", strength, ref), }, onPress: func() error { - return gui.resetToRef(ref, strength, "Reset", oscommands.RunCommandOptions{}) + return gui.resetToRef(ref, strength, "Reset", []string{}) }, } } diff --git a/pkg/gui/stash_panel.go b/pkg/gui/stash_panel.go index 61dd6afd8..13837a502 100644 --- a/pkg/gui/stash_panel.go +++ b/pkg/gui/stash_panel.go @@ -22,10 +22,10 @@ func (gui *Gui) stashRenderToMain() error { if stashEntry == nil { task = NewRenderStringTask(gui.Tr.NoStashEntries) } else { - cmd := gui.OSCommand.ExecutableFromString( + cmdObj := gui.OSCommand.NewCmdObj( gui.GitCommand.ShowStashEntryCmdStr(stashEntry.Index), ) - task = NewRunPtyTask(cmd) + task = NewRunPtyTask(cmdObj.GetCmd()) } return gui.refreshMainViews(refreshMainOpts{ diff --git a/pkg/gui/sub_commits_panel.go b/pkg/gui/sub_commits_panel.go index a60767eea..fe2d24c40 100644 --- a/pkg/gui/sub_commits_panel.go +++ b/pkg/gui/sub_commits_panel.go @@ -23,11 +23,9 @@ func (gui *Gui) subCommitsRenderToMain() error { if commit == nil { task = NewRenderStringTask("No commits") } else { - cmd := gui.OSCommand.ExecutableFromString( - gui.GitCommand.ShowCmdStr(commit.Sha, gui.State.Modes.Filtering.GetPath()), - ) + cmdObj := gui.GitCommand.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath()) - task = NewRunPtyTask(cmd) + task = NewRunPtyTask(cmdObj.GetCmd()) } return gui.refreshMainViews(refreshMainOpts{ diff --git a/pkg/gui/submodules_panel.go b/pkg/gui/submodules_panel.go index 4dd6aa628..01cd130e0 100644 --- a/pkg/gui/submodules_panel.go +++ b/pkg/gui/submodules_panel.go @@ -36,9 +36,8 @@ func (gui *Gui) submodulesRenderToMain() error { if file == nil { task = NewRenderStringTask(prefix) } else { - cmdStr := gui.GitCommand.WorktreeFileDiffCmdStr(file, false, !file.HasUnstagedChanges && file.HasStagedChanges, gui.State.IgnoreWhitespaceInDiffView) - cmd := gui.OSCommand.ExecutableFromString(cmdStr) - task = NewRunCommandTaskWithPrefix(cmd, prefix) + cmdObj := gui.GitCommand.WorktreeFileDiffCmdObj(file, false, !file.HasUnstagedChanges && file.HasStagedChanges, gui.State.IgnoreWhitespaceInDiffView) + task = NewRunCommandTaskWithPrefix(cmdObj.GetCmd(), prefix) } } @@ -212,10 +211,10 @@ func (gui *Gui) handleResetRemoveSubmodule(submodule *models.SubmoduleConfig) er func (gui *Gui) handleBulkSubmoduleActionsMenu() error { menuItems := []*menuItem{ { - displayStrings: []string{gui.Tr.LcBulkInitSubmodules, style.FgGreen.Sprint(gui.GitCommand.SubmoduleBulkInitCmdStr())}, + displayStrings: []string{gui.Tr.LcBulkInitSubmodules, style.FgGreen.Sprint(gui.GitCommand.SubmoduleBulkInitCmdObj().ToString())}, onPress: func() error { return gui.WithWaitingStatus(gui.Tr.LcRunningCommand, func() error { - if err := gui.OSCommand.WithSpan(gui.Tr.Spans.BulkInitialiseSubmodules).RunCommand(gui.GitCommand.SubmoduleBulkInitCmdStr()); err != nil { + if err := gui.OSCommand.WithSpan(gui.Tr.Spans.BulkInitialiseSubmodules).Run(gui.GitCommand.SubmoduleBulkInitCmdObj()); err != nil { return gui.surfaceError(err) } @@ -224,10 +223,10 @@ func (gui *Gui) handleBulkSubmoduleActionsMenu() error { }, }, { - displayStrings: []string{gui.Tr.LcBulkUpdateSubmodules, style.FgYellow.Sprint(gui.GitCommand.SubmoduleBulkUpdateCmdStr())}, + displayStrings: []string{gui.Tr.LcBulkUpdateSubmodules, style.FgYellow.Sprint(gui.GitCommand.SubmoduleBulkUpdateCmdObj().ToString())}, onPress: func() error { return gui.WithWaitingStatus(gui.Tr.LcRunningCommand, func() error { - if err := gui.OSCommand.WithSpan(gui.Tr.Spans.BulkUpdateSubmodules).RunCommand(gui.GitCommand.SubmoduleBulkUpdateCmdStr()); err != nil { + if err := gui.OSCommand.WithSpan(gui.Tr.Spans.BulkUpdateSubmodules).Run(gui.GitCommand.SubmoduleBulkUpdateCmdObj()); err != nil { return gui.surfaceError(err) } @@ -236,7 +235,7 @@ func (gui *Gui) handleBulkSubmoduleActionsMenu() error { }, }, { - displayStrings: []string{gui.Tr.LcSubmoduleStashAndReset, style.FgRed.Sprintf("git stash in each submodule && %s", gui.GitCommand.SubmoduleForceBulkUpdateCmdStr())}, + displayStrings: []string{gui.Tr.LcSubmoduleStashAndReset, style.FgRed.Sprintf("git stash in each submodule && %s", gui.GitCommand.SubmoduleForceBulkUpdateCmdObj().ToString())}, onPress: func() error { return gui.WithWaitingStatus(gui.Tr.LcRunningCommand, func() error { if err := gui.GitCommand.WithSpan(gui.Tr.Spans.BulkStashAndResetSubmodules).ResetSubmodules(gui.State.Submodules); err != nil { @@ -248,10 +247,10 @@ func (gui *Gui) handleBulkSubmoduleActionsMenu() error { }, }, { - displayStrings: []string{gui.Tr.LcBulkDeinitSubmodules, style.FgRed.Sprint(gui.GitCommand.SubmoduleBulkDeinitCmdStr())}, + displayStrings: []string{gui.Tr.LcBulkDeinitSubmodules, style.FgRed.Sprint(gui.GitCommand.SubmoduleBulkDeinitCmdObj().ToString())}, onPress: func() error { return gui.WithWaitingStatus(gui.Tr.LcRunningCommand, func() error { - if err := gui.OSCommand.WithSpan(gui.Tr.Spans.BulkDeinitialiseSubmodules).RunCommand(gui.GitCommand.SubmoduleBulkDeinitCmdStr()); err != nil { + if err := gui.OSCommand.WithSpan(gui.Tr.Spans.BulkDeinitialiseSubmodules).Run(gui.GitCommand.SubmoduleBulkDeinitCmdObj()); err != nil { return gui.surfaceError(err) } diff --git a/pkg/gui/tags_panel.go b/pkg/gui/tags_panel.go index b747c7e1a..51790fd58 100644 --- a/pkg/gui/tags_panel.go +++ b/pkg/gui/tags_panel.go @@ -25,10 +25,8 @@ func (gui *Gui) tagsRenderToMain() error { if tag == nil { task = NewRenderStringTask("No tags") } else { - cmd := gui.OSCommand.ExecutableFromString( - gui.GitCommand.GetBranchGraphCmdStr(tag.Name), - ) - task = NewRunCommandTask(cmd) + cmdObj := gui.GitCommand.GetBranchGraphCmdObj(tag.Name) + task = NewRunCommandTask(cmdObj.GetCmd()) } return gui.refreshMainViews(refreshMainOpts{ diff --git a/pkg/gui/undoing.go b/pkg/gui/undoing.go index bd67408d4..76a391730 100644 --- a/pkg/gui/undoing.go +++ b/pkg/gui/undoing.go @@ -2,7 +2,6 @@ package gui import ( "github.com/jesseduffield/lazygit/pkg/commands" - "github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/utils" ) @@ -169,7 +168,7 @@ func (gui *Gui) handleHardResetWithAutoStash(commitSha string, options handleHar gitCommand := gui.GitCommand.WithSpan(options.span) reset := func() error { - if err := gui.resetToRef(commitSha, "hard", options.span, oscommands.RunCommandOptions{EnvVars: options.EnvVars}); err != nil { + if err := gui.resetToRef(commitSha, "hard", options.span, options.EnvVars); err != nil { return gui.surfaceError(err) } return nil diff --git a/pkg/integration/integration.go b/pkg/integration/integration.go index 4b1842d44..ba49d3bd7 100644 --- a/pkg/integration/integration.go +++ b/pkg/integration/integration.go @@ -45,7 +45,7 @@ func RunTests( testDir := filepath.Join(rootDir, "test", "integration") osCommand := oscommands.NewDummyOSCommand() - err = osCommand.RunCommand("go build -o %s", tempLazygitPath()) + err = osCommand.Run(osCommand.NewCmdObj("go build -o " + tempLazygitPath())) if err != nil { return err } @@ -216,11 +216,10 @@ func GetRootDirectory() string { } func createFixture(testPath, actualDir string) error { - osCommand := oscommands.NewDummyOSCommand() bashScriptPath := filepath.Join(testPath, "setup.sh") cmd := secureexec.Command("bash", bashScriptPath, actualDir) - if err := osCommand.RunExecutable(cmd); err != nil { + if _, err := cmd.CombinedOutput(); err != nil { return err } @@ -320,7 +319,7 @@ func generateSnapshot(dir string) (string, error) { for _, cmdStr := range cmdStrs { // ignoring error for now. If there's an error it could be that there are no results - output, _ := osCommand.RunCommandWithOutput(cmdStr) + output, _ := osCommand.RunWithOutput(osCommand.NewCmdObj(cmdStr)) snapshot += output + "\n" } @@ -429,22 +428,16 @@ func getLazygitCommand(testPath string, rootDir string, record bool, speed float cmdStr := fmt.Sprintf("%s -debug --use-config-dir=%s --path=%s %s", tempLazygitPath(), configDir, actualDir, extraCmdArgs) - cmd := osCommand.ExecutableFromString(cmdStr) - cmd.Env = append(cmd.Env, fmt.Sprintf("SPEED=%f", speed)) + cmdObj := osCommand.NewCmdObj(cmdStr) + cmdObj.AddEnvVars(fmt.Sprintf("SPEED=%f", speed)) if record { - cmd.Env = append( - cmd.Env, - fmt.Sprintf("RECORD_EVENTS_TO=%s", replayPath), - ) + cmdObj.AddEnvVars(fmt.Sprintf("RECORD_EVENTS_TO=%s", replayPath)) } else { - cmd.Env = append( - cmd.Env, - fmt.Sprintf("REPLAY_EVENTS_FROM=%s", replayPath), - ) + cmdObj.AddEnvVars(fmt.Sprintf("REPLAY_EVENTS_FROM=%s", replayPath)) } - return cmd, nil + return cmdObj.GetCmd(), nil } func folderExists(path string) bool { diff --git a/pkg/updates/updates.go b/pkg/updates/updates.go index cb5a64788..b6d0f2734 100644 --- a/pkg/updates/updates.go +++ b/pkg/updates/updates.go @@ -300,7 +300,8 @@ func (u *Updater) downloadAndInstall(rawUrl string) error { } u.Log.Info("untarring tarball/unzipping zip file") - if err := u.OSCommand.RunCommand("tar -zxf %s %s", u.OSCommand.Quote(zipPath), "lazygit"); err != nil { + cmdObj := u.OSCommand.NewCmdObj(fmt.Sprintf("tar -zxf %s %s", u.OSCommand.Quote(zipPath), "lazygit")) + if err := u.OSCommand.Run(cmdObj); err != nil { return err }