1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-04-15 11:56:37 +02:00
This commit is contained in:
Jesse Duffield 2021-12-29 14:33:38 +11:00
parent 192a548c99
commit 43a4fa970d
41 changed files with 539 additions and 419 deletions

View File

@ -151,7 +151,7 @@ func NewApp(config config.AppConfigurer, filterPath string) (*App, error) {
} }
func (app *App) validateGitVersion() error { func (app *App) validateGitVersion() error {
output, err := app.OSCommand.RunWithOutput(app.OSCommand.NewCmdObj("git --version")) output, err := app.OSCommand.Cmd.New("git --version").RunWithOutput()
// 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 {
@ -236,7 +236,7 @@ func (app *App) setupRepo() (bool, error) {
os.Exit(1) os.Exit(1)
} }
if err := app.OSCommand.Run(app.OSCommand.NewCmdObj("git init")); err != nil { if err := app.OSCommand.Cmd.New("git init").Run(); 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.Run(c.NewCmdObj(fmt.Sprintf("git checkout -b %s %s", c.OSCommand.Quote(name), c.OSCommand.Quote(base)))) return c.Cmd.New(fmt.Sprintf("git checkout -b %s %s", c.OSCommand.Quote(name), c.OSCommand.Quote(base))).Run()
} }
// 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.RunWithOutput(c.NewCmdObj("git symbolic-ref --short HEAD")) branchName, err := c.Cmd.New("git symbolic-ref --short HEAD").RunWithOutput()
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.RunWithOutput(c.NewCmdObj("git branch --contains")) output, err := c.Cmd.New("git branch --contains").RunWithOutput()
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.Run(c.OSCommand.NewCmdObj(fmt.Sprintf("%s %s", command, c.OSCommand.Quote(branch)))) return c.Cmd.New(fmt.Sprintf("%s %s", command, c.OSCommand.Quote(branch))).Run()
} }
// 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
@ -62,24 +62,23 @@ func (c *GitCommand) Checkout(branch string, options CheckoutOptions) error {
forceArg = " --force" forceArg = " --force"
} }
cmdObj := c.NewCmdObj(fmt.Sprintf("git checkout%s %s", forceArg, c.OSCommand.Quote(branch))). return c.Cmd.New(fmt.Sprintf("git checkout%s %s", forceArg, c.OSCommand.Quote(branch))).
// prevents git from prompting us for input which would freeze the program // prevents git from prompting us for input which would freeze the program
// TODO: see if this is actually needed here // TODO: see if this is actually needed here
AddEnvVars("GIT_TERMINAL_PROMPT=0"). AddEnvVars("GIT_TERMINAL_PROMPT=0").
AddEnvVars(options.EnvVars...) AddEnvVars(options.EnvVars...).
Run()
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) {
return c.OSCommand.RunWithOutput(c.GetBranchGraphCmdObj(branchName)) return c.GetBranchGraphCmdObj(branchName).RunWithOutput()
} }
func (c *GitCommand) GetUpstreamForBranch(branchName string) (string, error) { func (c *GitCommand) GetUpstreamForBranch(branchName string) (string, error) {
output, err := c.RunWithOutput(c.NewCmdObj(fmt.Sprintf("git rev-parse --abbrev-ref --symbolic-full-name %s@{u}", c.OSCommand.Quote(branchName)))) output, err := c.Cmd.New(fmt.Sprintf("git rev-parse --abbrev-ref --symbolic-full-name %s@{u}", c.OSCommand.Quote(branchName))).RunWithOutput()
return strings.TrimSpace(output), err return strings.TrimSpace(output), err
} }
@ -88,15 +87,15 @@ func (c *GitCommand) GetBranchGraphCmdObj(branchName string) oscommands.ICmdObj
templateValues := map[string]string{ templateValues := map[string]string{
"branchName": c.OSCommand.Quote(branchName), "branchName": c.OSCommand.Quote(branchName),
} }
return c.NewCmdObj(utils.ResolvePlaceholderString(branchLogCmdTemplate, templateValues)) return c.Cmd.New(utils.ResolvePlaceholderString(branchLogCmdTemplate, templateValues))
} }
func (c *GitCommand) SetUpstreamBranch(upstream string) error { func (c *GitCommand) SetUpstreamBranch(upstream string) error {
return c.Run(c.NewCmdObj("git branch -u " + c.OSCommand.Quote(upstream))) return c.Cmd.New("git branch -u " + c.OSCommand.Quote(upstream)).Run()
} }
func (c *GitCommand) SetBranchUpstream(remoteName string, remoteBranchName string, branchName string) error { func (c *GitCommand) SetBranchUpstream(remoteName string, remoteBranchName string, branchName string) error {
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)))) return c.Cmd.New(fmt.Sprintf("git branch --set-upstream-to=%s/%s %s", c.OSCommand.Quote(remoteName), c.OSCommand.Quote(remoteBranchName), c.OSCommand.Quote(branchName))).Run()
} }
func (c *GitCommand) GetCurrentBranchUpstreamDifferenceCount() (string, string) { func (c *GitCommand) GetCurrentBranchUpstreamDifferenceCount() (string, string) {
@ -111,11 +110,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.RunWithOutput(c.NewCmdObj(fmt.Sprintf(command, to, from))) pushableCount, err := c.Cmd.New(fmt.Sprintf(command, to, from)).RunWithOutput()
if err != nil { if err != nil {
return "?", "?" return "?", "?"
} }
pullableCount, err := c.RunWithOutput(c.NewCmdObj(fmt.Sprintf(command, from, to))) pullableCount, err := c.Cmd.New(fmt.Sprintf(command, from, to)).RunWithOutput()
if err != nil { if err != nil {
return "?", "?" return "?", "?"
} }
@ -135,37 +134,37 @@ 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.Run(c.OSCommand.NewCmdObj(command)) return c.OSCommand.Cmd.New(command).Run()
} }
// AbortMerge abort merge // AbortMerge abort merge
func (c *GitCommand) AbortMerge() error { func (c *GitCommand) AbortMerge() error {
return c.Run(c.NewCmdObj("git merge --abort")) return c.Cmd.New("git merge --abort").Run()
} }
func (c *GitCommand) IsHeadDetached() bool { func (c *GitCommand) IsHeadDetached() bool {
err := c.Run(c.NewCmdObj("git symbolic-ref -q HEAD")) err := c.Cmd.New("git symbolic-ref -q HEAD").Run()
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.Run(c.NewCmdObj("git reset --hard " + c.OSCommand.Quote(ref))) return c.Cmd.New("git reset --hard " + c.OSCommand.Quote(ref)).Run()
} }
// 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.Run(c.NewCmdObj("git reset --soft " + c.OSCommand.Quote(ref))) return c.Cmd.New("git reset --soft " + c.OSCommand.Quote(ref)).Run()
} }
func (c *GitCommand) ResetMixed(ref string) error { func (c *GitCommand) ResetMixed(ref string) error {
return c.Run(c.NewCmdObj("git reset --mixed " + c.OSCommand.Quote(ref))) return c.Cmd.New("git reset --mixed " + c.OSCommand.Quote(ref)).Run()
} }
func (c *GitCommand) RenameBranch(oldName string, newName string) error { func (c *GitCommand) RenameBranch(oldName string, newName string) error {
return c.Run(c.NewCmdObj(fmt.Sprintf("git branch --move %s %s", c.OSCommand.Quote(oldName), c.OSCommand.Quote(newName)))) return c.Cmd.New(fmt.Sprintf("git branch --move %s %s", c.OSCommand.Quote(oldName), c.OSCommand.Quote(newName))).Run()
} }
func (c *GitCommand) GetRawBranches() (string, error) { func (c *GitCommand) GetRawBranches() (string, error) {
return c.RunWithOutput(c.NewCmdObj(`git for-each-ref --sort=-committerdate --format="%(HEAD)|%(refname:short)|%(upstream:short)|%(upstream:track)" refs/heads`)) return c.Cmd.New(`git for-each-ref --sort=-committerdate --format="%(HEAD)|%(refname:short)|%(upstream:short)|%(upstream:track)" refs/heads`).RunWithOutput()
} }

View File

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

View File

@ -10,18 +10,17 @@ 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.Run(c.NewCmdObj("git commit --allow-empty --amend --only -m " + c.OSCommand.Quote(name))) return c.Cmd.New("git commit --allow-empty --amend --only -m " + c.OSCommand.Quote(name)).Run()
} }
// ResetToCommit reset to commit // ResetToCommit reset to commit
func (c *GitCommand) ResetToCommit(sha string, strength string, envVars []string) error { func (c *GitCommand) ResetToCommit(sha string, strength string, envVars []string) error {
cmdObj := c.NewCmdObj(fmt.Sprintf("git reset --%s %s", strength, sha)). return c.Cmd.New(fmt.Sprintf("git reset --%s %s", strength, sha)).
// prevents git from prompting us for input which would freeze the program // prevents git from prompting us for input which would freeze the program
// TODO: see if this is actually needed here // TODO: see if this is actually needed here
AddEnvVars("GIT_TERMINAL_PROMPT=0"). AddEnvVars("GIT_TERMINAL_PROMPT=0").
AddEnvVars(envVars...) AddEnvVars(envVars...).
Run()
return c.OSCommand.Run(cmdObj)
} }
func (c *GitCommand) CommitCmdObj(message string, flags string) oscommands.ICmdObj { func (c *GitCommand) CommitCmdObj(message string, flags string) oscommands.ICmdObj {
@ -36,33 +35,33 @@ func (c *GitCommand) CommitCmdObj(message string, flags string) oscommands.ICmdO
flagsStr = fmt.Sprintf(" %s", flags) flagsStr = fmt.Sprintf(" %s", flags)
} }
return c.NewCmdObj(fmt.Sprintf("git commit%s%s", flagsStr, lineArgs)) return c.Cmd.New(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) {
message, err := c.RunWithOutput(c.NewCmdObj("git log -1 --pretty=%s")) message, err := c.Cmd.New("git log -1 --pretty=%s").RunWithOutput()
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.RunWithOutput(c.NewCmdObj(cmdStr)) messageWithHeader, err := c.Cmd.New(cmdStr).RunWithOutput()
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.RunWithOutput(c.NewCmdObj(fmt.Sprintf("git show --no-patch --pretty=format:%%s %s", sha))) return c.Cmd.New(fmt.Sprintf("git show --no-patch --pretty=format:%%s %s", sha)).RunWithOutput()
} }
// 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.Run(c.AmendHeadCmdObj()) return c.AmendHeadCmdObj().Run()
} }
func (c *GitCommand) AmendHeadCmdObj() oscommands.ICmdObj { func (c *GitCommand) AmendHeadCmdObj() oscommands.ICmdObj {
return c.NewCmdObj("git commit --amend --no-edit --allow-empty") return c.Cmd.New("git commit --amend --no-edit --allow-empty")
} }
func (c *GitCommand) ShowCmdObj(sha string, filterPath string) oscommands.ICmdObj { func (c *GitCommand) ShowCmdObj(sha string, filterPath string) oscommands.ICmdObj {
@ -73,16 +72,16 @@ func (c *GitCommand) ShowCmdObj(sha string, filterPath string) oscommands.ICmdOb
} }
cmdStr := 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) return c.Cmd.New(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.Run(c.NewCmdObj(fmt.Sprintf("git revert %s", sha))) return c.Cmd.New(fmt.Sprintf("git revert %s", sha)).Run()
} }
func (c *GitCommand) RevertMerge(sha string, parentNumber int) error { func (c *GitCommand) RevertMerge(sha string, parentNumber int) error {
return c.Run(c.NewCmdObj(fmt.Sprintf("git revert %s -m %d", sha, parentNumber))) return c.Cmd.New(fmt.Sprintf("git revert %s -m %d", sha, parentNumber)).Run()
} }
// 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
@ -92,15 +91,15 @@ func (c *GitCommand) CherryPickCommits(commits []*models.Commit) error {
todo = "pick " + commit.Sha + " " + commit.Name + "\n" + todo todo = "pick " + commit.Sha + " " + commit.Name + "\n" + todo
} }
cmd, err := c.PrepareInteractiveRebaseCommand("HEAD", todo, false) cmdObj, err := c.PrepareInteractiveRebaseCommand("HEAD", todo, false)
if err != nil { if err != nil {
return err return err
} }
return c.OSCommand.Run(cmd) return cmdObj.Run()
} }
// 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.Run(c.NewCmdObj(fmt.Sprintf("git commit --fixup=%s", sha))) return c.Cmd.New(fmt.Sprintf("git commit --fixup=%s", sha)).Run()
} }

View File

@ -25,26 +25,26 @@ func (c *GitCommand) CatFile(fileName string) (string, error) {
} }
func (c *GitCommand) OpenMergeToolCmdObj() oscommands.ICmdObj { func (c *GitCommand) OpenMergeToolCmdObj() oscommands.ICmdObj {
return c.NewCmdObj("git mergetool") return c.Cmd.New("git mergetool")
} }
func (c *GitCommand) OpenMergeTool() error { func (c *GitCommand) OpenMergeTool() error {
return c.Run(c.OpenMergeToolCmdObj()) return c.OpenMergeToolCmdObj().Run()
} }
// StageFile stages a file // StageFile stages a file
func (c *GitCommand) StageFile(fileName string) error { func (c *GitCommand) StageFile(fileName string) error {
return c.Run(c.NewCmdObj("git add -- " + c.OSCommand.Quote(fileName))) return c.Cmd.New("git add -- " + c.OSCommand.Quote(fileName)).Run()
} }
// StageAll stages all files // StageAll stages all files
func (c *GitCommand) StageAll() error { func (c *GitCommand) StageAll() error {
return c.Run(c.NewCmdObj("git add -A")) return c.Cmd.New("git add -A").Run()
} }
// UnstageAll unstages all files // UnstageAll unstages all files
func (c *GitCommand) UnstageAll() error { func (c *GitCommand) UnstageAll() error {
return c.Run(c.NewCmdObj("git reset")) return c.Cmd.New("git reset").Run()
} }
// UnStageFile unstages a file // UnStageFile unstages a file
@ -57,8 +57,8 @@ func (c *GitCommand) UnStageFile(fileNames []string, reset bool) error {
} }
for _, name := range fileNames { for _, name := range fileNames {
cmdObj := c.NewCmdObj(fmt.Sprintf(command, c.OSCommand.Quote(name))) err := c.Cmd.New(fmt.Sprintf(command, c.OSCommand.Quote(name))).Run()
if err := c.Run(cmdObj); err != nil { if err != nil {
return err return err
} }
} }
@ -122,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.Run(c.NewCmdObj("git checkout --ours -- " + quotedFileName)); err != nil { if err := c.Cmd.New("git checkout --ours -- " + quotedFileName).Run(); err != nil {
return err return err
} }
if err := c.Run(c.NewCmdObj("git add -- " + quotedFileName)); err != nil { if err := c.Cmd.New("git add -- " + quotedFileName).Run(); err != nil {
return err return err
} }
return nil return nil
} }
if file.ShortStatus == "DU" { if file.ShortStatus == "DU" {
return c.Run(c.NewCmdObj("git rm -- " + quotedFileName)) return c.Cmd.New("git rm -- " + quotedFileName).Run()
} }
// 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.Run(c.NewCmdObj("git reset -- " + quotedFileName)); err != nil { if err := c.Cmd.New("git reset -- " + quotedFileName).Run(); err != nil {
return err return err
} }
} }
@ -163,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.Run(c.NewCmdObj("git checkout -- " + quotedPath)); err != nil { if err := c.Cmd.New("git checkout -- " + quotedPath).Run(); err != nil {
return err return err
} }
@ -188,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.Run(c.NewCmdObj("git checkout -- " + quotedFileName)) return c.Cmd.New("git checkout -- " + quotedFileName).Run()
} }
// Ignore adds a file to the gitignore for the repo // Ignore adds a file to the gitignore for the repo
@ -199,7 +199,7 @@ 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.RunWithOutput(c.WorktreeFileDiffCmdObj(file, plain, cached, ignoreWhitespace)) s, _ := c.WorktreeFileDiffCmdObj(file, plain, cached, ignoreWhitespace).RunWithOutput()
return s return s
} }
@ -225,7 +225,7 @@ func (c *GitCommand) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cache
cmdStr := 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) return c.Cmd.New(cmdStr)
} }
func (c *GitCommand) ApplyPatch(patch string, flags ...string) error { func (c *GitCommand) ApplyPatch(patch string, flags ...string) error {
@ -240,14 +240,13 @@ func (c *GitCommand) ApplyPatch(patch string, flags ...string) error {
flagStr += " --" + flag flagStr += " --" + flag
} }
return c.Run(c.NewCmdObj(fmt.Sprintf("git apply %s %s", flagStr, c.OSCommand.Quote(filepath)))) return c.Cmd.New(fmt.Sprintf("git apply %s %s", flagStr, c.OSCommand.Quote(filepath))).Run()
} }
// 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) {
cmdObj := c.ShowFileDiffCmdObj(from, to, reverse, fileName, plain) return c.ShowFileDiffCmdObj(from, to, reverse, fileName, plain).RunWithOutput()
return c.RunWithOutput(cmdObj)
} }
func (c *GitCommand) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool) oscommands.ICmdObj { func (c *GitCommand) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool) oscommands.ICmdObj {
@ -262,12 +261,12 @@ func (c *GitCommand) ShowFileDiffCmdObj(from string, to string, reverse bool, fi
reverseFlag = " -R " reverseFlag = " -R "
} }
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))) return c.Cmd.New(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.Run(c.NewCmdObj(fmt.Sprintf("git checkout %s -- %s", commitSha, c.OSCommand.Quote(fileName)))) return c.Cmd.New(fmt.Sprintf("git checkout %s -- %s", commitSha, c.OSCommand.Quote(fileName))).Run()
} }
// DiscardOldFileChanges discards changes to a file from an old commit // DiscardOldFileChanges discards changes to a file from an old commit
@ -277,7 +276,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.Run(c.NewCmdObj("git cat-file -e HEAD^:" + c.OSCommand.Quote(fileName))); err != nil { if err := c.Cmd.New("git cat-file -e HEAD^:" + c.OSCommand.Quote(fileName)).Run(); err != nil {
if err := c.OSCommand.Remove(fileName); err != nil { if err := c.OSCommand.Remove(fileName); err != nil {
return err return err
} }
@ -300,17 +299,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.Run(c.NewCmdObj("git checkout -- .")) return c.Cmd.New("git checkout -- .").Run()
} }
// 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.Run(c.NewCmdObj("git rm -r --cached -- " + c.OSCommand.Quote(name))) return c.Cmd.New("git rm -r --cached -- " + c.OSCommand.Quote(name)).Run()
} }
// RemoveUntrackedFiles runs `git clean -fd` // RemoveUntrackedFiles runs `git clean -fd`
func (c *GitCommand) RemoveUntrackedFiles() error { func (c *GitCommand) RemoveUntrackedFiles() error {
return c.Run(c.NewCmdObj("git clean -fd")) return c.Cmd.New("git clean -fd").Run()
} }
// ResetAndClean removes all unstaged changes and removes all untracked files // ResetAndClean removes all unstaged changes and removes all untracked files
@ -350,7 +349,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.Run(c.NewCmdObj("which vi")); err == nil { if err := c.OSCommand.Cmd.New("which vi").Run(); err == nil {
editor = "vi" editor = "vi"
} }
} }

View File

@ -6,7 +6,6 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"time"
"github.com/go-errors/errors" "github.com/go-errors/errors"
@ -42,6 +41,8 @@ type GitCommand struct {
// Coincidentally at the moment it's the same view that OnRunCommand logs to // Coincidentally at the moment it's the same view that OnRunCommand logs to
// but that need not always be the case. // but that need not always be the case.
GetCmdWriter func() io.Writer GetCmdWriter func() io.Writer
Cmd oscommands.ICmdObjBuilder
} }
// NewGitCommand it runs git commands // NewGitCommand it runs git commands
@ -68,6 +69,8 @@ func NewGitCommand(
return nil, err return nil, err
} }
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd)
gitCommand := &GitCommand{ gitCommand := &GitCommand{
Common: cmn, Common: cmn,
OSCommand: osCommand, OSCommand: osCommand,
@ -76,6 +79,7 @@ func NewGitCommand(
PushToCurrent: pushToCurrent, PushToCurrent: pushToCurrent,
GitConfig: gitConfig, GitConfig: gitConfig,
GetCmdWriter: func() io.Writer { return ioutil.Discard }, GetCmdWriter: func() io.Writer { return ioutil.Discard },
Cmd: cmd,
} }
gitCommand.PatchManager = patch.NewPatchManager(gitCommand.Log, gitCommand.ApplyPatch, gitCommand.ShowFileDiff) gitCommand.PatchManager = patch.NewPatchManager(gitCommand.Log, gitCommand.ApplyPatch, gitCommand.ShowFileDiff)
@ -215,44 +219,5 @@ 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.Run(osCommand.NewCmdObj("git rev-parse --git-dir")) return osCommand.Cmd.New("git rev-parse --git-dir").Run()
}
func (c *GitCommand) Run(cmdObj oscommands.ICmdObj) error {
_, err := c.RunWithOutput(cmdObj)
return err
}
func (c *GitCommand) RunWithOutput(cmdObj oscommands.ICmdObj) (string, error) {
// TODO: have this retry logic in other places we run the command
waitTime := 50 * time.Millisecond
retryCount := 5
attempt := 0
for {
output, err := c.OSCommand.RunWithOutput(cmdObj)
if err != nil {
// if we have an error based on the index lock, we should wait a bit and then retry
if strings.Contains(output, ".git/index.lock") {
c.Log.Error(output)
c.Log.Info("index.lock prevented command from running. Retrying command after a small wait")
attempt++
time.Sleep(waitTime)
if attempt < retryCount {
continue
}
}
}
return output, err
}
}
func (c *GitCommand) NewCmdObj(cmdStr string) oscommands.ICmdObj {
return c.OSCommand.NewCmdObj(cmdStr).AddEnvVars("GIT_OPTIONAL_LOCKS=0")
}
func (c *GitCommand) NewCmdObjWithLog(cmdStr string) oscommands.ICmdObj {
cmdObj := c.NewCmdObj(cmdStr)
c.OSCommand.LogCmdObj(cmdObj)
return cmdObj
} }

View File

@ -0,0 +1,47 @@
package commands
import (
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/sirupsen/logrus"
)
// all we're doing here is wrapping the default command object builder with
// some git-specific stuff: e.g. adding a git-specific env var
type gitCmdObjBuilder struct {
innerBuilder *oscommands.CmdObjBuilder
}
var _ oscommands.ICmdObjBuilder = &gitCmdObjBuilder{}
func NewGitCmdObjBuilder(log *logrus.Entry, innerBuilder *oscommands.CmdObjBuilder) *gitCmdObjBuilder {
// the price of having a convenient interface where we can say .New(...).Run() is that our builder now depends on our runner, so when we want to wrap the default builder/runner in new functionality we need to jump through some hoops. We could avoid the use of a decorator function here by just exporting the runner field on the default builder but that would be misleading because we don't want anybody using that to run commands (i.e. we want there to be a single API used across the codebase)
updatedBuilder := innerBuilder.CloneWithNewRunner(func(runner oscommands.ICmdObjRunner) oscommands.ICmdObjRunner {
return &gitCmdObjRunner{
log: log,
innerRunner: runner,
}
})
return &gitCmdObjBuilder{
innerBuilder: updatedBuilder,
}
}
var defaultEnvVar = "GIT_OPTIONAL_LOCKS=0"
func (self *gitCmdObjBuilder) New(cmdStr string) oscommands.ICmdObj {
return self.innerBuilder.New(cmdStr).AddEnvVars(defaultEnvVar)
}
func (self *gitCmdObjBuilder) NewFromArgs(args []string) oscommands.ICmdObj {
return self.innerBuilder.NewFromArgs(args).AddEnvVars(defaultEnvVar)
}
func (self *gitCmdObjBuilder) NewShell(cmdStr string) oscommands.ICmdObj {
return self.innerBuilder.NewShell(cmdStr).AddEnvVars(defaultEnvVar)
}
func (self *gitCmdObjBuilder) Quote(str string) string {
return self.innerBuilder.Quote(str)
}

View File

@ -0,0 +1,49 @@
package commands
import (
"strings"
"time"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/sirupsen/logrus"
)
// here we're wrapping the default command runner in some git-specific stuff e.g. retry logic if we get an error due to the presence of .git/index.lock
type gitCmdObjRunner struct {
log *logrus.Entry
innerRunner oscommands.ICmdObjRunner
}
func (self *gitCmdObjRunner) Run(cmdObj oscommands.ICmdObj) error {
_, err := self.RunWithOutput(cmdObj)
return err
}
func (self *gitCmdObjRunner) RunWithOutput(cmdObj oscommands.ICmdObj) (string, error) {
// TODO: have this retry logic in other places we run the command
waitTime := 50 * time.Millisecond
retryCount := 5
attempt := 0
for {
output, err := self.innerRunner.RunWithOutput(cmdObj)
if err != nil {
// if we have an error based on the index lock, we should wait a bit and then retry
if strings.Contains(output, ".git/index.lock") {
self.log.Error(output)
self.log.Info("index.lock prevented command from running. Retrying command after a small wait")
attempt++
time.Sleep(waitTime)
if attempt < retryCount {
continue
}
}
}
return output, err
}
}
func (self *gitCmdObjRunner) RunLineOutputCmd(cmdObj oscommands.ICmdObj, onLine func(line string) (bool, error)) error {
return self.innerRunner.RunLineOutputCmd(cmdObj, onLine)
}

View File

@ -42,6 +42,51 @@ func NewBranchListBuilder(
} }
} }
// Build the list of branches for the current repo
func (b *BranchListBuilder) Build() []*models.Branch {
branches := b.obtainBranches()
reflogBranches := b.obtainReflogBranches()
// loop through reflog branches. If there is a match, merge them, then remove it from the branches and keep it in the reflog branches
branchesWithRecency := make([]*models.Branch, 0)
outer:
for _, reflogBranch := range reflogBranches {
for j, branch := range branches {
if branch.Head {
continue
}
if strings.EqualFold(reflogBranch.Name, branch.Name) {
branch.Recency = reflogBranch.Recency
branchesWithRecency = append(branchesWithRecency, branch)
branches = append(branches[0:j], branches[j+1:]...)
continue outer
}
}
}
branches = append(branchesWithRecency, branches...)
foundHead := false
for i, branch := range branches {
if branch.Head {
foundHead = true
branch.Recency = " *"
branches = append(branches[0:i], branches[i+1:]...)
branches = append([]*models.Branch{branch}, branches...)
break
}
}
if !foundHead {
currentBranchName, currentBranchDisplayName, err := b.getCurrentBranchName()
if err != nil {
panic(err)
}
branches = append([]*models.Branch{{Name: currentBranchName, DisplayName: currentBranchDisplayName, Head: true, Recency: " *"}}, branches...)
}
return branches
}
func (b *BranchListBuilder) obtainBranches() []*models.Branch { func (b *BranchListBuilder) obtainBranches() []*models.Branch {
output, err := b.getRawBranches() output, err := b.getRawBranches()
if err != nil { if err != nil {
@ -103,51 +148,6 @@ func (b *BranchListBuilder) obtainBranches() []*models.Branch {
return branches return branches
} }
// Build the list of branches for the current repo
func (b *BranchListBuilder) Build() []*models.Branch {
branches := b.obtainBranches()
reflogBranches := b.obtainReflogBranches()
// loop through reflog branches. If there is a match, merge them, then remove it from the branches and keep it in the reflog branches
branchesWithRecency := make([]*models.Branch, 0)
outer:
for _, reflogBranch := range reflogBranches {
for j, branch := range branches {
if branch.Head {
continue
}
if strings.EqualFold(reflogBranch.Name, branch.Name) {
branch.Recency = reflogBranch.Recency
branchesWithRecency = append(branchesWithRecency, branch)
branches = append(branches[0:j], branches[j+1:]...)
continue outer
}
}
}
branches = append(branchesWithRecency, branches...)
foundHead := false
for i, branch := range branches {
if branch.Head {
foundHead = true
branch.Recency = " *"
branches = append(branches[0:i], branches[i+1:]...)
branches = append([]*models.Branch{branch}, branches...)
break
}
}
if !foundHead {
currentBranchName, currentBranchDisplayName, err := b.getCurrentBranchName()
if err != nil {
panic(err)
}
branches = append([]*models.Branch{{Name: currentBranchName, DisplayName: currentBranchDisplayName, Head: true, Recency: " *"}}, branches...)
}
return branches
}
// TODO: only look at the new reflog commits, and otherwise store the recencies in // TODO: only look at the new reflog commits, and otherwise store the recencies in
// int form against the branch to recalculate the time ago // int form against the branch to recalculate the time ago
func (b *BranchListBuilder) obtainReflogBranches() []*models.Branch { func (b *BranchListBuilder) obtainReflogBranches() []*models.Branch {

View File

@ -14,7 +14,7 @@ func (c *GitCommand) GetFilesInDiff(from string, to string, reverse bool) ([]*mo
reverseFlag = " -R " reverseFlag = " -R "
} }
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))) filenames, err := c.Cmd.New(fmt.Sprintf("git diff --submodule --no-ext-diff --name-status -z --no-renames %s %s %s", reverseFlag, from, to)).RunWithOutput()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -11,9 +11,8 @@ import (
"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/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/sirupsen/logrus"
) )
// context: // context:
@ -29,24 +28,28 @@ const SEPARATION_CHAR = "|"
// CommitListBuilder returns a list of Branch objects for the current repo // CommitListBuilder returns a list of Branch objects for the current repo
type CommitListBuilder struct { type CommitListBuilder struct {
Log *logrus.Entry *common.Common
GitCommand *GitCommand cmd oscommands.ICmdObjBuilder
OSCommand *oscommands.OSCommand
Tr *i18n.TranslationSet getCurrentBranchName func() (string, string, error)
getRebaseMode func() (string, error)
readFile func(filename string) ([]byte, error)
dotGitDir string
} }
// NewCommitListBuilder builds a new commit list builder // NewCommitListBuilder builds a new commit list builder
func NewCommitListBuilder( func NewCommitListBuilder(
log *logrus.Entry, cmn *common.Common,
gitCommand *GitCommand, gitCommand *GitCommand,
osCommand *oscommands.OSCommand, osCommand *oscommands.OSCommand,
tr *i18n.TranslationSet,
) *CommitListBuilder { ) *CommitListBuilder {
return &CommitListBuilder{ return &CommitListBuilder{
Log: log, Common: cmn,
GitCommand: gitCommand, cmd: gitCommand.Cmd,
OSCommand: osCommand, getCurrentBranchName: gitCommand.CurrentBranchName,
Tr: tr, getRebaseMode: gitCommand.RebaseMode,
dotGitDir: gitCommand.DotGitDir,
readFile: ioutil.ReadFile,
} }
} }
@ -106,7 +109,7 @@ func (c *CommitListBuilder) MergeRebasingCommits(commits []*models.Commit) ([]*m
} }
} }
rebaseMode, err := c.GitCommand.RebaseMode() rebaseMode, err := c.getRebaseMode()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -131,7 +134,7 @@ func (c *CommitListBuilder) MergeRebasingCommits(commits []*models.Commit) ([]*m
func (c *CommitListBuilder) GetCommits(opts GetCommitsOptions) ([]*models.Commit, error) { func (c *CommitListBuilder) GetCommits(opts GetCommitsOptions) ([]*models.Commit, error) {
commits := []*models.Commit{} commits := []*models.Commit{}
var rebasingCommits []*models.Commit var rebasingCommits []*models.Commit
rebaseMode, err := c.GitCommand.RebaseMode() rebaseMode, err := c.getRebaseMode()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -152,9 +155,7 @@ func (c *CommitListBuilder) GetCommits(opts GetCommitsOptions) ([]*models.Commit
passedFirstPushedCommit = true passedFirstPushedCommit = true
} }
cmdObj := c.getLogCmd(opts) err = c.getLogCmd(opts).RunLineOutputCmd(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 {
@ -200,7 +201,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
cmdObj := c.OSCommand.NewCmdObj( cmdObj := c.cmd.New(
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, " "),
@ -211,7 +212,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 = c.OSCommand.RunLineOutputCmd(cmdObj, func(line string) (bool, error) { err = cmdObj.RunLineOutputCmd(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]
@ -242,7 +243,7 @@ func (c *CommitListBuilder) getRebasingCommits(rebaseMode string) ([]*models.Com
func (c *CommitListBuilder) getNormalRebasingCommits() ([]*models.Commit, error) { func (c *CommitListBuilder) getNormalRebasingCommits() ([]*models.Commit, error) {
rewrittenCount := 0 rewrittenCount := 0
bytesContent, err := ioutil.ReadFile(filepath.Join(c.GitCommand.DotGitDir, "rebase-apply/rewritten")) bytesContent, err := c.readFile(filepath.Join(c.dotGitDir, "rebase-apply/rewritten"))
if err == nil { if err == nil {
content := string(bytesContent) content := string(bytesContent)
rewrittenCount = len(strings.Split(content, "\n")) rewrittenCount = len(strings.Split(content, "\n"))
@ -250,7 +251,7 @@ func (c *CommitListBuilder) getNormalRebasingCommits() ([]*models.Commit, error)
// we know we're rebasing, so lets get all the files whose names have numbers // we know we're rebasing, so lets get all the files whose names have numbers
commits := []*models.Commit{} commits := []*models.Commit{}
err = filepath.Walk(filepath.Join(c.GitCommand.DotGitDir, "rebase-apply"), func(path string, f os.FileInfo, err error) error { err = filepath.Walk(filepath.Join(c.dotGitDir, "rebase-apply"), func(path string, f os.FileInfo, err error) error {
if rewrittenCount > 0 { if rewrittenCount > 0 {
rewrittenCount-- rewrittenCount--
return nil return nil
@ -262,7 +263,7 @@ func (c *CommitListBuilder) getNormalRebasingCommits() ([]*models.Commit, error)
if !re.MatchString(f.Name()) { if !re.MatchString(f.Name()) {
return nil return nil
} }
bytesContent, err := ioutil.ReadFile(path) bytesContent, err := c.readFile(path)
if err != nil { if err != nil {
return err return err
} }
@ -294,7 +295,7 @@ func (c *CommitListBuilder) getNormalRebasingCommits() ([]*models.Commit, error)
// and extracts out the sha and names of commits that we still have to go // and extracts out the sha and names of commits that we still have to go
// in the rebase: // in the rebase:
func (c *CommitListBuilder) getInteractiveRebasingCommits() ([]*models.Commit, error) { func (c *CommitListBuilder) getInteractiveRebasingCommits() ([]*models.Commit, error) {
bytesContent, err := ioutil.ReadFile(filepath.Join(c.GitCommand.DotGitDir, "rebase-merge/git-rebase-todo")) bytesContent, err := c.readFile(filepath.Join(c.dotGitDir, "rebase-merge/git-rebase-todo"))
if err != nil { if err != nil {
c.Log.Error(fmt.Sprintf("error occurred reading git-rebase-todo: %s", err.Error())) c.Log.Error(fmt.Sprintf("error occurred reading git-rebase-todo: %s", err.Error()))
// we assume an error means the file doesn't exist so we just return // we assume an error means the file doesn't exist so we just return
@ -362,7 +363,7 @@ func (c *CommitListBuilder) setCommitMergedStatuses(refName string, commits []*m
} }
func (c *CommitListBuilder) getMergeBase(refName string) (string, error) { func (c *CommitListBuilder) getMergeBase(refName string) (string, error) {
currentBranch, _, err := c.GitCommand.CurrentBranchName() currentBranch, _, err := c.getCurrentBranchName()
if err != nil { if err != nil {
return "", err return "", err
} }
@ -373,7 +374,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.RunWithOutput(c.OSCommand.NewCmdObj(fmt.Sprintf("git merge-base %s %s", c.OSCommand.Quote(refName), c.OSCommand.Quote(baseBranch)))) output, _ := c.cmd.New(fmt.Sprintf("git merge-base %s %s", c.cmd.Quote(refName), c.cmd.Quote(baseBranch))).RunWithOutput()
return ignoringWarnings(output), nil return ignoringWarnings(output), nil
} }
@ -390,7 +391,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.RunWithOutput(c.OSCommand.NewCmdObj(fmt.Sprintf("git merge-base %s %s@{u}", c.OSCommand.Quote(refName), c.OSCommand.Quote(refName)))) output, err := c.cmd.New(fmt.Sprintf("git merge-base %s %s@{u}", c.cmd.Quote(refName), c.cmd.Quote(refName))).RunWithOutput()
if err != nil { if err != nil {
return "", err return "", err
} }
@ -407,10 +408,10 @@ func (c *CommitListBuilder) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj
filterFlag := "" filterFlag := ""
if opts.FilterPath != "" { if opts.FilterPath != "" {
filterFlag = fmt.Sprintf(" --follow -- %s", c.OSCommand.Quote(opts.FilterPath)) filterFlag = fmt.Sprintf(" --follow -- %s", c.cmd.Quote(opts.FilterPath))
} }
config := c.GitCommand.UserConfig.Git.Log config := c.UserConfig.Git.Log
orderFlag := "--" + config.Order orderFlag := "--" + config.Order
allFlag := "" allFlag := ""
@ -418,10 +419,10 @@ func (c *CommitListBuilder) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj
allFlag = " --all" allFlag = " --all"
} }
return c.OSCommand.NewCmdObj( return c.cmd.New(
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.cmd.Quote(opts.RefName),
orderFlag, orderFlag,
allFlag, allFlag,
prettyFormat, prettyFormat,

View File

@ -80,7 +80,7 @@ func (c *GitCommand) GitStatus(opts GitStatusOptions) ([]FileStatus, error) {
noRenamesFlag = "--no-renames" noRenamesFlag = "--no-renames"
} }
statusLines, err := c.RunWithOutput(c.NewCmdObj(fmt.Sprintf("git status %s --porcelain -z %s", opts.UntrackedFilesArg, noRenamesFlag))) statusLines, err := c.Cmd.New(fmt.Sprintf("git status %s --porcelain -z %s", opts.UntrackedFilesArg, noRenamesFlag)).RunWithOutput()
if err != nil { if err != nil {
return []FileStatus{}, err return []FileStatus{}, err
} }

View File

@ -18,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))
} }
cmdObj := c.OSCommand.NewCmdObj(fmt.Sprintf(`git log -g --abbrev=20 --format="%%h %%ct %%gs" %s`, filterPathArg)) cmdObj := c.OSCommand.Cmd.New(fmt.Sprintf(`git log -g --abbrev=20 --format="%%h %%ct %%gs" %s`, filterPathArg))
onlyObtainedNewReflogCommits := false onlyObtainedNewReflogCommits := false
err := c.OSCommand.RunLineOutputCmd(cmdObj, func(line string) (bool, error) { err := cmdObj.RunLineOutputCmd(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,7 +10,7 @@ import (
) )
func (c *GitCommand) GetRemotes() ([]*models.Remote, error) { func (c *GitCommand) GetRemotes() ([]*models.Remote, error) {
remoteBranchesStr, err := c.RunWithOutput(c.NewCmdObj("git branch -r")) remoteBranchesStr, err := c.Cmd.New("git branch -r").RunWithOutput()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -10,7 +10,7 @@ import (
) )
func (c *GitCommand) getUnfilteredStashEntries() []*models.StashEntry { func (c *GitCommand) getUnfilteredStashEntries() []*models.StashEntry {
rawString, _ := c.RunWithOutput(c.NewCmdObj("git stash list --pretty='%gs'")) rawString, _ := c.Cmd.New("git stash list --pretty='%gs'").RunWithOutput()
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))
@ -24,7 +24,7 @@ func (c *GitCommand) GetStashEntries(filterPath string) []*models.StashEntry {
return c.getUnfilteredStashEntries() return c.getUnfilteredStashEntries()
} }
rawString, err := c.RunWithOutput(c.NewCmdObj("git stash list --name-only")) rawString, err := c.Cmd.New("git stash list --name-only").RunWithOutput()
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.RunWithOutput(c.NewCmdObj(`git tag --list --sort=-creatordate`)) remoteBranchesStr, err := c.Cmd.New(`git tag --list --sort=-creatordate`).RunWithOutput()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -5,18 +5,27 @@ import (
) )
// A command object is a general way to represent a command to be run on the // A command object is a general way to represent a command to be run on the
// command line. If you want to log the command you'll use .ToString() and // command line.
// if you want to run it you'll use .GetCmd()
type ICmdObj interface { type ICmdObj interface {
GetCmd() *exec.Cmd GetCmd() *exec.Cmd
ToString() string ToString() string
AddEnvVars(...string) ICmdObj AddEnvVars(...string) ICmdObj
GetEnvVars() []string GetEnvVars() []string
Run() error
RunWithOutput() (string, error)
RunLineOutputCmd(onLine func(line string) (bool, error)) error
// logs command
Log() ICmdObj
} }
type CmdObj struct { type CmdObj struct {
cmdStr string cmdStr string
cmd *exec.Cmd cmd *exec.Cmd
runner ICmdObjRunner
logCommand func(ICmdObj)
} }
func (self *CmdObj) GetCmd() *exec.Cmd { func (self *CmdObj) GetCmd() *exec.Cmd {
@ -36,3 +45,21 @@ func (self *CmdObj) AddEnvVars(vars ...string) ICmdObj {
func (self *CmdObj) GetEnvVars() []string { func (self *CmdObj) GetEnvVars() []string {
return self.cmd.Env return self.cmd.Env
} }
func (self *CmdObj) Log() ICmdObj {
self.logCommand(self)
return self
}
func (self *CmdObj) Run() error {
return self.runner.Run(self)
}
func (self *CmdObj) RunWithOutput() (string, error) {
return self.runner.RunWithOutput(self)
}
func (self *CmdObj) RunLineOutputCmd(onLine func(line string) (bool, error)) error {
return self.runner.RunLineOutputCmd(self, onLine)
}

View File

@ -0,0 +1,72 @@
package oscommands
import (
"os"
"os/exec"
"strings"
"github.com/mgutz/str"
)
type ICmdObjBuilder interface {
// New returns a new command object based on the string provided
New(cmdStr string) ICmdObj
// NewShell takes a string like `git commit` and returns an executable shell command for it e.g. `sh -c 'git commit'`
NewShell(commandStr string) ICmdObj
// NewFromArgs takes a slice of strings like []string{"git", "commit"} and returns a new command object. This can be useful when you don't want to worry about whitespace and quoting and stuff.
NewFromArgs(args []string) ICmdObj
// Quote wraps a string in quotes with any necessary escaping applied. The reason for bundling this up with the other methods in this interface is that we basically always need to make use of this when creating new command objects.
Quote(str string) string
}
// poor man's version of explicitly saying that struct X implements interface Y
var _ ICmdObjBuilder = &CmdObjBuilder{}
type CmdObjBuilder struct {
runner ICmdObjRunner
logCmdObj func(ICmdObj)
// TODO: see if you can just remove this entirely and use secureexec.Command,
// now that we're mocking out the runner itself.
command func(string, ...string) *exec.Cmd
platform *Platform
}
func (self *CmdObjBuilder) New(cmdStr string) ICmdObj {
args := str.ToArgv(cmdStr)
cmd := self.command(args[0], args[1:]...)
cmd.Env = os.Environ()
return &CmdObj{
cmdStr: cmdStr,
cmd: cmd,
runner: self.runner,
logCommand: self.logCmdObj,
}
}
func (self *CmdObjBuilder) NewFromArgs(args []string) ICmdObj {
cmd := self.command(args[0], args[1:]...)
cmd.Env = os.Environ()
return &CmdObj{
cmdStr: strings.Join(args, " "),
cmd: cmd,
runner: self.runner,
logCommand: self.logCmdObj,
}
}
func (self *CmdObjBuilder) NewShell(commandStr string) ICmdObj {
return self.NewFromArgs([]string{self.platform.Shell, self.platform.ShellArg, commandStr})
}
func (self *CmdObjBuilder) CloneWithNewRunner(decorate func(ICmdObjRunner) ICmdObjRunner) *CmdObjBuilder {
decoratedRunner := decorate(self.runner)
return &CmdObjBuilder{
runner: decoratedRunner,
logCmdObj: self.logCmdObj,
command: self.command,
platform: self.platform,
}
}

View File

@ -0,0 +1,79 @@
package oscommands
import (
"bufio"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sirupsen/logrus"
)
type ICmdObjRunner 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 RealRunner struct {
log *logrus.Entry
logCmdObj func(ICmdObj)
}
func (self *RealRunner) Run(cmdObj ICmdObj) error {
_, err := self.RunWithOutput(cmdObj)
return err
}
func (self *RealRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
self.logCmdObj(cmdObj)
output, err := sanitisedCommandOutput(cmdObj.GetCmd().CombinedOutput())
if err != nil {
self.log.WithField("command", cmdObj.ToString()).Error(output)
}
return output, err
}
func (self *RealRunner) RunLineOutputCmd(cmdObj ICmdObj, onLine func(line string) (bool, error)) error {
cmd := cmdObj.GetCmd()
stdoutPipe, err := cmd.StdoutPipe()
if err != nil {
return err
}
scanner := bufio.NewScanner(stdoutPipe)
scanner.Split(bufio.ScanLines)
if err := cmd.Start(); err != nil {
return err
}
for scanner.Scan() {
line := scanner.Text()
stop, err := onLine(line)
if err != nil {
return err
}
if stop {
_ = cmd.Process.Kill()
break
}
}
_ = cmd.Wait()
return nil
}
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
}

View File

@ -1,7 +1,6 @@
package oscommands package oscommands
import ( import (
"bufio"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@ -16,7 +15,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/common" "github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/secureexec" "github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/mgutz/str"
) )
// Platform stores the os state // Platform stores the os state
@ -55,7 +53,7 @@ type OSCommand struct {
removeFile func(string) error removeFile func(string) error
IRunner Cmd *CmdObjBuilder
} }
// TODO: make these fields private // TODO: make these fields private
@ -90,15 +88,20 @@ func NewCmdLogEntry(cmdStr string, span string, commandLine bool) CmdLogEntry {
// NewOSCommand os command runner // NewOSCommand os command runner
func NewOSCommand(common *common.Common) *OSCommand { func NewOSCommand(common *common.Common) *OSCommand {
command := secureexec.Command
platform := getPlatform()
c := &OSCommand{ c := &OSCommand{
Common: common, Common: common,
Platform: getPlatform(), Platform: platform,
Command: secureexec.Command, Command: command,
Getenv: os.Getenv, Getenv: os.Getenv,
removeFile: os.RemoveAll, removeFile: os.RemoveAll,
} }
c.IRunner = &RealRunner{c: c} runner := &RealRunner{log: common.Log, logCmdObj: c.LogCmdObj}
c.Cmd = &CmdObjBuilder{runner: runner, command: command, logCmdObj: c.LogCmdObj, platform: platform}
return c return c
} }
@ -162,8 +165,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.Run(c.NewShellCmdObjFromString(command)) return c.Cmd.NewShell(command).Run()
return err
} }
// OpenLink opens a file with the given // OpenLink opens a file with the given
@ -175,14 +177,17 @@ func (c *OSCommand) OpenLink(link string) error {
} }
command := utils.ResolvePlaceholderString(commandTemplate, templateValues) command := utils.ResolvePlaceholderString(commandTemplate, templateValues)
err := c.Run(c.NewShellCmdObjFromString(command)) return c.Cmd.NewShell(command).Run()
return err
} }
// 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 {
return c.Cmd.Quote(message)
}
func (self *CmdObjBuilder) Quote(message string) string {
var quote string var quote string
if c.Platform.OS == "windows" { if self.platform.OS == "windows" {
quote = `\"` quote = `\"`
message = strings.NewReplacer( message = strings.NewReplacer(
`"`, `"'"'"`, `"`, `"'"'"`,
@ -289,7 +294,7 @@ func (c *OSCommand) PipeCommands(commandStrings ...string) error {
logCmdStr += " | " logCmdStr += " | "
} }
logCmdStr += str logCmdStr += str
cmds[i] = c.NewCmdObj(str).GetCmd() cmds[i] = c.Cmd.New(str).GetCmd()
} }
c.LogCommand(logCmdStr, true) c.LogCommand(logCmdStr, true)
@ -365,127 +370,6 @@ func (c *OSCommand) RemoveFile(path string) error {
return c.removeFile(path) 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 RealRunner struct {
c *OSCommand
}
func (self *RealRunner) Run(cmdObj ICmdObj) error {
_, err := self.RunWithOutput(cmdObj)
return err
}
func (self *RealRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
self.c.LogCmdObj(cmdObj)
output, err := sanitisedCommandOutput(cmdObj.GetCmd().CombinedOutput())
if err != nil {
self.c.Log.WithField("command", cmdObj.ToString()).Error(output)
}
return output, err
}
func (self *RealRunner) RunLineOutputCmd(cmdObj ICmdObj, onLine func(line string) (bool, error)) error {
cmd := cmdObj.GetCmd()
stdoutPipe, err := cmd.StdoutPipe()
if err != nil {
return err
}
scanner := bufio.NewScanner(stdoutPipe)
scanner.Split(bufio.ScanLines)
if err := cmd.Start(); err != nil {
return err
}
for scanner.Scan() {
line := scanner.Text()
stop, err := onLine(line)
if err != nil {
return err
}
if stop {
_ = cmd.Process.Kill()
break
}
}
_ = cmd.Wait()
return nil
}
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
}
func GetTempDir() string { func GetTempDir() string {
return filepath.Join(os.TempDir(), "lazygit") return filepath.Join(os.TempDir(), "lazygit")
} }

View File

@ -33,7 +33,7 @@ func TestOSCommandRunWithOutput(t *testing.T) {
for _, s := range scenarios { for _, s := range scenarios {
c := NewDummyOSCommand() c := NewDummyOSCommand()
s.test(NewDummyOSCommand().RunWithOutput(c.NewCmdObj(s.command))) s.test(c.Cmd.New(s.command).RunWithOutput())
} }
} }
@ -55,7 +55,7 @@ func TestOSCommandRun(t *testing.T) {
for _, s := range scenarios { for _, s := range scenarios {
c := NewDummyOSCommand() c := NewDummyOSCommand()
s.test(c.Run(c.NewCmdObj(s.command))) s.test(c.Cmd.New(s.command)).Run()
} }
} }

View File

@ -85,12 +85,12 @@ func (c *GitCommand) MovePatchToSelectedCommit(commits []*models.Commit, sourceC
todo = a + " " + commit.Sha + " " + commit.Name + "\n" + todo todo = a + " " + commit.Sha + " " + commit.Name + "\n" + todo
} }
cmd, err := c.PrepareInteractiveRebaseCommand(commits[baseIndex].Sha, todo, true) cmdObj, err := c.PrepareInteractiveRebaseCommand(commits[baseIndex].Sha, todo, true)
if err != nil { if err != nil {
return err return err
} }
if err := c.OSCommand.Run(cmd); err != nil { if err := cmdObj.Run(); 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.Run(c.CommitCmdObj(new_message, "")) err := c.CommitCmdObj(new_message, "").Run()
if err != nil { if err != nil {
return err return err
} }

View File

@ -33,12 +33,12 @@ func (c *GitCommand) MoveCommitDown(commits []*models.Commit, index int) error {
todo = "pick " + commit.Sha + " " + commit.Name + "\n" + todo todo = "pick " + commit.Sha + " " + commit.Name + "\n" + todo
} }
cmd, err := c.PrepareInteractiveRebaseCommand(commits[index+2].Sha, todo, true) cmdObj, err := c.PrepareInteractiveRebaseCommand(commits[index+2].Sha, todo, true)
if err != nil { if err != nil {
return err return err
} }
return c.OSCommand.Run(cmd) return cmdObj.Run()
} }
func (c *GitCommand) InteractiveRebase(commits []*models.Commit, index int, action string) error { func (c *GitCommand) InteractiveRebase(commits []*models.Commit, index int, action string) error {
@ -47,12 +47,12 @@ func (c *GitCommand) InteractiveRebase(commits []*models.Commit, index int, acti
return err return err
} }
cmd, err := c.PrepareInteractiveRebaseCommand(sha, todo, true) cmdObj, err := c.PrepareInteractiveRebaseCommand(sha, todo, true)
if err != nil { if err != nil {
return err return err
} }
return c.OSCommand.Run(cmd) return cmdObj.Run()
} }
// PrepareInteractiveRebaseCommand returns the cmd for an interactive rebase // PrepareInteractiveRebaseCommand returns the cmd for an interactive rebase
@ -69,7 +69,7 @@ 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")
cmdObj := c.NewCmdObj(cmdStr) cmdObj := c.Cmd.New(cmdStr)
gitSequenceEditor := ex gitSequenceEditor := ex
if todo == "" { if todo == "" {
@ -217,26 +217,22 @@ func (c *GitCommand) BeginInteractiveRebaseForCommit(commits []*models.Commit, c
return err return err
} }
cmd, err := c.PrepareInteractiveRebaseCommand(sha, todo, true) cmdObj, err := c.PrepareInteractiveRebaseCommand(sha, todo, true)
if err != nil { if err != nil {
return err return err
} }
if err := c.OSCommand.Run(cmd); err != nil { return cmdObj.Run()
return err
}
return nil
} }
// RebaseBranch interactive rebases onto a branch // RebaseBranch interactive rebases onto a branch
func (c *GitCommand) RebaseBranch(branchName string) error { func (c *GitCommand) RebaseBranch(branchName string) error {
cmd, err := c.PrepareInteractiveRebaseCommand(branchName, "", false) cmdObj, err := c.PrepareInteractiveRebaseCommand(branchName, "", false)
if err != nil { if err != nil {
return err return err
} }
return c.OSCommand.Run(cmd) return cmdObj.Run()
} }
// 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"
@ -271,13 +267,14 @@ func (c *GitCommand) GenericMergeOrRebaseAction(commandType string, command stri
} }
func (c *GitCommand) runSkipEditorCommand(command string) error { func (c *GitCommand) runSkipEditorCommand(command string) error {
cmdObj := c.OSCommand.NewCmdObj(command) cmdObj := c.OSCommand.Cmd.New(command)
lazyGitPath := c.OSCommand.GetLazygitPath() lazyGitPath := c.OSCommand.GetLazygitPath()
cmdObj.AddEnvVars( return cmdObj.
"LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY", AddEnvVars(
"GIT_EDITOR="+lazyGitPath, "LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY",
"EDITOR="+lazyGitPath, "GIT_EDITOR="+lazyGitPath,
"VISUAL="+lazyGitPath, "EDITOR="+lazyGitPath,
) "VISUAL="+lazyGitPath,
return c.OSCommand.Run(cmdObj) ).
Run()
} }

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.Run(c.NewCmdObj(fmt.Sprintf("git remote add %s %s", c.OSCommand.Quote(name), c.OSCommand.Quote(url)))) return c.Cmd.New(fmt.Sprintf("git remote add %s %s", c.OSCommand.Quote(name), c.OSCommand.Quote(url))).Run()
} }
func (c *GitCommand) RemoveRemote(name string) error { func (c *GitCommand) RemoveRemote(name string) error {
return c.Run(c.NewCmdObj(fmt.Sprintf("git remote remove %s", c.OSCommand.Quote(name)))) return c.Cmd.New(fmt.Sprintf("git remote remove %s", c.OSCommand.Quote(name))).Run()
} }
func (c *GitCommand) RenameRemote(oldRemoteName string, newRemoteName string) error { func (c *GitCommand) RenameRemote(oldRemoteName string, newRemoteName string) error {
return c.Run(c.NewCmdObj(fmt.Sprintf("git remote rename %s %s", c.OSCommand.Quote(oldRemoteName), c.OSCommand.Quote(newRemoteName)))) return c.Cmd.New(fmt.Sprintf("git remote rename %s %s", c.OSCommand.Quote(oldRemoteName), c.OSCommand.Quote(newRemoteName))).Run()
} }
func (c *GitCommand) UpdateRemoteUrl(remoteName string, updatedUrl string) error { func (c *GitCommand) UpdateRemoteUrl(remoteName string, updatedUrl string) error {
return c.Run(c.NewCmdObj(fmt.Sprintf("git remote set-url %s %s", c.OSCommand.Quote(remoteName), c.OSCommand.Quote(updatedUrl)))) return c.Cmd.New(fmt.Sprintf("git remote set-url %s %s", c.OSCommand.Quote(remoteName), c.OSCommand.Quote(updatedUrl))).Run()
} }
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.NewCmdObj(command) cmdObj := c.Cmd.New(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.RunWithOutput(c.NewCmdObj( _, err := c.Cmd.New(
fmt.Sprintf("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),
))) )).RunWithOutput()
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.Run(c.NewCmdObj(fmt.Sprintf("git stash %s stash@{%d}", method, index))) return c.Cmd.New(fmt.Sprintf("git stash %s stash@{%d}", method, index)).Run()
} }
// 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.Run(c.NewCmdObj("git stash save " + c.OSCommand.Quote(message))) return c.Cmd.New("git stash save " + c.OSCommand.Quote(message)).Run()
} }
// 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.Run(c.NewCmdObj("git stash --keep-index")); err != nil { if err := c.Cmd.New("git stash --keep-index").Run(); err != nil {
return err return err
} }
@ -30,7 +30,7 @@ func (c *GitCommand) StashSaveStagedChanges(message string) error {
return err return err
} }
if err := c.Run(c.NewCmdObj("git stash apply stash@{1}")); err != nil { if err := c.Cmd.New("git stash apply stash@{1}").Run(); err != nil {
return err return err
} }
@ -38,7 +38,7 @@ func (c *GitCommand) StashSaveStagedChanges(message string) error {
return err return err
} }
if err := c.Run(c.NewCmdObj("git stash drop stash@{1}")); err != nil { if err := c.Cmd.New("git stash drop stash@{1}").Run(); err != nil {
return err return err
} }

View File

@ -71,28 +71,28 @@ func (c *GitCommand) SubmoduleStash(submodule *models.SubmoduleConfig) error {
return nil return nil
} }
return c.Run(c.NewCmdObj("git -C " + c.OSCommand.Quote(submodule.Path) + " stash --include-untracked")) return c.Cmd.New("git -C " + c.OSCommand.Quote(submodule.Path) + " stash --include-untracked").Run()
} }
func (c *GitCommand) SubmoduleReset(submodule *models.SubmoduleConfig) error { func (c *GitCommand) SubmoduleReset(submodule *models.SubmoduleConfig) error {
return c.Run(c.NewCmdObj("git submodule update --init --force -- " + c.OSCommand.Quote(submodule.Path))) return c.Cmd.New("git submodule update --init --force -- " + c.OSCommand.Quote(submodule.Path)).Run()
} }
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.Run(c.NewCmdObj("git submodule update --force")) return c.Cmd.New("git submodule update --force").Run()
} }
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.Run(c.NewCmdObj("git submodule deinit --force -- " + c.OSCommand.Quote(submodule.Path))); err != nil { if err := c.Cmd.New("git submodule deinit --force -- " + c.OSCommand.Quote(submodule.Path)).Run(); 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.Run(c.NewCmdObj("git config --file .gitmodules --remove-section submodule." + c.OSCommand.Quote(submodule.Name))); err != nil { if err := c.Cmd.New("git config --file .gitmodules --remove-section submodule." + c.OSCommand.Quote(submodule.Name)).Run(); err != nil {
return err return err
} }
if err := c.Run(c.NewCmdObj("git config --remove-section submodule." + c.OSCommand.Quote(submodule.Name))); err != nil { if err := c.Cmd.New("git config --remove-section submodule." + c.OSCommand.Quote(submodule.Name)).Run(); err != nil {
return err return err
} }
@ -102,7 +102,7 @@ func (c *GitCommand) SubmoduleDelete(submodule *models.SubmoduleConfig) error {
} }
} }
if err := c.Run(c.NewCmdObj("git rm --force -r " + submodule.Path)); err != nil { if err := c.Cmd.New("git rm --force -r " + submodule.Path).Run(); 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)
} }
@ -111,23 +111,24 @@ 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.Run( return c.Cmd.
c.OSCommand.NewCmdObj( New(
fmt.Sprintf( fmt.Sprintf(
"git submodule add --force --name %s -- %s %s ", "git submodule add --force --name %s -- %s %s ",
c.OSCommand.Quote(name), c.OSCommand.Quote(name),
c.OSCommand.Quote(url), c.OSCommand.Quote(url),
c.OSCommand.Quote(path), c.OSCommand.Quote(path),
))) )).
Run()
} }
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.Run(c.NewCmdObj("git config --file .gitmodules submodule." + c.OSCommand.Quote(name) + ".url " + c.OSCommand.Quote(newUrl))); err != nil { if err := c.Cmd.New("git config --file .gitmodules submodule." + c.OSCommand.Quote(name) + ".url " + c.OSCommand.Quote(newUrl)).Run(); err != nil {
return err return err
} }
if err := c.Run(c.NewCmdObj("git submodule sync -- " + c.OSCommand.Quote(path))); err != nil { if err := c.Cmd.New("git submodule sync -- " + c.OSCommand.Quote(path)).Run(); err != nil {
return err return err
} }
@ -135,27 +136,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.Run(c.NewCmdObj("git submodule init -- " + c.OSCommand.Quote(path))) return c.Cmd.New("git submodule init -- " + c.OSCommand.Quote(path)).Run()
} }
func (c *GitCommand) SubmoduleUpdate(path string) error { func (c *GitCommand) SubmoduleUpdate(path string) error {
return c.Run(c.NewCmdObj("git submodule update --init -- " + c.OSCommand.Quote(path))) return c.Cmd.New("git submodule update --init -- " + c.OSCommand.Quote(path)).Run()
} }
func (c *GitCommand) SubmoduleBulkInitCmdObj() oscommands.ICmdObj { func (c *GitCommand) SubmoduleBulkInitCmdObj() oscommands.ICmdObj {
return c.NewCmdObj("git submodule init") return c.Cmd.New("git submodule init")
} }
func (c *GitCommand) SubmoduleBulkUpdateCmdObj() oscommands.ICmdObj { func (c *GitCommand) SubmoduleBulkUpdateCmdObj() oscommands.ICmdObj {
return c.NewCmdObj("git submodule update") return c.Cmd.New("git submodule update")
} }
func (c *GitCommand) SubmoduleForceBulkUpdateCmdObj() oscommands.ICmdObj { func (c *GitCommand) SubmoduleForceBulkUpdateCmdObj() oscommands.ICmdObj {
return c.NewCmdObj("git submodule update --force") return c.Cmd.New("git submodule update --force")
} }
func (c *GitCommand) SubmoduleBulkDeinitCmdObj() oscommands.ICmdObj { func (c *GitCommand) SubmoduleBulkDeinitCmdObj() oscommands.ICmdObj {
return c.NewCmdObj("git submodule deinit --all --force") return c.Cmd.New("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.NewCmdObj(cmdStr) cmdObj := c.Cmd.New(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.NewCmdObj(cmdStr) cmdObj := c.Cmd.New(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.NewCmdObj(cmdStr).AddEnvVars("GIT_SEQUENCE_EDITOR=:") cmdObj := c.Cmd.New(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.NewCmdObj(cmdStr) cmdObj := c.Cmd.New(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.NewCmdObj(cmdStr) cmdObj := c.Cmd.New(cmdStr)
return c.DetectUnamePass(cmdObj, promptUserForCredential) return c.DetectUnamePass(cmdObj, promptUserForCredential)
} }

View File

@ -5,19 +5,19 @@ import (
) )
func (c *GitCommand) CreateLightweightTag(tagName string, commitSha string) error { func (c *GitCommand) CreateLightweightTag(tagName string, commitSha string) error {
return c.Run(c.NewCmdObj(fmt.Sprintf("git tag -- %s %s", c.OSCommand.Quote(tagName), commitSha))) return c.Cmd.New(fmt.Sprintf("git tag -- %s %s", c.OSCommand.Quote(tagName), commitSha)).Run()
} }
func (c *GitCommand) CreateAnnotatedTag(tagName, commitSha, msg string) error { func (c *GitCommand) CreateAnnotatedTag(tagName, commitSha, msg string) error {
return c.Run(c.NewCmdObj(fmt.Sprintf("git tag %s %s -m %s", tagName, commitSha, c.OSCommand.Quote(msg)))) return c.Cmd.New(fmt.Sprintf("git tag %s %s -m %s", tagName, commitSha, c.OSCommand.Quote(msg))).Run()
} }
func (c *GitCommand) DeleteTag(tagName string) error { func (c *GitCommand) DeleteTag(tagName string) error {
return c.Run(c.NewCmdObj(fmt.Sprintf("git tag -d %s", c.OSCommand.Quote(tagName)))) return c.Cmd.New(fmt.Sprintf("git tag -d %s", c.OSCommand.Quote(tagName))).Run()
} }
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.NewCmdObj(cmdStr) cmdObj := c.Cmd.New(cmdStr)
return c.DetectUnamePass(cmdObj, promptUserForCredential) return c.DetectUnamePass(cmdObj, promptUserForCredential)
} }

View File

@ -119,7 +119,7 @@ func (gui *Gui) refreshCommitsWithLimit() error {
gui.Mutexes.BranchCommitsMutex.Lock() gui.Mutexes.BranchCommitsMutex.Lock()
defer gui.Mutexes.BranchCommitsMutex.Unlock() defer gui.Mutexes.BranchCommitsMutex.Unlock()
builder := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr) builder := commands.NewCommitListBuilder(gui.Common, gui.GitCommand, gui.OSCommand)
commits, err := builder.GetCommits( commits, err := builder.GetCommits(
commands.GetCommitsOptions{ commands.GetCommitsOptions{
@ -142,7 +142,7 @@ func (gui *Gui) refreshRebaseCommits() error {
gui.Mutexes.BranchCommitsMutex.Lock() gui.Mutexes.BranchCommitsMutex.Lock()
defer gui.Mutexes.BranchCommitsMutex.Unlock() defer gui.Mutexes.BranchCommitsMutex.Unlock()
builder := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr) builder := commands.NewCommitListBuilder(gui.Common, gui.GitCommand, gui.OSCommand)
updatedCommits, err := builder.MergeRebasingCommits(gui.State.Commits) updatedCommits, err := builder.MergeRebasingCommits(gui.State.Commits)
if err != nil { if err != nil {

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.RunWithOutput(gui.GitCommand.NewCmdObj(cmdStr)) message, err := gui.GitCommand.Cmd.New(cmdStr).RunWithOutput()
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.NewShellCmdObjFromString2(cmdStr)) return gui.runSubprocessWithSuspenseAndRefresh(gui.OSCommand.Cmd.NewShell(cmdStr))
} }
loadingText := customCommand.LoadingText loadingText := customCommand.LoadingText
@ -252,8 +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 {
cmdObj := gui.OSCommand.NewShellCmdObjFromString(cmdStr) err := gui.OSCommand.WithSpan(gui.Tr.Spans.CustomCommand).Cmd.NewShell(cmdStr).Run()
if err := gui.OSCommand.WithSpan(gui.Tr.Spans.CustomCommand).Run(cmdObj); err != nil { if err != nil {
return gui.surfaceError(err) return gui.surfaceError(err)
} }
return gui.refreshSidePanels(refreshOptions{}) return gui.refreshSidePanels(refreshOptions{})

View File

@ -13,7 +13,7 @@ func (gui *Gui) exitDiffMode() error {
} }
func (gui *Gui) renderDiff() error { func (gui *Gui) renderDiff() error {
cmdObj := gui.OSCommand.NewCmdObj( cmdObj := gui.OSCommand.Cmd.New(
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(cmdObj.GetCmd()) task := NewRunPtyTask(cmdObj.GetCmd())

View File

@ -465,7 +465,7 @@ func (gui *Gui) handleCommitEditorPress() error {
cmdStr := "git " + strings.Join(args, " ") cmdStr := "git " + strings.Join(args, " ")
return gui.runSubprocessWithSuspenseAndRefresh( return gui.runSubprocessWithSuspenseAndRefresh(
gui.GitCommand.WithSpan(gui.Tr.Spans.Commit).NewCmdObjWithLog(cmdStr), gui.GitCommand.WithSpan(gui.Tr.Spans.Commit).Cmd.New(cmdStr).Log(),
) )
} }
@ -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).NewShellCmdObjFromString(cmdStr), gui.OSCommand.WithSpan(gui.Tr.Spans.EditFile).Cmd.NewShell(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.NewShellCmdObjFromString2(command), gui.OSCommand.Cmd.NewShell(command),
) )
}, },
}) })

View File

@ -32,7 +32,7 @@ func (gui *Gui) gitFlowFinishBranch(gitFlowConfig string, branchName string) err
} }
return gui.runSubprocessWithSuspenseAndRefresh( return gui.runSubprocessWithSuspenseAndRefresh(
gui.GitCommand.WithSpan(gui.Tr.Spans.GitFlowFinish).NewCmdObjWithLog("git flow " + branchType + " finish " + suffix), gui.GitCommand.WithSpan(gui.Tr.Spans.GitFlowFinish).Cmd.New("git flow " + branchType + " finish " + suffix).Log(),
) )
} }
@ -43,7 +43,7 @@ func (gui *Gui) handleCreateGitFlowMenu() error {
} }
// get config // get config
gitFlowConfig, err := gui.GitCommand.RunWithOutput(gui.GitCommand.NewCmdObj("git config --local --get-regexp gitflow")) gitFlowConfig, err := gui.GitCommand.Cmd.New("git config --local --get-regexp gitflow").RunWithOutput()
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.GitCommand.WithSpan(gui.Tr.Spans.GitFlowStart).NewCmdObjWithLog("git flow " + branchType + " start " + name), gui.GitCommand.WithSpan(gui.Tr.Spans.GitFlowStart).Cmd.New("git flow " + branchType + " start " + name).Log(),
) )
}, },
}) })

View File

@ -15,7 +15,7 @@ import (
func (gui *Gui) withGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error { 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 {
success, err := gui.runSubprocessWithSuspense(gui.OSCommand.NewShellCmdObjFromString(cmdObj.ToString())) success, err := gui.runSubprocessWithSuspense(gui.OSCommand.Cmd.NewShell(cmdObj.ToString()))
if success && onSuccess != nil { if success && onSuccess != nil {
if err := onSuccess(); err != nil { if err := onSuccess(); err != nil {
return err return err
@ -33,7 +33,7 @@ func (gui *Gui) withGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus string,
func (gui *Gui) RunAndStream(cmdObj oscommands.ICmdObj, 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 {
cmdObj := gui.OSCommand.NewShellCmdObjFromString(cmdObj.ToString()) cmdObj := gui.OSCommand.Cmd.NewShell(cmdObj.ToString())
cmdObj.AddEnvVars("TERM=dumb") cmdObj.AddEnvVars("TERM=dumb")
cmdWriter := gui.getCmdWriter() cmdWriter := gui.getCmdWriter()
cmd := cmdObj.GetCmd() cmd := cmdObj.GetCmd()

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.UserConfig.Git.Merging.ManualCommit { if status == commands.REBASE_MODE_MERGING && command != REBASE_OPTION_ABORT && gui.UserConfig.Git.Merging.ManualCommit {
sub := gitCommand.NewCmdObj("git " + commandType + " --" + command) sub := gitCommand.Cmd.New("git " + commandType + " --" + command)
if sub != nil { if sub != nil {
return gui.runSubprocessWithSuspenseAndRefresh(sub) return gui.runSubprocessWithSuspenseAndRefresh(sub)
} }

View File

@ -38,7 +38,7 @@ func (gui *Gui) handleCreateRecentReposMenu() error {
} }
func (gui *Gui) handleShowAllBranchLogs() error { func (gui *Gui) handleShowAllBranchLogs() error {
cmdObj := gui.OSCommand.NewCmdObj( cmdObj := gui.OSCommand.Cmd.New(
gui.UserConfig.Git.AllBranchesLogCmd, gui.UserConfig.Git.AllBranchesLogCmd,
) )
task := NewRunPtyTask(cmdObj.GetCmd()) task := NewRunPtyTask(cmdObj.GetCmd())

View File

@ -22,7 +22,7 @@ func (gui *Gui) stashRenderToMain() error {
if stashEntry == nil { if stashEntry == nil {
task = NewRenderStringTask(gui.Tr.NoStashEntries) task = NewRenderStringTask(gui.Tr.NoStashEntries)
} else { } else {
cmdObj := gui.OSCommand.NewCmdObj( cmdObj := gui.OSCommand.Cmd.New(
gui.GitCommand.ShowStashEntryCmdStr(stashEntry.Index), gui.GitCommand.ShowStashEntryCmdStr(stashEntry.Index),
) )
task = NewRunPtyTask(cmdObj.GetCmd()) task = NewRunPtyTask(cmdObj.GetCmd())

View File

@ -75,7 +75,7 @@ func (gui *Gui) handleViewSubCommitFiles() error {
func (gui *Gui) switchToSubCommitsContext(refName string) error { func (gui *Gui) switchToSubCommitsContext(refName string) error {
// need to populate my sub commits // need to populate my sub commits
builder := commands.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr) builder := commands.NewCommitListBuilder(gui.Common, gui.GitCommand, gui.OSCommand)
commits, err := builder.GetCommits( commits, err := builder.GetCommits(
commands.GetCommitsOptions{ commands.GetCommitsOptions{

View File

@ -214,7 +214,8 @@ func (gui *Gui) handleBulkSubmoduleActionsMenu() error {
displayStrings: []string{gui.Tr.LcBulkInitSubmodules, style.FgGreen.Sprint(gui.GitCommand.SubmoduleBulkInitCmdObj().ToString())}, 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).Run(gui.GitCommand.SubmoduleBulkInitCmdObj()); err != nil { err := gui.GitCommand.WithSpan(gui.Tr.Spans.BulkInitialiseSubmodules).SubmoduleBulkInitCmdObj().Run()
if err != nil {
return gui.surfaceError(err) return gui.surfaceError(err)
} }
@ -226,7 +227,7 @@ func (gui *Gui) handleBulkSubmoduleActionsMenu() error {
displayStrings: []string{gui.Tr.LcBulkUpdateSubmodules, style.FgYellow.Sprint(gui.GitCommand.SubmoduleBulkUpdateCmdObj().ToString())}, 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).Run(gui.GitCommand.SubmoduleBulkUpdateCmdObj()); err != nil { if err := gui.GitCommand.WithSpan(gui.Tr.Spans.BulkUpdateSubmodules).SubmoduleBulkUpdateCmdObj().Run(); err != nil {
return gui.surfaceError(err) return gui.surfaceError(err)
} }
@ -250,7 +251,7 @@ func (gui *Gui) handleBulkSubmoduleActionsMenu() error {
displayStrings: []string{gui.Tr.LcBulkDeinitSubmodules, style.FgRed.Sprint(gui.GitCommand.SubmoduleBulkDeinitCmdObj().ToString())}, 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).Run(gui.GitCommand.SubmoduleBulkDeinitCmdObj()); err != nil { if err := gui.GitCommand.WithSpan(gui.Tr.Spans.BulkDeinitialiseSubmodules).SubmoduleBulkDeinitCmdObj().Run(); err != nil {
return gui.surfaceError(err) return gui.surfaceError(err)
} }

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.Run(osCommand.NewCmdObj("go build -o " + tempLazygitPath())) err = osCommand.Cmd.New("go build -o " + tempLazygitPath()).Run()
if err != nil { if err != nil {
return err return err
} }
@ -319,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.RunWithOutput(osCommand.NewCmdObj(cmdStr)) output, _ := osCommand.Cmd.New(cmdStr).RunWithOutput()
snapshot += output + "\n" snapshot += output + "\n"
} }
@ -428,7 +428,7 @@ 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)
cmdObj := osCommand.NewCmdObj(cmdStr) cmdObj := osCommand.Cmd.New(cmdStr)
cmdObj.AddEnvVars(fmt.Sprintf("SPEED=%f", speed)) cmdObj.AddEnvVars(fmt.Sprintf("SPEED=%f", speed))
if record { if record {

View File

@ -295,8 +295,8 @@ func (u *Updater) downloadAndInstall(rawUrl string) error {
} }
u.Log.Info("untarring tarball/unzipping zip file") u.Log.Info("untarring tarball/unzipping zip file")
cmdObj := u.OSCommand.NewCmdObj(fmt.Sprintf("tar -zxf %s %s", u.OSCommand.Quote(zipPath), "lazygit")) err = u.OSCommand.Cmd.New(fmt.Sprintf("tar -zxf %s %s", u.OSCommand.Quote(zipPath), "lazygit")).Run()
if err := u.OSCommand.Run(cmdObj); err != nil { if err != nil {
return err return err
} }