This commit is contained in:
Jesse Duffield 2021-12-07 21:59:36 +11:00
parent 157dd309f7
commit b4c078d565
48 changed files with 437 additions and 493 deletions

View file

@ -146,7 +146,7 @@ func NewApp(config config.AppConfigurer, filterPath string) (*App, error) {
} }
func (app *App) validateGitVersion() 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 // if we get an error anywhere here we'll show the same status
minVersionError := errors.New(app.Tr.MinGitVersionError) minVersionError := errors.New(app.Tr.MinGitVersionError)
if err != nil { if err != nil {
@ -231,7 +231,7 @@ func (app *App) setupRepo() (bool, error) {
os.Exit(1) 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 return false, err
} }
} }

View file

@ -11,19 +11,19 @@ import (
// NewBranch create new branch // NewBranch create new branch
func (c *GitCommand) NewBranch(name string, base string) error { 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. // CurrentBranchName get the current branch name and displayname.
// the first returned string is the name and the second is the 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)' // e.g. name is 123asdf and displayname is '(HEAD detached at 123asdf)'
func (c *GitCommand) CurrentBranchName() (string, string, error) { 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" { if err == nil && branchName != "HEAD\n" {
trimmedBranchName := strings.TrimSpace(branchName) trimmedBranchName := strings.TrimSpace(branchName)
return trimmedBranchName, trimmedBranchName, nil return trimmedBranchName, trimmedBranchName, nil
} }
output, err := c.RunCommandWithOutput("git branch --contains") output, err := c.RunWithOutput(c.NewCmdObj("git branch --contains"))
if err != nil { if err != nil {
return "", "", err return "", "", err
} }
@ -47,7 +47,7 @@ func (c *GitCommand) DeleteBranch(branch string, force bool) error {
command = "git branch -D" 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 // 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 { if options.Force {
forceArg = " --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 // 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 // Currently it limits the result to 100 commits, but when we get async stuff
// working we can do lazy loading // working we can do lazy loading
func (c *GitCommand) GetBranchGraph(branchName string) (string, error) { func (c *GitCommand) GetBranchGraph(branchName string) (string, error) {
cmdStr := c.GetBranchGraphCmdStr(branchName) return c.OSCommand.RunWithOutput(c.GetBranchGraphCmdObj(branchName))
return c.OSCommand.RunCommandWithOutput(cmdStr)
} }
func (c *GitCommand) GetUpstreamForBranch(branchName string) (string, error) { 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 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 branchLogCmdTemplate := c.Config.GetUserConfig().Git.BranchLogCmd
templateValues := map[string]string{ templateValues := map[string]string{
"branchName": c.OSCommand.Quote(branchName), "branchName": c.OSCommand.Quote(branchName),
} }
return utils.ResolvePlaceholderString(branchLogCmdTemplate, templateValues) return c.NewCmdObj(utils.ResolvePlaceholderString(branchLogCmdTemplate, templateValues))
} }
func (c *GitCommand) SetUpstreamBranch(upstream string) error { 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 { 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) { func (c *GitCommand) GetCurrentBranchUpstreamDifferenceCount() (string, string) {
@ -105,11 +111,11 @@ func (c *GitCommand) GetBranchUpstreamDifferenceCount(branchName string) (string
// current branch // current branch
func (c *GitCommand) GetCommitDifferences(from, to string) (string, string) { func (c *GitCommand) GetCommitDifferences(from, to string) (string, string) {
command := "git rev-list %s..%s --count" 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 { if err != nil {
return "?", "?" return "?", "?"
} }
pullableCount, err := c.OSCommand.RunCommandWithOutput(command, from, to) pullableCount, err := c.RunWithOutput(c.NewCmdObj(fmt.Sprintf(command, from, to)))
if err != nil { if err != nil {
return "?", "?" return "?", "?"
} }
@ -129,33 +135,33 @@ func (c *GitCommand) Merge(branchName string, opts MergeOpts) error {
command = fmt.Sprintf("%s --ff-only", command) command = fmt.Sprintf("%s --ff-only", command)
} }
return c.OSCommand.RunCommand(command) return c.OSCommand.Run(c.OSCommand.NewCmdObj(command))
} }
// AbortMerge abort merge // AbortMerge abort merge
func (c *GitCommand) AbortMerge() error { func (c *GitCommand) AbortMerge() error {
return c.RunCommand("git merge --abort") return c.Run(c.NewCmdObj("git merge --abort"))
} }
func (c *GitCommand) IsHeadDetached() bool { 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 return err != nil
} }
// ResetHardHead runs `git reset --hard` // ResetHardHead runs `git reset --hard`
func (c *GitCommand) ResetHard(ref string) error { 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` // ResetSoft runs `git reset --soft HEAD`
func (c *GitCommand) ResetSoft(ref string) error { 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 { 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 { 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))))
} }

View file

@ -210,7 +210,7 @@ func TestGitCommandGetAllBranchGraph(t *testing.T) {
return secureexec.Command("echo") return secureexec.Command("echo")
} }
cmdStr := gitCmd.Config.GetUserConfig().Git.AllBranchesLogCmd cmdStr := gitCmd.Config.GetUserConfig().Git.AllBranchesLogCmd
_, err := gitCmd.OSCommand.RunCommandWithOutput(cmdStr) _, err := gitCmd.OSCommand.RunWithOutput(gitCmd.NewCmdObj(cmdStr))
assert.NoError(t, err) assert.NoError(t, err)
} }

View file

@ -10,15 +10,21 @@ import (
// RenameCommit renames the topmost commit with the given name // RenameCommit renames the topmost commit with the given name
func (c *GitCommand) RenameCommit(name string) error { 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 // ResetToCommit reset to commit
func (c *GitCommand) ResetToCommit(sha string, strength string, options oscommands.RunCommandOptions) error { func (c *GitCommand) ResetToCommit(sha string, strength string, envVars []string) error {
return c.OSCommand.RunCommandWithOptions(fmt.Sprintf("git reset --%s %s", strength, sha), options) 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") splitMessage := strings.Split(message, "\n")
lineArgs := "" lineArgs := ""
for _, line := range splitMessage { for _, line := range splitMessage {
@ -30,52 +36,53 @@ func (c *GitCommand) CommitCmdStr(message string, flags string) string {
flagsStr = fmt.Sprintf(" %s", flags) 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 // Get the subject of the HEAD commit
func (c *GitCommand) GetHeadCommitMessage() (string, error) { func (c *GitCommand) GetHeadCommitMessage() (string, error) {
cmdStr := "git log -1 --pretty=%s" message, err := c.RunWithOutput(c.NewCmdObj("git log -1 --pretty=%s"))
message, err := c.OSCommand.RunCommandWithOutput(cmdStr)
return strings.TrimSpace(message), err return strings.TrimSpace(message), err
} }
func (c *GitCommand) GetCommitMessage(commitSha string) (string, error) { func (c *GitCommand) GetCommitMessage(commitSha string) (string, error) {
cmdStr := "git rev-list --format=%B --max-count=1 " + commitSha 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") message := strings.Join(strings.SplitAfter(messageWithHeader, "\n")[1:], "\n")
return strings.TrimSpace(message), err return strings.TrimSpace(message), err
} }
func (c *GitCommand) GetCommitMessageFirstLine(sha string) (string, error) { 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 // AmendHead amends HEAD with whatever is staged in your working tree
func (c *GitCommand) AmendHead() error { func (c *GitCommand) AmendHead() error {
return c.OSCommand.RunCommand(c.AmendHeadCmdStr()) return c.OSCommand.Run(c.AmendHeadCmdObj())
} }
func (c *GitCommand) AmendHeadCmdStr() string { func (c *GitCommand) AmendHeadCmdObj() oscommands.ICmdObj {
return "git commit --amend --no-edit --allow-empty" 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 contextSize := c.Config.GetUserConfig().Git.DiffContextSize
filterPathArg := "" filterPathArg := ""
if filterPath != "" { if filterPath != "" {
filterPathArg = fmt.Sprintf(" -- %s", c.OSCommand.Quote(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 // Revert reverts the selected commit by sha
func (c *GitCommand) Revert(sha string) error { 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 { 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 // 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 err
} }
return c.OSCommand.RunPreparedCommand(cmd) return c.OSCommand.Run(cmd)
} }
// CreateFixupCommit creates a commit that fixes up a previous commit // CreateFixupCommit creates a commit that fixes up a previous commit
func (c *GitCommand) CreateFixupCommit(sha string) error { 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)))
} }

View file

@ -4,7 +4,6 @@ import (
"os/exec" "os/exec"
"testing" "testing"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/secureexec" "github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/jesseduffield/lazygit/pkg/test" "github.com/jesseduffield/lazygit/pkg/test"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -33,11 +32,11 @@ func TestGitCommandResetToCommit(t *testing.T) {
return secureexec.Command("echo") 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. // TestGitCommandCommitObj is a function.
func TestGitCommandCommitStr(t *testing.T) { func TestGitCommandCommitObj(t *testing.T) {
gitCmd := NewDummyGitCommand() gitCmd := NewDummyGitCommand()
type scenario struct { type scenario struct {
@ -70,7 +69,7 @@ func TestGitCommandCommitStr(t *testing.T) {
for _, s := range scenarios { for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) { 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) assert.Equal(t, s.expected, cmdStr)
}) })
} }
@ -111,8 +110,8 @@ func TestGitCommandCreateFixupCommit(t *testing.T) {
} }
} }
// TestGitCommandShowCmdStr is a function. // TestGitCommandShowCmdObj is a function.
func TestGitCommandShowCmdStr(t *testing.T) { func TestGitCommandShowCmdObj(t *testing.T) {
type scenario struct { type scenario struct {
testName string testName string
filterPath string filterPath string
@ -146,7 +145,7 @@ func TestGitCommandShowCmdStr(t *testing.T) {
for _, s := range scenarios { for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
gitCmd.Config.GetUserConfig().Git.DiffContextSize = s.contextSize 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) assert.Equal(t, s.expected, cmdStr)
}) })
} }

View file

@ -10,6 +10,7 @@ import (
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/models" "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/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
) )
@ -23,27 +24,27 @@ func (c *GitCommand) CatFile(fileName string) (string, error) {
return string(buf), nil return string(buf), nil
} }
func (c *GitCommand) OpenMergeToolCmd() string { func (c *GitCommand) OpenMergeToolCmdObj() oscommands.ICmdObj {
return "git mergetool" return c.NewCmdObj("git mergetool")
} }
func (c *GitCommand) OpenMergeTool() error { func (c *GitCommand) OpenMergeTool() error {
return c.OSCommand.RunCommand("git mergetool") return c.Run(c.OpenMergeToolCmdObj())
} }
// StageFile stages a file // StageFile stages a file
func (c *GitCommand) StageFile(fileName string) error { 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 // StageAll stages all files
func (c *GitCommand) StageAll() error { func (c *GitCommand) StageAll() error {
return c.RunCommand("git add -A") return c.Run(c.NewCmdObj("git add -A"))
} }
// UnstageAll unstages all files // UnstageAll unstages all files
func (c *GitCommand) UnstageAll() error { func (c *GitCommand) UnstageAll() error {
return c.RunCommand("git reset") return c.Run(c.NewCmdObj("git reset"))
} }
// UnStageFile unstages a file // UnStageFile unstages a file
@ -56,7 +57,8 @@ func (c *GitCommand) UnStageFile(fileNames []string, reset bool) error {
} }
for _, name := range fileNames { 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 return err
} }
} }
@ -120,22 +122,22 @@ func (c *GitCommand) DiscardAllFileChanges(file *models.File) error {
quotedFileName := c.OSCommand.Quote(file.Name) quotedFileName := c.OSCommand.Quote(file.Name)
if file.ShortStatus == "AA" { 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 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 err
} }
return nil return nil
} }
if file.ShortStatus == "DU" { 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 the file isn't tracked, we assume you want to delete it
if file.HasStagedChanges || file.HasMergeConflicts { 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 return err
} }
} }
@ -161,7 +163,7 @@ func (c *GitCommand) DiscardUnstagedDirChanges(node *filetree.FileNode) error {
} }
quotedPath := c.OSCommand.Quote(node.GetPath()) 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 return err
} }
@ -186,7 +188,7 @@ func (c *GitCommand) RemoveUntrackedDirFiles(node *filetree.FileNode) error {
// DiscardUnstagedFileChanges directly // DiscardUnstagedFileChanges directly
func (c *GitCommand) DiscardUnstagedFileChanges(file *models.File) error { func (c *GitCommand) DiscardUnstagedFileChanges(file *models.File) error {
quotedFileName := c.OSCommand.Quote(file.Name) 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 // 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 // WorktreeFileDiff returns the diff of a file
func (c *GitCommand) WorktreeFileDiff(file *models.File, plain bool, cached bool, ignoreWhitespace bool) string { 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 // 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 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 := "" cachedArg := ""
trackedArg := "--" trackedArg := "--"
colorArg := c.colorArg() colorArg := c.colorArg()
@ -221,7 +223,9 @@ func (c *GitCommand) WorktreeFileDiffCmdStr(node models.IFile, plain bool, cache
ignoreWhitespaceArg = "--ignore-all-space" 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 { func (c *GitCommand) ApplyPatch(patch string, flags ...string) error {
@ -236,17 +240,17 @@ func (c *GitCommand) ApplyPatch(patch string, flags ...string) error {
flagStr += " --" + flag 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 // 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. // 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) { func (c *GitCommand) ShowFileDiff(from string, to string, reverse bool, fileName string, plain bool) (string, error) {
cmdStr := c.ShowFileDiffCmdStr(from, to, reverse, fileName, plain) cmdObj := c.ShowFileDiffCmdObj(from, to, reverse, fileName, plain)
return c.OSCommand.RunCommandWithOutput(cmdStr) 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() colorArg := c.colorArg()
contextSize := c.Config.GetUserConfig().Git.DiffContextSize contextSize := c.Config.GetUserConfig().Git.DiffContextSize
if plain { if plain {
@ -258,12 +262,12 @@ func (c *GitCommand) ShowFileDiffCmdStr(from string, to string, reverse bool, fi
reverseFlag = " -R " 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 // CheckoutFile checks out the file for the given commit
func (c *GitCommand) CheckoutFile(commitSha, fileName string) error { 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 // 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) // 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 { if err := c.OSCommand.Remove(fileName); err != nil {
return err return err
} }
@ -296,17 +300,17 @@ func (c *GitCommand) DiscardOldFileChanges(commits []*models.Commit, commitIndex
// DiscardAnyUnstagedFileChanges discards any unstages file changes via `git checkout -- .` // DiscardAnyUnstagedFileChanges discards any unstages file changes via `git checkout -- .`
func (c *GitCommand) DiscardAnyUnstagedFileChanges() error { 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 // RemoveTrackedFiles will delete the given file(s) even if they are currently tracked
func (c *GitCommand) RemoveTrackedFiles(name string) error { 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` // RemoveUntrackedFiles runs `git clean -fd`
func (c *GitCommand) RemoveUntrackedFiles() error { 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 // 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") editor = c.OSCommand.Getenv("EDITOR")
} }
if 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" editor = "vi"
} }
} }

View file

@ -223,22 +223,22 @@ func findDotGitDir(stat func(string) (os.FileInfo, error), readFile func(filenam
} }
func VerifyInGitRepo(osCommand *oscommands.OSCommand) error { 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 { func (c *GitCommand) Run(cmdObj oscommands.ICmdObj) error {
_, err := c.RunCommandWithOutput(formatString, formatArgs...) _, err := c.RunWithOutput(cmdObj)
return err 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 // TODO: have this retry logic in other places we run the command
waitTime := 50 * time.Millisecond waitTime := 50 * time.Millisecond
retryCount := 5 retryCount := 5
attempt := 0 attempt := 0
for { for {
output, err := c.OSCommand.RunCommandWithOutput(formatString, formatArgs...) output, err := c.OSCommand.RunWithOutput(cmdObj)
if err != nil { if err != nil {
// if we have an error based on the index lock, we should wait a bit and then retry // 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") { 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 { func (c *GitCommand) NewCmdObj(cmdStr string) oscommands.ICmdObj {
return c.OSCommand.NewCmdObjFromStr(cmdStr).AddEnvVars("GIT_OPTIONAL_LOCKS=0") 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
} }

View file

@ -38,7 +38,7 @@ func NewBranchListBuilder(log *logrus.Entry, gitCommand *GitCommand, reflogCommi
func (b *BranchListBuilder) obtainBranches() []*models.Branch { func (b *BranchListBuilder) obtainBranches() []*models.Branch {
cmdStr := `git for-each-ref --sort=-committerdate --format="%(HEAD)|%(refname:short)|%(upstream:short)|%(upstream:track)" refs/heads` 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 { if err != nil {
panic(err) panic(err)
} }

View file

@ -1,6 +1,7 @@
package commands package commands
import ( import (
"fmt"
"strings" "strings"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
@ -13,7 +14,7 @@ func (c *GitCommand) GetFilesInDiff(from string, to string, reverse bool) ([]*mo
reverseFlag = " -R " 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 { if err != nil {
return nil, err return nil, err
} }

View file

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strconv" "strconv"
@ -153,9 +152,9 @@ func (c *CommitListBuilder) GetCommits(opts GetCommitsOptions) ([]*models.Commit
passedFirstPushedCommit = true 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) { if canExtractCommit(line) {
commit := c.extractCommitFromLine(line) commit := c.extractCommitFromLine(line)
if commit.Sha == firstPushedCommit { 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 // note that we're not filtering these as we do non-rebasing commits just because
// I suspect that will cause some damage // I suspect that will cause some damage
cmd := c.OSCommand.ExecutableFromString( cmdObj := c.OSCommand.NewCmdObj(
fmt.Sprintf( fmt.Sprintf(
"git show %s --no-patch --oneline %s --abbrev=%d", "git show %s --no-patch --oneline %s --abbrev=%d",
strings.Join(commitShas, " "), strings.Join(commitShas, " "),
@ -212,7 +211,7 @@ func (c *CommitListBuilder) getHydratedRebasingCommits(rebaseMode string) ([]*mo
hydratedCommits := make([]*models.Commit, 0, len(commits)) hydratedCommits := make([]*models.Commit, 0, len(commits))
i := 0 i := 0
err = oscommands.RunLineOutputCmd(cmd, func(line string) (bool, error) { err = c.OSCommand.RunLineOutputCmd(cmdObj, func(line string) (bool, error) {
if canExtractCommit(line) { if canExtractCommit(line) {
commit := c.extractCommitFromLine(line) commit := c.extractCommitFromLine(line)
matchingCommit := commits[i] 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 // 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 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. // 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. // all commits above this are deemed unpushed and marked as such.
func (c *CommitListBuilder) getFirstPushedCommit(refName string) (string, error) { 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 { if err != nil {
return "", err return "", err
} }
@ -400,7 +399,7 @@ func (c *CommitListBuilder) getFirstPushedCommit(refName string) (string, error)
} }
// getLog gets the git log. // getLog gets the git log.
func (c *CommitListBuilder) getLogCmd(opts GetCommitsOptions) *exec.Cmd { func (c *CommitListBuilder) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
limitFlag := "" limitFlag := ""
if opts.Limit { if opts.Limit {
limitFlag = "-300" limitFlag = "-300"
@ -419,7 +418,7 @@ func (c *CommitListBuilder) getLogCmd(opts GetCommitsOptions) *exec.Cmd {
allFlag = " --all" allFlag = " --all"
} }
return c.OSCommand.ExecutableFromString( return c.OSCommand.NewCmdObj(
fmt.Sprintf( fmt.Sprintf(
"git log %s %s %s --oneline %s %s --abbrev=%d %s", "git log %s %s %s --oneline %s %s --abbrev=%d %s",
c.OSCommand.Quote(opts.RefName), c.OSCommand.Quote(opts.RefName),

View file

@ -80,7 +80,7 @@ func (c *GitCommand) GitStatus(opts GitStatusOptions) ([]FileStatus, error) {
noRenamesFlag = "--no-renames" 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 { if err != nil {
return []FileStatus{}, err return []FileStatus{}, err
} }

View file

@ -6,7 +6,6 @@ import (
"strings" "strings"
"github.com/jesseduffield/lazygit/pkg/commands/models" "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 // 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)) 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 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) fields := strings.SplitN(line, " ", 3)
if len(fields) <= 2 { if len(fields) <= 2 {
return false, nil return false, nil

View file

@ -10,9 +10,7 @@ import (
) )
func (c *GitCommand) GetRemotes() ([]*models.Remote, error) { func (c *GitCommand) GetRemotes() ([]*models.Remote, error) {
// get remote branches remoteBranchesStr, err := c.RunWithOutput(c.NewCmdObj("git branch -r"))
unescaped := "git branch -r"
remoteBranchesStr, err := c.OSCommand.RunCommandWithOutput(unescaped)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -10,8 +10,7 @@ import (
) )
func (c *GitCommand) getUnfilteredStashEntries() []*models.StashEntry { func (c *GitCommand) getUnfilteredStashEntries() []*models.StashEntry {
unescaped := "git stash list --pretty='%gs'" rawString, _ := c.RunWithOutput(c.NewCmdObj("git stash list --pretty='%gs'"))
rawString, _ := c.OSCommand.RunCommandWithOutput(unescaped)
stashEntries := []*models.StashEntry{} stashEntries := []*models.StashEntry{}
for i, line := range utils.SplitLines(rawString) { for i, line := range utils.SplitLines(rawString) {
stashEntries = append(stashEntries, stashEntryFromLine(line, i)) stashEntries = append(stashEntries, stashEntryFromLine(line, i))
@ -25,7 +24,7 @@ func (c *GitCommand) GetStashEntries(filterPath string) []*models.StashEntry {
return c.getUnfilteredStashEntries() 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 { if err != nil {
return c.getUnfilteredStashEntries() return c.getUnfilteredStashEntries()
} }

View file

@ -10,7 +10,7 @@ import (
func (c *GitCommand) GetTags() ([]*models.Tag, error) { func (c *GitCommand) GetTags() ([]*models.Tag, error) {
// get remote branches, sorted by creation date (descending) // get remote branches, sorted by creation date (descending)
// see: https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---sortltkeygt // 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 { if err != nil {
return nil, err return nil, err
} }

View file

@ -11,6 +11,7 @@ type ICmdObj interface {
GetCmd() *exec.Cmd GetCmd() *exec.Cmd
ToString() string ToString() string
AddEnvVars(...string) ICmdObj AddEnvVars(...string) ICmdObj
GetEnvVars() []string
} }
type CmdObj struct { type CmdObj struct {
@ -31,3 +32,7 @@ func (self *CmdObj) AddEnvVars(vars ...string) ICmdObj {
return self return self
} }
func (self *CmdObj) GetEnvVars() []string {
return self.cmd.Env
}

View file

@ -9,6 +9,7 @@ import (
"path/filepath" "path/filepath"
"strings" "strings"
"sync" "sync"
"testing"
"github.com/go-errors/errors" "github.com/go-errors/errors"
@ -29,14 +30,25 @@ type Platform struct {
OpenLinkCommand string 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 // OSCommand holds all the os commands
type OSCommand struct { type OSCommand struct {
Log *logrus.Entry Log *logrus.Entry
Platform *Platform Platform *Platform
Config config.AppConfigurer Config config.AppConfigurer
Command func(string, ...string) *exec.Cmd Command func(string, ...string) *exec.Cmd
BeforeExecuteCmd func(*exec.Cmd) Getenv func(string) string
Getenv func(string) string
// callback to run before running a command, i.e. for the purposes of logging // callback to run before running a command, i.e. for the purposes of logging
onRunCommand func(CmdLogEntry) onRunCommand func(CmdLogEntry)
@ -45,6 +57,8 @@ type OSCommand struct {
CmdLogSpan string CmdLogSpan string
removeFile func(string) error removeFile func(string) error
IRunner
} }
// TODO: make these fields private // TODO: make these fields private
@ -79,15 +93,17 @@ func NewCmdLogEntry(cmdStr string, span string, commandLine bool) CmdLogEntry {
// NewOSCommand os command runner // NewOSCommand os command runner
func NewOSCommand(log *logrus.Entry, config config.AppConfigurer) *OSCommand { func NewOSCommand(log *logrus.Entry, config config.AppConfigurer) *OSCommand {
return &OSCommand{ c := &OSCommand{
Log: log, Log: log,
Platform: getPlatform(), Platform: getPlatform(),
Config: config, Config: config,
Command: secureexec.Command, Command: secureexec.Command,
BeforeExecuteCmd: func(*exec.Cmd) {}, Getenv: os.Getenv,
Getenv: os.Getenv, removeFile: os.RemoveAll,
removeFile: os.RemoveAll,
} }
c.IRunner = &RealRunner{c: c}
return c
} }
func (c *OSCommand) WithSpan(span string) *OSCommand { func (c *OSCommand) WithSpan(span string) *OSCommand {
@ -104,8 +120,8 @@ func (c *OSCommand) WithSpan(span string) *OSCommand {
return newOSCommand return newOSCommand
} }
func (c *OSCommand) LogExecCmd(cmd *exec.Cmd) { func (c *OSCommand) LogCmdObj(cmdObj ICmdObj) {
c.LogCommand(strings.Join(cmd.Args, " "), true) c.LogCommand(cmdObj.ToString(), true)
} }
func (c *OSCommand) LogCommand(cmdStr string, commandLine bool) { func (c *OSCommand) LogCommand(cmdStr string, commandLine bool) {
@ -131,108 +147,6 @@ func (c *OSCommand) SetRemoveFile(f func(string) error) {
c.removeFile = f 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 <command>'. 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 // FileType tells us if the file is a file, directory or other
func (c *OSCommand) FileType(path string) string { func (c *OSCommand) FileType(path string) string {
fileInfo, err := os.Stat(path) fileInfo, err := os.Stat(path)
@ -245,19 +159,6 @@ func (c *OSCommand) FileType(path string) string {
return "file" 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 // OpenFile opens a file with the given
func (c *OSCommand) OpenFile(filename string) error { func (c *OSCommand) OpenFile(filename string) error {
commandTemplate := c.Config.GetUserConfig().OS.OpenCommand commandTemplate := c.Config.GetUserConfig().OS.OpenCommand
@ -265,7 +166,7 @@ func (c *OSCommand) OpenFile(filename string) error {
"filename": c.Quote(filename), "filename": c.Quote(filename),
} }
command := utils.ResolvePlaceholderString(commandTemplate, templateValues) command := utils.ResolvePlaceholderString(commandTemplate, templateValues)
err := c.RunShellCommand(command) err := c.Run(c.NewShellCmdObjFromString(command))
return err return err
} }
@ -278,26 +179,10 @@ func (c *OSCommand) OpenLink(link string) error {
} }
command := utils.ResolvePlaceholderString(commandTemplate, templateValues) command := utils.ResolvePlaceholderString(commandTemplate, templateValues)
err := c.RunShellCommand(command) err := c.Run(c.NewShellCmdObjFromString(command))
return err 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 // Quote wraps a message in platform-specific quotation marks
func (c *OSCommand) Quote(message string) string { func (c *OSCommand) Quote(message string) string {
var quote string var quote string
@ -390,24 +275,6 @@ func (c *OSCommand) FileExists(path string) (bool, error) {
return true, nil 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 // GetLazygitPath returns the path of the currently executed file
func (c *OSCommand) GetLazygitPath() string { func (c *OSCommand) GetLazygitPath() string {
ex, err := os.Executable() // get the executable path for git to use 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 += " | "
} }
logCmdStr += str logCmdStr += str
cmds[i] = c.ExecutableFromString(str) cmds[i] = c.NewCmdObj(str).GetCmd()
} }
c.LogCommand(logCmdStr, true) c.LogCommand(logCmdStr, true)
@ -489,7 +356,107 @@ func Kill(cmd *exec.Cmd) error {
return cmd.Process.Kill() 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() stdoutPipe, err := cmd.StdoutPipe()
if err != nil { if err != nil {
return err return err
@ -518,42 +485,15 @@ func RunLineOutputCmd(cmd *exec.Cmd, onLine func(line string) (bool, error)) err
return nil return nil
} }
func (c *OSCommand) CopyToClipboard(str string) error { func sanitisedCommandOutput(output []byte, err error) (string, error) {
escaped := strings.Replace(str, "\n", "\\n", -1) outputString := string(output)
truncated := utils.TruncateWithEllipsis(escaped, 40) if err != nil {
c.LogCommand(fmt.Sprintf("Copying '%s' to clipboard", truncated), false) // errors like 'exit status 1' are not very useful so we'll create an error
return clipboard.WriteAll(str) // from the combined output
} if outputString == "" {
return "", utils.WrapError(err)
func (c *OSCommand) RemoveFile(path string) error { }
c.LogCommand(fmt.Sprintf("Deleting path '%s'", path), false) return outputString, errors.New(outputString)
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,
} }
return outputString, nil
} }

View file

@ -8,8 +8,8 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
// TestOSCommandRunCommandWithOutput is a function. // TestOSCommandRunWithOutput is a function.
func TestOSCommandRunCommandWithOutput(t *testing.T) { func TestOSCommandRunWithOutput(t *testing.T) {
type scenario struct { type scenario struct {
command string command string
test func(string, error) test func(string, error)
@ -32,12 +32,13 @@ func TestOSCommandRunCommandWithOutput(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s.test(NewDummyOSCommand().RunCommandWithOutput(s.command)) c := NewDummyOSCommand()
s.test(NewDummyOSCommand().RunWithOutput(c.NewCmdObj(s.command)))
} }
} }
// TestOSCommandRunCommand is a function. // TestOSCommandRun is a function.
func TestOSCommandRunCommand(t *testing.T) { func TestOSCommandRun(t *testing.T) {
type scenario struct { type scenario struct {
command string command string
test func(error) test func(error)
@ -53,7 +54,8 @@ func TestOSCommandRunCommand(t *testing.T) {
} }
for _, s := range scenarios { for _, s := range scenarios {
s.test(NewDummyOSCommand().RunCommand(s.command)) c := NewDummyOSCommand()
s.test(c.Run(c.NewCmdObj(s.command)))
} }
} }

View file

@ -90,7 +90,7 @@ func (c *GitCommand) MovePatchToSelectedCommit(commits []*models.Commit, sourceC
return err return err
} }
if err := c.OSCommand.RunPreparedCommand(cmd); err != nil { if err := c.OSCommand.Run(cmd); err != nil {
return err return err
} }
@ -217,7 +217,7 @@ func (c *GitCommand) PullPatchIntoNewCommit(commits []*models.Commit, commitIdx
head_message, _ := c.GetHeadCommitMessage() head_message, _ := c.GetHeadCommitMessage()
new_message := fmt.Sprintf("Split from \"%s\"", head_message) 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 { if err != nil {
return err return err
} }

View file

@ -3,17 +3,15 @@ package commands
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os"
"os/exec"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/models" "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") todo, sha, err := c.GenerateGenericRebaseTodo(commits, index, "reword")
if err != nil { if err != nil {
return nil, err return nil, err
@ -40,7 +38,7 @@ func (c *GitCommand) MoveCommitDown(commits []*models.Commit, index int) error {
return err return err
} }
return c.OSCommand.RunPreparedCommand(cmd) return c.OSCommand.Run(cmd)
} }
func (c *GitCommand) InteractiveRebase(commits []*models.Commit, index int, action string) error { 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 err
} }
return c.OSCommand.RunPreparedCommand(cmd) return c.OSCommand.Run(cmd)
} }
// PrepareInteractiveRebaseCommand returns the cmd for an interactive rebase // PrepareInteractiveRebaseCommand returns the cmd for an interactive rebase
// we tell git to run lazygit to edit the todo list, and we pass the client // 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 // 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() ex := c.OSCommand.GetLazygitPath()
debug := "FALSE" 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) cmdStr := fmt.Sprintf("git rebase --interactive --autostash --keep-empty %s", baseSha)
c.Log.WithField("command", cmdStr).Info("RunCommand") 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 gitSequenceEditor := ex
if todo == "" { 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) c.OSCommand.LogCommand(fmt.Sprintf("Creating TODO file for interactive rebase: \n\n%s", todo), false)
} }
cmd.Env = os.Environ() cmdObj.AddEnvVars(
cmd.Env = append(
cmd.Env,
"LAZYGIT_CLIENT_COMMAND=INTERACTIVE_REBASE", "LAZYGIT_CLIENT_COMMAND=INTERACTIVE_REBASE",
"LAZYGIT_REBASE_TODO="+todo, "LAZYGIT_REBASE_TODO="+todo,
"DEBUG="+debug, "DEBUG="+debug,
@ -93,10 +88,10 @@ func (c *GitCommand) PrepareInteractiveRebaseCommand(baseSha string, todo string
) )
if overrideEditor { 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) { 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 return err
} }
if err := c.OSCommand.RunPreparedCommand(cmd); err != nil { if err := c.OSCommand.Run(cmd); err != nil {
return err return err
} }
@ -241,7 +236,7 @@ func (c *GitCommand) RebaseBranch(branchName string) error {
return err 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" // 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 { func (c *GitCommand) runSkipEditorCommand(command string) error {
cmd := c.OSCommand.ExecutableFromString(command) cmdObj := c.OSCommand.NewCmdObj(command)
lazyGitPath := c.OSCommand.GetLazygitPath() lazyGitPath := c.OSCommand.GetLazygitPath()
cmd.Env = append( cmdObj.AddEnvVars(
cmd.Env,
"LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY", "LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY",
"GIT_EDITOR="+lazyGitPath, "GIT_EDITOR="+lazyGitPath,
"EDITOR="+lazyGitPath, "EDITOR="+lazyGitPath,
"VISUAL="+lazyGitPath, "VISUAL="+lazyGitPath,
) )
return c.OSCommand.RunExecutable(cmd) return c.OSCommand.Run(cmdObj)
} }

View file

@ -5,6 +5,7 @@ import (
"regexp" "regexp"
"testing" "testing"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/test" "github.com/jesseduffield/lazygit/pkg/test"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -62,31 +63,31 @@ func TestGitCommandRebaseBranch(t *testing.T) {
func TestGitCommandSkipEditorCommand(t *testing.T) { func TestGitCommandSkipEditorCommand(t *testing.T) {
cmd := NewDummyGitCommand() cmd := NewDummyGitCommand()
cmd.OSCommand.SetBeforeExecuteCmd(func(cmd *exec.Cmd) { cmd.OSCommand.SetBeforeExecuteCmd(func(cmdObj oscommands.ICmdObj) {
test.AssertContainsMatch( test.AssertContainsMatch(
t, t,
cmd.Env, cmdObj.GetEnvVars(),
regexp.MustCompile("^VISUAL="), regexp.MustCompile("^VISUAL="),
"expected VISUAL to be set for a non-interactive external command", "expected VISUAL to be set for a non-interactive external command",
) )
test.AssertContainsMatch( test.AssertContainsMatch(
t, t,
cmd.Env, cmdObj.GetEnvVars(),
regexp.MustCompile("^EDITOR="), regexp.MustCompile("^EDITOR="),
"expected EDITOR to be set for a non-interactive external command", "expected EDITOR to be set for a non-interactive external command",
) )
test.AssertContainsMatch( test.AssertContainsMatch(
t, t,
cmd.Env, cmdObj.GetEnvVars(),
regexp.MustCompile("^GIT_EDITOR="), regexp.MustCompile("^GIT_EDITOR="),
"expected GIT_EDITOR to be set for a non-interactive external command", "expected GIT_EDITOR to be set for a non-interactive external command",
) )
test.AssertContainsMatch( test.AssertContainsMatch(
t, t,
cmd.Env, cmdObj.GetEnvVars(),
regexp.MustCompile("^LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY$"), regexp.MustCompile("^LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY$"),
"expected LAZYGIT_CLIENT_COMMAND to be set for a non-interactive external command", "expected LAZYGIT_CLIENT_COMMAND to be set for a non-interactive external command",
) )

View file

@ -7,24 +7,24 @@ import (
) )
func (c *GitCommand) AddRemote(name string, url string) error { 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 { 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 { 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 { 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 { 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)) 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) return c.DetectUnamePass(cmdObj, promptUserForCredential)
} }
@ -34,10 +34,10 @@ func (c *GitCommand) DetectUnamePass(cmdObj oscommands.ICmdObj, promptUserForCre
// CheckRemoteBranchExists Returns remote branch // CheckRemoteBranchExists Returns remote branch
func (c *GitCommand) CheckRemoteBranchExists(branchName string) bool { func (c *GitCommand) CheckRemoteBranchExists(branchName string) bool {
_, err := c.OSCommand.RunCommandWithOutput( _, err := c.RunWithOutput(c.NewCmdObj(
"git show-ref --verify -- refs/remotes/origin/%s", fmt.Sprintf("git show-ref --verify -- refs/remotes/origin/%s",
c.OSCommand.Quote(branchName), c.OSCommand.Quote(branchName),
) )))
return err == nil return err == nil
} }

View file

@ -4,13 +4,13 @@ import "fmt"
// StashDo modify stash // StashDo modify stash
func (c *GitCommand) StashDo(index int, method string) error { 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 // StashSave save stash
// TODO: before calling this, check if there is anything to save // TODO: before calling this, check if there is anything to save
func (c *GitCommand) StashSave(message string) error { 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 // 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 // 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 { func (c *GitCommand) StashSaveStagedChanges(message string) error {
// wrap in 'writing', which uses a mutex // 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 return err
} }
@ -30,7 +30,7 @@ func (c *GitCommand) StashSaveStagedChanges(message string) error {
return err 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 return err
} }
@ -38,7 +38,7 @@ func (c *GitCommand) StashSaveStagedChanges(message string) error {
return err 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 return err
} }

View file

@ -2,12 +2,14 @@ package commands
import ( import (
"bufio" "bufio"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings" "strings"
"github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
) )
// .gitmodules looks like this: // .gitmodules looks like this:
@ -69,28 +71,28 @@ func (c *GitCommand) SubmoduleStash(submodule *models.SubmoduleConfig) error {
return nil 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 { 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 { func (c *GitCommand) SubmoduleUpdateAll() error {
// not doing an --init here because the user probably doesn't want that // 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 { func (c *GitCommand) SubmoduleDelete(submodule *models.SubmoduleConfig) error {
// based on https://gist.github.com/myusuf3/7f645819ded92bda6677 // 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 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 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 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 // if the directory isn't there then that's fine
c.Log.Error(err) 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 { func (c *GitCommand) SubmoduleAdd(name string, path string, url string) error {
return c.OSCommand.RunCommand( return c.OSCommand.Run(
"git submodule add --force --name %s -- %s %s ", c.OSCommand.NewCmdObj(
c.OSCommand.Quote(name), fmt.Sprintf(
c.OSCommand.Quote(url), "git submodule add --force --name %s -- %s %s ",
c.OSCommand.Quote(path), c.OSCommand.Quote(name),
) c.OSCommand.Quote(url),
c.OSCommand.Quote(path),
)))
} }
func (c *GitCommand) SubmoduleUpdateUrl(name string, path string, newUrl string) error { 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 // 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 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 return err
} }
@ -131,27 +135,27 @@ func (c *GitCommand) SubmoduleUpdateUrl(name string, path string, newUrl string)
} }
func (c *GitCommand) SubmoduleInit(path string) error { 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 { 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 { func (c *GitCommand) SubmoduleBulkInitCmdObj() oscommands.ICmdObj {
return "git submodule init" return c.NewCmdObj("git submodule init")
} }
func (c *GitCommand) SubmoduleBulkUpdateCmdStr() string { func (c *GitCommand) SubmoduleBulkUpdateCmdObj() oscommands.ICmdObj {
return "git submodule update" return c.NewCmdObj("git submodule update")
} }
func (c *GitCommand) SubmoduleForceBulkUpdateCmdStr() string { func (c *GitCommand) SubmoduleForceBulkUpdateCmdObj() oscommands.ICmdObj {
return "git submodule update --force" return c.NewCmdObj("git submodule update --force")
} }
func (c *GitCommand) SubmoduleBulkDeinitCmdStr() string { func (c *GitCommand) SubmoduleBulkDeinitCmdObj() oscommands.ICmdObj {
return "git submodule deinit --all --force" return c.NewCmdObj("git submodule deinit --all --force")
} }
func (c *GitCommand) ResetSubmodules(submodules []*models.SubmoduleConfig) error { func (c *GitCommand) ResetSubmodules(submodules []*models.SubmoduleConfig) error {

View file

@ -37,7 +37,7 @@ func (c *GitCommand) Push(opts PushOpts) error {
cmdStr += " " + c.OSCommand.Quote(opts.UpstreamBranch) cmdStr += " " + c.OSCommand.Quote(opts.UpstreamBranch)
} }
cmdObj := c.NewCmdObjFromStr(cmdStr) cmdObj := c.NewCmdObj(cmdStr)
return c.DetectUnamePass(cmdObj, opts.PromptUserForCredential) 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)) 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 { return c.DetectUnamePass(cmdObj, func(question string) string {
if opts.PromptUserForCredential != nil { if opts.PromptUserForCredential != nil {
return opts.PromptUserForCredential(question) 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 // setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user
// has 'pull.rebase = interactive' configured. // 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) return c.DetectUnamePass(cmdObj, opts.PromptUserForCredential)
} }
func (c *GitCommand) FastForward(branchName string, remoteName string, remoteBranchName string, promptUserForCredential func(string) string) error { 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)) 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) return c.DetectUnamePass(cmdObj, promptUserForCredential)
} }
func (c *GitCommand) FetchRemote(remoteName string, promptUserForCredential func(string) string) error { func (c *GitCommand) FetchRemote(remoteName string, promptUserForCredential func(string) string) error {
cmdStr := fmt.Sprintf("git fetch %s", c.OSCommand.Quote(remoteName)) cmdStr := fmt.Sprintf("git fetch %s", c.OSCommand.Quote(remoteName))
cmdObj := c.NewCmdObjFromStr(cmdStr) cmdObj := c.NewCmdObj(cmdStr)
return c.DetectUnamePass(cmdObj, promptUserForCredential) return c.DetectUnamePass(cmdObj, promptUserForCredential)
} }

View file

@ -5,7 +5,7 @@ import (
) )
func (c *GitCommand) CreateLightweightTag(tagName string, commitSha string) error { 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 { 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 { 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 { 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)) 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) return c.DetectUnamePass(cmdObj, promptUserForCredential)
} }

View file

@ -32,11 +32,9 @@ func (gui *Gui) branchesRenderToMain() error {
if branch == nil { if branch == nil {
task = NewRenderStringTask(gui.Tr.NoBranchesThisRepo) task = NewRenderStringTask(gui.Tr.NoBranchesThisRepo)
} else { } else {
cmd := gui.OSCommand.ExecutableFromString( cmdObj := gui.GitCommand.GetBranchGraphCmdObj(branch.Name)
gui.GitCommand.GetBranchGraphCmdStr(branch.Name),
)
task = NewRunPtyTask(cmd) task = NewRunPtyTask(cmdObj.GetCmd())
} }
return gui.refreshMainViews(refreshMainOpts{ return gui.refreshMainViews(refreshMainOpts{

View file

@ -45,10 +45,8 @@ func (gui *Gui) commitFilesRenderToMain() error {
to := gui.State.CommitFileManager.GetParent() to := gui.State.CommitFileManager.GetParent()
from, reverse := gui.getFromAndReverseArgsForDiff(to) from, reverse := gui.getFromAndReverseArgsForDiff(to)
cmd := gui.OSCommand.ExecutableFromString( cmdObj := gui.GitCommand.ShowFileDiffCmdObj(from, to, reverse, node.GetPath(), false)
gui.GitCommand.ShowFileDiffCmdStr(from, to, reverse, node.GetPath(), false), task := NewRunPtyTask(cmdObj.GetCmd())
)
task := NewRunPtyTask(cmd)
return gui.refreshMainViews(refreshMainOpts{ return gui.refreshMainViews(refreshMainOpts{
main: &viewUpdateOpts{ main: &viewUpdateOpts{

View file

@ -24,10 +24,11 @@ func (gui *Gui) handleCommitConfirm() error {
flags = append(flags, "--signoff") flags = append(flags, "--signoff")
} }
cmdStr := gui.GitCommand.CommitCmdStr(message, strings.Join(flags, " ")) cmdObj := gui.GitCommand.CommitCmdObj(message, strings.Join(flags, " "))
gui.OnRunCommand(oscommands.NewCmdLogEntry(cmdStr, gui.Tr.Spans.Commit, true)) gui.OnRunCommand(oscommands.NewCmdLogEntry(cmdObj.ToString(), gui.Tr.Spans.Commit, true))
_ = gui.returnFromContext() _ = gui.returnFromContext()
return gui.withGpgHandling(cmdStr, gui.Tr.CommittingStatus, func() error { return gui.withGpgHandling(cmdObj, gui.Tr.CommittingStatus, func() error {
gui.Views.CommitMessage.ClearTextArea() gui.Views.CommitMessage.ClearTextArea()
return nil return nil
}) })

View file

@ -46,10 +46,8 @@ func (gui *Gui) branchCommitsRenderToMain() error {
if commit == nil { if commit == nil {
task = NewRenderStringTask(gui.Tr.NoCommitsThisBranch) task = NewRenderStringTask(gui.Tr.NoCommitsThisBranch)
} else { } else {
cmd := gui.OSCommand.ExecutableFromString( cmdObj := gui.GitCommand.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath())
gui.GitCommand.ShowCmdStr(commit.Sha, gui.State.Modes.Filtering.GetPath()), task = NewRunPtyTask(cmdObj.GetCmd())
)
task = NewRunPtyTask(cmd)
} }
return gui.refreshMainViews(refreshMainOpts{ return gui.refreshMainViews(refreshMainOpts{

View file

@ -203,7 +203,7 @@ func (gui *Gui) menuPromptFromCommand(prompt config.CustomCommandPrompt, promptR
} }
// Run and save output // Run and save output
message, err := gui.GitCommand.RunCommandWithOutput(cmdStr) message, err := gui.GitCommand.RunWithOutput(gui.GitCommand.NewCmdObj(cmdStr))
if err != nil { if err != nil {
return gui.surfaceError(err) return gui.surfaceError(err)
} }
@ -244,7 +244,7 @@ func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand
} }
if customCommand.Subprocess { if customCommand.Subprocess {
return gui.runSubprocessWithSuspenseAndRefresh(gui.OSCommand.PrepareShellSubProcess(cmdStr)) return gui.runSubprocessWithSuspenseAndRefresh(gui.OSCommand.NewShellCmdObjFromString2(cmdStr))
} }
loadingText := customCommand.LoadingText loadingText := customCommand.LoadingText
@ -252,7 +252,8 @@ func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand
loadingText = gui.Tr.LcRunningCustomCommandStatus loadingText = gui.Tr.LcRunningCustomCommandStatus
} }
return gui.WithWaitingStatus(loadingText, func() error { 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.surfaceError(err)
} }
return gui.refreshSidePanels(refreshOptions{}) return gui.refreshSidePanels(refreshOptions{})

View file

@ -13,10 +13,10 @@ func (gui *Gui) exitDiffMode() error {
} }
func (gui *Gui) renderDiff() 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()), fmt.Sprintf("git diff --submodule --no-ext-diff --color %s", gui.diffStr()),
) )
task := NewRunPtyTask(cmd) task := NewRunPtyTask(cmdObj.GetCmd())
return gui.refreshMainViews(refreshMainOpts{ return gui.refreshMainViews(refreshMainOpts{
main: &viewUpdateOpts{ main: &viewUpdateOpts{

View file

@ -58,22 +58,20 @@ func (gui *Gui) filesRenderToMain() error {
return gui.refreshMergePanelWithLock() return gui.refreshMergePanelWithLock()
} }
cmdStr := gui.GitCommand.WorktreeFileDiffCmdStr(node, false, !node.GetHasUnstagedChanges() && node.GetHasStagedChanges(), gui.State.IgnoreWhitespaceInDiffView) cmdObj := gui.GitCommand.WorktreeFileDiffCmdObj(node, false, !node.GetHasUnstagedChanges() && node.GetHasStagedChanges(), gui.State.IgnoreWhitespaceInDiffView)
cmd := gui.OSCommand.ExecutableFromString(cmdStr)
refreshOpts := refreshMainOpts{main: &viewUpdateOpts{ refreshOpts := refreshMainOpts{main: &viewUpdateOpts{
title: gui.Tr.UnstagedChanges, title: gui.Tr.UnstagedChanges,
task: NewRunPtyTask(cmd), task: NewRunPtyTask(cmdObj.GetCmd()),
}} }}
if node.GetHasUnstagedChanges() { if node.GetHasUnstagedChanges() {
if node.GetHasStagedChanges() { if node.GetHasStagedChanges() {
cmdStr := gui.GitCommand.WorktreeFileDiffCmdStr(node, false, true, gui.State.IgnoreWhitespaceInDiffView) cmdObj := gui.GitCommand.WorktreeFileDiffCmdObj(node, false, true, gui.State.IgnoreWhitespaceInDiffView)
cmd := gui.OSCommand.ExecutableFromString(cmdStr)
refreshOpts.secondary = &viewUpdateOpts{ refreshOpts.secondary = &viewUpdateOpts{
title: gui.Tr.StagedChanges, title: gui.Tr.StagedChanges,
task: NewRunPtyTask(cmd), task: NewRunPtyTask(cmdObj.GetCmd()),
} }
} }
} else { } else {
@ -440,9 +438,9 @@ func (gui *Gui) handleAmendCommitPress() error {
title: strings.Title(gui.Tr.AmendLastCommit), title: strings.Title(gui.Tr.AmendLastCommit),
prompt: gui.Tr.SureToAmend, prompt: gui.Tr.SureToAmend,
handleConfirm: func() error { handleConfirm: func() error {
cmdStr := gui.GitCommand.AmendHeadCmdStr() cmdObj := gui.GitCommand.AmendHeadCmdObj()
gui.OnRunCommand(oscommands.NewCmdLogEntry(cmdStr, gui.Tr.Spans.AmendCommit, true)) gui.OnRunCommand(oscommands.NewCmdLogEntry(cmdObj.ToString(), gui.Tr.Spans.AmendCommit, true))
return gui.withGpgHandling(cmdStr, gui.Tr.AmendingStatus, nil) return gui.withGpgHandling(cmdObj, gui.Tr.AmendingStatus, nil)
}, },
}) })
} }
@ -464,8 +462,10 @@ func (gui *Gui) handleCommitEditorPress() error {
args = append(args, "--signoff") args = append(args, "--signoff")
} }
cmdStr := "git " + strings.Join(args, " ")
return gui.runSubprocessWithSuspenseAndRefresh( 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( 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)) gui.OnRunCommand(oscommands.NewCmdLogEntry(command, gui.Tr.Spans.CustomCommand, true))
return gui.runSubprocessWithSuspenseAndRefresh( return gui.runSubprocessWithSuspenseAndRefresh(
gui.OSCommand.PrepareShellSubProcess(command), gui.OSCommand.NewShellCmdObjFromString2(command),
) )
}, },
}) })
@ -1004,7 +1004,7 @@ func (gui *Gui) handleOpenMergeTool() error {
prompt: gui.Tr.MergeToolPrompt, prompt: gui.Tr.MergeToolPrompt,
handleConfirm: func() error { handleConfirm: func() error {
return gui.runSubprocessWithSuspenseAndRefresh( return gui.runSubprocessWithSuspenseAndRefresh(
gui.OSCommand.ExecutableFromString(gui.GitCommand.OpenMergeToolCmd()), gui.GitCommand.OpenMergeToolCmdObj(),
) )
}, },
}) })

View file

@ -32,7 +32,7 @@ func (gui *Gui) gitFlowFinishBranch(gitFlowConfig string, branchName string) err
} }
return gui.runSubprocessWithSuspenseAndRefresh( 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 // 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 { if err != nil {
return gui.createErrorPanel("You need to install git-flow and enable it in this repo to use git-flow features") 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, title: title,
handleConfirm: func(name string) error { handleConfirm: func(name string) error {
return gui.runSubprocessWithSuspenseAndRefresh( 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),
) )
}, },
}) })

View file

@ -3,6 +3,7 @@ package gui
import ( import (
"fmt" "fmt"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/gui/style" "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 // 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 // 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. // 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() useSubprocess := gui.GitCommand.UsingGpg()
if useSubprocess { if useSubprocess {
// Need to remember why we use the shell for the subprocess but not in the other case success, err := gui.runSubprocessWithSuspense(gui.OSCommand.NewShellCmdObjFromString(cmdObj.ToString()))
// Maybe there's no good reason
success, err := gui.runSubprocessWithSuspense(gui.OSCommand.ShellCommandFromString(cmdStr))
if success && onSuccess != nil { if success && onSuccess != nil {
if err := onSuccess(); err != nil { if err := onSuccess(); err != nil {
return err return err
@ -27,15 +27,16 @@ func (gui *Gui) withGpgHandling(cmdStr string, waitingStatus string, onSuccess f
return err return err
} else { } 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 { return gui.WithWaitingStatus(waitingStatus, func() error {
cmd := gui.OSCommand.ShellCommandFromString(cmdStr) cmdObj := gui.OSCommand.NewShellCmdObjFromString(cmdObj.ToString())
cmd.Env = append(cmd.Env, "TERM=dumb") cmdObj.AddEnvVars("TERM=dumb")
cmdWriter := gui.getCmdWriter() cmdWriter := gui.getCmdWriter()
cmd := cmdObj.GetCmd()
cmd.Stdout = cmdWriter cmd.Stdout = cmdWriter
cmd.Stderr = cmdWriter cmd.Stderr = cmdWriter

View file

@ -7,7 +7,6 @@ import (
"os" "os"
"sync" "sync"
"os/exec"
"strings" "strings"
"time" "time"
@ -578,7 +577,7 @@ func (gui *Gui) RunAndHandleError() error {
} }
// returns whether command exited without error or not // 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) _, err := gui.runSubprocessWithSuspense(subprocess)
if err != nil { if err != nil {
return err return err
@ -592,7 +591,7 @@ func (gui *Gui) runSubprocessWithSuspenseAndRefresh(subprocess *exec.Cmd) error
} }
// returns whether command exited without error or not // 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() gui.Mutexes.SubprocessMutex.Lock()
defer gui.Mutexes.SubprocessMutex.Unlock() defer gui.Mutexes.SubprocessMutex.Unlock()
@ -621,7 +620,8 @@ func (gui *Gui) runSubprocessWithSuspense(subprocess *exec.Cmd) (bool, error) {
return cmdErr == nil, gui.surfaceError(cmdErr) 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.Stdout = os.Stdout
subprocess.Stderr = os.Stdout subprocess.Stderr = os.Stdout
subprocess.Stdin = os.Stdin subprocess.Stdin = os.Stdin

View file

@ -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 // 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 { 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 { if sub != nil {
return gui.runSubprocessWithSuspenseAndRefresh(sub) return gui.runSubprocessWithSuspenseAndRefresh(sub)
} }

View file

@ -38,10 +38,10 @@ func (gui *Gui) handleCreateRecentReposMenu() error {
} }
func (gui *Gui) handleShowAllBranchLogs() error { func (gui *Gui) handleShowAllBranchLogs() error {
cmd := gui.OSCommand.ExecutableFromString( cmdObj := gui.OSCommand.NewCmdObj(
gui.Config.GetUserConfig().Git.AllBranchesLogCmd, gui.Config.GetUserConfig().Git.AllBranchesLogCmd,
) )
task := NewRunPtyTask(cmd) task := NewRunPtyTask(cmdObj.GetCmd())
return gui.refreshMainViews(refreshMainOpts{ return gui.refreshMainViews(refreshMainOpts{
main: &viewUpdateOpts{ main: &viewUpdateOpts{

View file

@ -22,11 +22,9 @@ func (gui *Gui) reflogCommitsRenderToMain() error {
if commit == nil { if commit == nil {
task = NewRenderStringTask("No reflog history") task = NewRenderStringTask("No reflog history")
} else { } else {
cmd := gui.OSCommand.ExecutableFromString( cmdObj := gui.GitCommand.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath())
gui.GitCommand.ShowCmdStr(commit.Sha, gui.State.Modes.Filtering.GetPath()),
)
task = NewRunPtyTask(cmd) task = NewRunPtyTask(cmdObj.GetCmd())
} }
return gui.refreshMainViews(refreshMainOpts{ return gui.refreshMainViews(refreshMainOpts{

View file

@ -24,10 +24,8 @@ func (gui *Gui) remoteBranchesRenderToMain() error {
if remoteBranch == nil { if remoteBranch == nil {
task = NewRenderStringTask("No branches for this remote") task = NewRenderStringTask("No branches for this remote")
} else { } else {
cmd := gui.OSCommand.ExecutableFromString( cmdObj := gui.GitCommand.GetBranchGraphCmdObj(remoteBranch.FullName())
gui.GitCommand.GetBranchGraphCmdStr(remoteBranch.FullName()), task = NewRunCommandTask(cmdObj.GetCmd())
)
task = NewRunCommandTask(cmd)
} }
return gui.refreshMainViews(refreshMainOpts{ return gui.refreshMainViews(refreshMainOpts{

View file

@ -3,12 +3,11 @@ package gui
import ( import (
"fmt" "fmt"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/gui/style"
) )
func (gui *Gui) resetToRef(ref string, strength string, span string, options oscommands.RunCommandOptions) error { func (gui *Gui) resetToRef(ref string, strength string, span string, envVars []string) error {
if err := gui.GitCommand.WithSpan(span).ResetToCommit(ref, strength, options); err != nil { if err := gui.GitCommand.WithSpan(span).ResetToCommit(ref, strength, envVars); err != nil {
return gui.surfaceError(err) return gui.surfaceError(err)
} }
@ -39,7 +38,7 @@ func (gui *Gui) createResetMenu(ref string) error {
style.FgRed.Sprintf("reset --%s %s", strength, ref), style.FgRed.Sprintf("reset --%s %s", strength, ref),
}, },
onPress: func() error { onPress: func() error {
return gui.resetToRef(ref, strength, "Reset", oscommands.RunCommandOptions{}) return gui.resetToRef(ref, strength, "Reset", []string{})
}, },
} }
} }

View file

@ -22,10 +22,10 @@ func (gui *Gui) stashRenderToMain() error {
if stashEntry == nil { if stashEntry == nil {
task = NewRenderStringTask(gui.Tr.NoStashEntries) task = NewRenderStringTask(gui.Tr.NoStashEntries)
} else { } else {
cmd := gui.OSCommand.ExecutableFromString( cmdObj := gui.OSCommand.NewCmdObj(
gui.GitCommand.ShowStashEntryCmdStr(stashEntry.Index), gui.GitCommand.ShowStashEntryCmdStr(stashEntry.Index),
) )
task = NewRunPtyTask(cmd) task = NewRunPtyTask(cmdObj.GetCmd())
} }
return gui.refreshMainViews(refreshMainOpts{ return gui.refreshMainViews(refreshMainOpts{

View file

@ -23,11 +23,9 @@ func (gui *Gui) subCommitsRenderToMain() error {
if commit == nil { if commit == nil {
task = NewRenderStringTask("No commits") task = NewRenderStringTask("No commits")
} else { } else {
cmd := gui.OSCommand.ExecutableFromString( cmdObj := gui.GitCommand.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath())
gui.GitCommand.ShowCmdStr(commit.Sha, gui.State.Modes.Filtering.GetPath()),
)
task = NewRunPtyTask(cmd) task = NewRunPtyTask(cmdObj.GetCmd())
} }
return gui.refreshMainViews(refreshMainOpts{ return gui.refreshMainViews(refreshMainOpts{

View file

@ -36,9 +36,8 @@ func (gui *Gui) submodulesRenderToMain() error {
if file == nil { if file == nil {
task = NewRenderStringTask(prefix) task = NewRenderStringTask(prefix)
} else { } else {
cmdStr := gui.GitCommand.WorktreeFileDiffCmdStr(file, false, !file.HasUnstagedChanges && file.HasStagedChanges, gui.State.IgnoreWhitespaceInDiffView) cmdObj := gui.GitCommand.WorktreeFileDiffCmdObj(file, false, !file.HasUnstagedChanges && file.HasStagedChanges, gui.State.IgnoreWhitespaceInDiffView)
cmd := gui.OSCommand.ExecutableFromString(cmdStr) task = NewRunCommandTaskWithPrefix(cmdObj.GetCmd(), prefix)
task = NewRunCommandTaskWithPrefix(cmd, prefix)
} }
} }
@ -212,10 +211,10 @@ func (gui *Gui) handleResetRemoveSubmodule(submodule *models.SubmoduleConfig) er
func (gui *Gui) handleBulkSubmoduleActionsMenu() error { func (gui *Gui) handleBulkSubmoduleActionsMenu() error {
menuItems := []*menuItem{ 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 { onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.LcRunningCommand, 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) 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 { onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.LcRunningCommand, 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) 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 { onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.LcRunningCommand, 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 { 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 { onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.LcRunningCommand, 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) return gui.surfaceError(err)
} }

View file

@ -25,10 +25,8 @@ func (gui *Gui) tagsRenderToMain() error {
if tag == nil { if tag == nil {
task = NewRenderStringTask("No tags") task = NewRenderStringTask("No tags")
} else { } else {
cmd := gui.OSCommand.ExecutableFromString( cmdObj := gui.GitCommand.GetBranchGraphCmdObj(tag.Name)
gui.GitCommand.GetBranchGraphCmdStr(tag.Name), task = NewRunCommandTask(cmdObj.GetCmd())
)
task = NewRunCommandTask(cmd)
} }
return gui.refreshMainViews(refreshMainOpts{ return gui.refreshMainViews(refreshMainOpts{

View file

@ -2,7 +2,6 @@ package gui
import ( import (
"github.com/jesseduffield/lazygit/pkg/commands" "github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
) )
@ -169,7 +168,7 @@ func (gui *Gui) handleHardResetWithAutoStash(commitSha string, options handleHar
gitCommand := gui.GitCommand.WithSpan(options.span) gitCommand := gui.GitCommand.WithSpan(options.span)
reset := func() error { 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 gui.surfaceError(err)
} }
return nil return nil

View file

@ -45,7 +45,7 @@ func RunTests(
testDir := filepath.Join(rootDir, "test", "integration") testDir := filepath.Join(rootDir, "test", "integration")
osCommand := oscommands.NewDummyOSCommand() osCommand := oscommands.NewDummyOSCommand()
err = osCommand.RunCommand("go build -o %s", tempLazygitPath()) err = osCommand.Run(osCommand.NewCmdObj("go build -o " + tempLazygitPath()))
if err != nil { if err != nil {
return err return err
} }
@ -216,11 +216,10 @@ func GetRootDirectory() string {
} }
func createFixture(testPath, actualDir string) error { func createFixture(testPath, actualDir string) error {
osCommand := oscommands.NewDummyOSCommand()
bashScriptPath := filepath.Join(testPath, "setup.sh") bashScriptPath := filepath.Join(testPath, "setup.sh")
cmd := secureexec.Command("bash", bashScriptPath, actualDir) cmd := secureexec.Command("bash", bashScriptPath, actualDir)
if err := osCommand.RunExecutable(cmd); err != nil { if _, err := cmd.CombinedOutput(); err != nil {
return err return err
} }
@ -320,7 +319,7 @@ func generateSnapshot(dir string) (string, error) {
for _, cmdStr := range cmdStrs { for _, cmdStr := range cmdStrs {
// ignoring error for now. If there's an error it could be that there are no results // 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" 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) cmdStr := fmt.Sprintf("%s -debug --use-config-dir=%s --path=%s %s", tempLazygitPath(), configDir, actualDir, extraCmdArgs)
cmd := osCommand.ExecutableFromString(cmdStr) cmdObj := osCommand.NewCmdObj(cmdStr)
cmd.Env = append(cmd.Env, fmt.Sprintf("SPEED=%f", speed)) cmdObj.AddEnvVars(fmt.Sprintf("SPEED=%f", speed))
if record { if record {
cmd.Env = append( cmdObj.AddEnvVars(fmt.Sprintf("RECORD_EVENTS_TO=%s", replayPath))
cmd.Env,
fmt.Sprintf("RECORD_EVENTS_TO=%s", replayPath),
)
} else { } else {
cmd.Env = append( cmdObj.AddEnvVars(fmt.Sprintf("REPLAY_EVENTS_FROM=%s", replayPath))
cmd.Env,
fmt.Sprintf("REPLAY_EVENTS_FROM=%s", replayPath),
)
} }
return cmd, nil return cmdObj.GetCmd(), nil
} }
func folderExists(path string) bool { func folderExists(path string) bool {

View file

@ -300,7 +300,8 @@ func (u *Updater) downloadAndInstall(rawUrl string) error {
} }
u.Log.Info("untarring tarball/unzipping zip file") 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 return err
} }