1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-04-25 12:24:47 +02:00

Merge pull request #2655 from jesseduffield/use-command-args-vector

This commit is contained in:
Jesse Duffield 2023-05-23 19:55:09 +10:00 committed by GitHub
commit ee1597415d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
221 changed files with 1050 additions and 885 deletions

View File

@ -191,7 +191,7 @@ func (app *App) setupRepo() (bool, error) {
} }
if shouldInitRepo { if shouldInitRepo {
if err := app.OSCommand.Cmd.New("git init " + initialBranchArg).Run(); err != nil { if err := app.OSCommand.Cmd.New([]string{"git", "init", initialBranchArg}).Run(); err != nil {
return false, err return false, err
} }
return false, nil return false, nil

View File

@ -24,6 +24,7 @@ type GitCommand struct {
Commit *git_commands.CommitCommands Commit *git_commands.CommitCommands
Config *git_commands.ConfigCommands Config *git_commands.ConfigCommands
Custom *git_commands.CustomCommands Custom *git_commands.CustomCommands
Diff *git_commands.DiffCommands
File *git_commands.FileCommands File *git_commands.FileCommands
Flow *git_commands.FlowCommands Flow *git_commands.FlowCommands
Patch *git_commands.PatchCommands Patch *git_commands.PatchCommands
@ -112,6 +113,7 @@ func NewGitCommandAux(
tagCommands := git_commands.NewTagCommands(gitCommon) tagCommands := git_commands.NewTagCommands(gitCommon)
commitCommands := git_commands.NewCommitCommands(gitCommon) commitCommands := git_commands.NewCommitCommands(gitCommon)
customCommands := git_commands.NewCustomCommands(gitCommon) customCommands := git_commands.NewCustomCommands(gitCommon)
diffCommands := git_commands.NewDiffCommands(gitCommon)
fileCommands := git_commands.NewFileCommands(gitCommon) fileCommands := git_commands.NewFileCommands(gitCommon)
submoduleCommands := git_commands.NewSubmoduleCommands(gitCommon) submoduleCommands := git_commands.NewSubmoduleCommands(gitCommon)
workingTreeCommands := git_commands.NewWorkingTreeCommands(gitCommon, submoduleCommands, fileLoader) workingTreeCommands := git_commands.NewWorkingTreeCommands(gitCommon, submoduleCommands, fileLoader)
@ -139,6 +141,7 @@ func NewGitCommandAux(
Commit: commitCommands, Commit: commitCommands,
Config: configCommands, Config: configCommands,
Custom: customCommands, Custom: customCommands,
Diff: diffCommands,
File: fileCommands, File: fileCommands,
Flow: flowCommands, Flow: flowCommands,
Patch: patchCommands, Patch: patchCommands,
@ -274,5 +277,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.Cmd.New("git rev-parse --git-dir").DontLog().Run() return osCommand.Cmd.New(git_commands.NewGitCmd("rev-parse").Arg("--git-dir").ToArgv()).DontLog().Run()
} }

View File

@ -30,12 +30,8 @@ func NewGitCmdObjBuilder(log *logrus.Entry, innerBuilder *oscommands.CmdObjBuild
var defaultEnvVar = "GIT_OPTIONAL_LOCKS=0" var defaultEnvVar = "GIT_OPTIONAL_LOCKS=0"
func (self *gitCmdObjBuilder) New(cmdStr string) oscommands.ICmdObj { func (self *gitCmdObjBuilder) New(args []string) oscommands.ICmdObj {
return self.innerBuilder.New(cmdStr).AddEnvVars(defaultEnvVar) return self.innerBuilder.New(args).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 { func (self *gitCmdObjBuilder) NewShell(cmdStr string) oscommands.ICmdObj {

View File

@ -97,15 +97,15 @@ func (self *BisectCommands) GetInfo() *BisectInfo {
} }
func (self *BisectCommands) Reset() error { func (self *BisectCommands) Reset() error {
cmdStr := NewGitCmd("bisect").Arg("reset").ToString() cmdArgs := NewGitCmd("bisect").Arg("reset").ToArgv()
return self.cmd.New(cmdStr).StreamOutput().Run() return self.cmd.New(cmdArgs).StreamOutput().Run()
} }
func (self *BisectCommands) Mark(ref string, term string) error { func (self *BisectCommands) Mark(ref string, term string) error {
cmdStr := NewGitCmd("bisect").Arg(term, ref).ToString() cmdArgs := NewGitCmd("bisect").Arg(term, ref).ToArgv()
return self.cmd.New(cmdStr). return self.cmd.New(cmdArgs).
IgnoreEmptyError(). IgnoreEmptyError().
StreamOutput(). StreamOutput().
Run() Run()
@ -116,9 +116,9 @@ func (self *BisectCommands) Skip(ref string) error {
} }
func (self *BisectCommands) Start() error { func (self *BisectCommands) Start() error {
cmdStr := NewGitCmd("bisect").Arg("start").ToString() cmdArgs := NewGitCmd("bisect").Arg("start").ToArgv()
return self.cmd.New(cmdStr).StreamOutput().Run() return self.cmd.New(cmdArgs).StreamOutput().Run()
} }
// tells us whether we've found our problem commit(s). We return a string slice of // tells us whether we've found our problem commit(s). We return a string slice of
@ -140,8 +140,8 @@ func (self *BisectCommands) IsDone() (bool, []string, error) {
done := false done := false
candidates := []string{} candidates := []string{}
cmdStr := NewGitCmd("rev-list").Arg(newSha).ToString() cmdArgs := NewGitCmd("rev-list").Arg(newSha).ToArgv()
err := self.cmd.New(cmdStr).RunAndProcessLines(func(line string) (bool, error) { err := self.cmd.New(cmdArgs).RunAndProcessLines(func(line string) (bool, error) {
sha := strings.TrimSpace(line) sha := strings.TrimSpace(line)
if status, ok := info.statusMap[sha]; ok { if status, ok := info.statusMap[sha]; ok {
@ -171,11 +171,11 @@ func (self *BisectCommands) IsDone() (bool, []string, error) {
// bisecting is actually a descendant of our current bisect commit. If it's not, we need to // bisecting is actually a descendant of our current bisect commit. If it's not, we need to
// render the commits from the bad commit. // render the commits from the bad commit.
func (self *BisectCommands) ReachableFromStart(bisectInfo *BisectInfo) bool { func (self *BisectCommands) ReachableFromStart(bisectInfo *BisectInfo) bool {
cmdStr := NewGitCmd("merge-base"). cmdArgs := NewGitCmd("merge-base").
Arg("--is-ancestor", bisectInfo.GetNewSha(), bisectInfo.GetStartSha()). Arg("--is-ancestor", bisectInfo.GetNewSha(), bisectInfo.GetStartSha()).
ToString() ToArgv()
err := self.cmd.New(cmdStr).DontLog().Run() err := self.cmd.New(cmdArgs).DontLog().Run()
return err == nil return err == nil
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/oscommands" "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils" "github.com/jesseduffield/lazygit/pkg/utils"
"github.com/mgutz/str"
) )
type BranchCommands struct { type BranchCommands struct {
@ -20,11 +21,11 @@ func NewBranchCommands(gitCommon *GitCommon) *BranchCommands {
// New creates a new branch // New creates a new branch
func (self *BranchCommands) New(name string, base string) error { func (self *BranchCommands) New(name string, base string) error {
cmdStr := NewGitCmd("checkout"). cmdArgs := NewGitCmd("checkout").
Arg("-b", self.cmd.Quote(name), self.cmd.Quote(base)). Arg("-b", name, base).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
// CurrentBranchInfo get the current branch information. // CurrentBranchInfo get the current branch information.
@ -32,7 +33,7 @@ func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
branchName, err := self.cmd.New( branchName, err := self.cmd.New(
NewGitCmd("symbolic-ref"). NewGitCmd("symbolic-ref").
Arg("--short", "HEAD"). Arg("--short", "HEAD").
ToString(), ToArgv(),
).DontLog().RunWithOutput() ).DontLog().RunWithOutput()
if err == nil && branchName != "HEAD\n" { if err == nil && branchName != "HEAD\n" {
trimmedBranchName := strings.TrimSpace(branchName) trimmedBranchName := strings.TrimSpace(branchName)
@ -44,8 +45,8 @@ func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
} }
output, err := self.cmd.New( output, err := self.cmd.New(
NewGitCmd("branch"). NewGitCmd("branch").
Arg("--points-at=HEAD", "--format=\"%(HEAD)%00%(objectname)%00%(refname)\""). Arg("--points-at=HEAD", "--format=%(HEAD)%00%(objectname)%00%(refname)").
ToString(), ToArgv(),
).DontLog().RunWithOutput() ).DontLog().RunWithOutput()
if err != nil { if err != nil {
return BranchInfo{}, err return BranchInfo{}, err
@ -71,12 +72,12 @@ func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
// Delete delete branch // Delete delete branch
func (self *BranchCommands) Delete(branch string, force bool) error { func (self *BranchCommands) Delete(branch string, force bool) error {
cmdStr := NewGitCmd("branch"). cmdArgs := NewGitCmd("branch").
ArgIfElse(force, "-D", "-d"). ArgIfElse(force, "-D", "-d").
Arg(self.cmd.Quote(branch)). Arg(branch).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).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
@ -86,12 +87,12 @@ type CheckoutOptions struct {
} }
func (self *BranchCommands) Checkout(branch string, options CheckoutOptions) error { func (self *BranchCommands) Checkout(branch string, options CheckoutOptions) error {
cmdStr := NewGitCmd("checkout"). cmdArgs := NewGitCmd("checkout").
ArgIf(options.Force, "--force"). ArgIf(options.Force, "--force").
Arg(self.cmd.Quote(branch)). Arg(branch).
ToString() ToArgv()
return self.cmd.New(cmdStr). return self.cmd.New(cmdArgs).
// 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").
@ -111,31 +112,34 @@ func (self *BranchCommands) GetGraphCmdObj(branchName string) oscommands.ICmdObj
templateValues := map[string]string{ templateValues := map[string]string{
"branchName": self.cmd.Quote(branchName), "branchName": self.cmd.Quote(branchName),
} }
return self.cmd.New(utils.ResolvePlaceholderString(branchLogCmdTemplate, templateValues)).DontLog()
resolvedTemplate := utils.ResolvePlaceholderString(branchLogCmdTemplate, templateValues)
return self.cmd.New(str.ToArgv(resolvedTemplate)).DontLog()
} }
func (self *BranchCommands) SetCurrentBranchUpstream(remoteName string, remoteBranchName string) error { func (self *BranchCommands) SetCurrentBranchUpstream(remoteName string, remoteBranchName string) error {
cmdStr := NewGitCmd("branch"). cmdArgs := NewGitCmd("branch").
Arg(fmt.Sprintf("--set-upstream-to=%s/%s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName))). Arg(fmt.Sprintf("--set-upstream-to=%s/%s", remoteName, remoteBranchName)).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *BranchCommands) SetUpstream(remoteName string, remoteBranchName string, branchName string) error { func (self *BranchCommands) SetUpstream(remoteName string, remoteBranchName string, branchName string) error {
cmdStr := NewGitCmd("branch"). cmdArgs := NewGitCmd("branch").
Arg(fmt.Sprintf("--set-upstream-to=%s/%s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName))). Arg(fmt.Sprintf("--set-upstream-to=%s/%s", remoteName, remoteBranchName)).
Arg(self.cmd.Quote(branchName)). Arg(branchName).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *BranchCommands) UnsetUpstream(branchName string) error { func (self *BranchCommands) UnsetUpstream(branchName string) error {
cmdStr := NewGitCmd("branch").Arg("--unset-upstream", self.cmd.Quote(branchName)). cmdArgs := NewGitCmd("branch").Arg("--unset-upstream", branchName).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *BranchCommands) GetCurrentBranchUpstreamDifferenceCount() (string, string) { func (self *BranchCommands) GetCurrentBranchUpstreamDifferenceCount() (string, string) {
@ -161,37 +165,37 @@ func (self *BranchCommands) GetCommitDifferences(from, to string) (string, strin
} }
func (self *BranchCommands) countDifferences(from, to string) (string, error) { func (self *BranchCommands) countDifferences(from, to string) (string, error) {
cmdStr := NewGitCmd("rev-list"). cmdArgs := NewGitCmd("rev-list").
Arg(fmt.Sprintf("%s..%s", from, to)). Arg(fmt.Sprintf("%s..%s", from, to)).
Arg("--count"). Arg("--count").
ToString() ToArgv()
return self.cmd.New(cmdStr).DontLog().RunWithOutput() return self.cmd.New(cmdArgs).DontLog().RunWithOutput()
} }
func (self *BranchCommands) IsHeadDetached() bool { func (self *BranchCommands) IsHeadDetached() bool {
cmdStr := NewGitCmd("symbolic-ref").Arg("-q", "HEAD").ToString() cmdArgs := NewGitCmd("symbolic-ref").Arg("-q", "HEAD").ToArgv()
err := self.cmd.New(cmdStr).DontLog().Run() err := self.cmd.New(cmdArgs).DontLog().Run()
return err != nil return err != nil
} }
func (self *BranchCommands) Rename(oldName string, newName string) error { func (self *BranchCommands) Rename(oldName string, newName string) error {
cmdStr := NewGitCmd("branch"). cmdArgs := NewGitCmd("branch").
Arg("--move", self.cmd.Quote(oldName), self.cmd.Quote(newName)). Arg("--move", oldName, newName).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *BranchCommands) GetRawBranches() (string, error) { func (self *BranchCommands) GetRawBranches() (string, error) {
cmdStr := NewGitCmd("for-each-ref"). cmdArgs := NewGitCmd("for-each-ref").
Arg("--sort=-committerdate"). Arg("--sort=-committerdate").
Arg(`--format="%(HEAD)%00%(refname:short)%00%(upstream:short)%00%(upstream:track)"`). Arg(`--format=%(HEAD)%00%(refname:short)%00%(upstream:short)%00%(upstream:track)`).
Arg("refs/heads"). Arg("refs/heads").
ToString() ToArgv()
return self.cmd.New(cmdStr).DontLog().RunWithOutput() return self.cmd.New(cmdArgs).DontLog().RunWithOutput()
} }
type MergeOpts struct { type MergeOpts struct {
@ -199,16 +203,16 @@ type MergeOpts struct {
} }
func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error { func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error {
command := NewGitCmd("merge"). cmdArgs := NewGitCmd("merge").
Arg("--no-edit"). Arg("--no-edit").
ArgIf(self.UserConfig.Git.Merging.Args != "", self.UserConfig.Git.Merging.Args). ArgIf(self.UserConfig.Git.Merging.Args != "", self.UserConfig.Git.Merging.Args).
ArgIf(opts.FastForwardOnly, "--ff-only"). ArgIf(opts.FastForwardOnly, "--ff-only").
Arg(self.cmd.Quote(branchName)). Arg(branchName).
ToString() ToArgv()
return self.cmd.New(command).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *BranchCommands) AllBranchesLogCmdObj() oscommands.ICmdObj { func (self *BranchCommands) AllBranchesLogCmdObj() oscommands.ICmdObj {
return self.cmd.New(self.UserConfig.Git.AllBranchesLogCmd).DontLog() return self.cmd.New(str.ToArgv(self.UserConfig.Git.AllBranchesLogCmd)).DontLog()
} }

View File

@ -21,21 +21,21 @@ func TestBranchGetCommitDifferences(t *testing.T) {
{ {
"Can't retrieve pushable count", "Can't retrieve pushable count",
oscommands.NewFakeRunner(t). oscommands.NewFakeRunner(t).
Expect("git rev-list @{u}..HEAD --count", "", errors.New("error")), ExpectGitArgs([]string{"rev-list", "@{u}..HEAD", "--count"}, "", errors.New("error")),
"?", "?", "?", "?",
}, },
{ {
"Can't retrieve pullable count", "Can't retrieve pullable count",
oscommands.NewFakeRunner(t). oscommands.NewFakeRunner(t).
Expect("git rev-list @{u}..HEAD --count", "1\n", nil). ExpectGitArgs([]string{"rev-list", "@{u}..HEAD", "--count"}, "1\n", nil).
Expect("git rev-list HEAD..@{u} --count", "", errors.New("error")), ExpectGitArgs([]string{"rev-list", "HEAD..@{u}", "--count"}, "", errors.New("error")),
"?", "?", "?", "?",
}, },
{ {
"Retrieve pullable and pushable count", "Retrieve pullable and pushable count",
oscommands.NewFakeRunner(t). oscommands.NewFakeRunner(t).
Expect("git rev-list @{u}..HEAD --count", "1\n", nil). ExpectGitArgs([]string{"rev-list", "@{u}..HEAD", "--count"}, "1\n", nil).
Expect("git rev-list HEAD..@{u} --count", "2\n", nil), ExpectGitArgs([]string{"rev-list", "HEAD..@{u}", "--count"}, "2\n", nil),
"1", "2", "1", "2",
}, },
} }
@ -54,7 +54,7 @@ func TestBranchGetCommitDifferences(t *testing.T) {
func TestBranchNewBranch(t *testing.T) { func TestBranchNewBranch(t *testing.T) {
runner := oscommands.NewFakeRunner(t). runner := oscommands.NewFakeRunner(t).
Expect(`git checkout -b "test" "refs/heads/master"`, "", nil) ExpectGitArgs([]string{"checkout", "-b", "test", "refs/heads/master"}, "", nil)
instance := buildBranchCommands(commonDeps{runner: runner}) instance := buildBranchCommands(commonDeps{runner: runner})
assert.NoError(t, instance.New("test", "refs/heads/master")) assert.NoError(t, instance.New("test", "refs/heads/master"))
@ -73,7 +73,7 @@ func TestBranchDeleteBranch(t *testing.T) {
{ {
"Delete a branch", "Delete a branch",
false, false,
oscommands.NewFakeRunner(t).Expect(`git branch -d "test"`, "", nil), oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"branch", "-d", "test"}, "", nil),
func(err error) { func(err error) {
assert.NoError(t, err) assert.NoError(t, err)
}, },
@ -81,7 +81,7 @@ func TestBranchDeleteBranch(t *testing.T) {
{ {
"Force delete a branch", "Force delete a branch",
true, true,
oscommands.NewFakeRunner(t).Expect(`git branch -D "test"`, "", nil), oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"branch", "-D", "test"}, "", nil),
func(err error) { func(err error) {
assert.NoError(t, err) assert.NoError(t, err)
}, },
@ -105,14 +105,14 @@ func TestBranchMerge(t *testing.T) {
userConfig *config.UserConfig userConfig *config.UserConfig
opts MergeOpts opts MergeOpts
branchName string branchName string
expected string expected []string
}{ }{
{ {
testName: "basic", testName: "basic",
userConfig: &config.UserConfig{}, userConfig: &config.UserConfig{},
opts: MergeOpts{}, opts: MergeOpts{},
branchName: "mybranch", branchName: "mybranch",
expected: `git merge --no-edit "mybranch"`, expected: []string{"merge", "--no-edit", "mybranch"},
}, },
{ {
testName: "merging args", testName: "merging args",
@ -125,14 +125,14 @@ func TestBranchMerge(t *testing.T) {
}, },
opts: MergeOpts{}, opts: MergeOpts{},
branchName: "mybranch", branchName: "mybranch",
expected: `git merge --no-edit --merging-args "mybranch"`, expected: []string{"merge", "--no-edit", "--merging-args", "mybranch"},
}, },
{ {
testName: "fast forward only", testName: "fast forward only",
userConfig: &config.UserConfig{}, userConfig: &config.UserConfig{},
opts: MergeOpts{FastForwardOnly: true}, opts: MergeOpts{FastForwardOnly: true},
branchName: "mybranch", branchName: "mybranch",
expected: `git merge --no-edit --ff-only "mybranch"`, expected: []string{"merge", "--no-edit", "--ff-only", "mybranch"},
}, },
} }
@ -140,7 +140,7 @@ func TestBranchMerge(t *testing.T) {
s := s s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
runner := oscommands.NewFakeRunner(t). runner := oscommands.NewFakeRunner(t).
Expect(s.expected, "", nil) ExpectGitArgs(s.expected, "", nil)
instance := buildBranchCommands(commonDeps{runner: runner, userConfig: s.userConfig}) instance := buildBranchCommands(commonDeps{runner: runner, userConfig: s.userConfig})
assert.NoError(t, instance.Merge(s.branchName, s.opts)) assert.NoError(t, instance.Merge(s.branchName, s.opts))
@ -160,7 +160,7 @@ func TestBranchCheckout(t *testing.T) {
scenarios := []scenario{ scenarios := []scenario{
{ {
"Checkout", "Checkout",
oscommands.NewFakeRunner(t).Expect(`git checkout "test"`, "", nil), oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"checkout", "test"}, "", nil),
func(err error) { func(err error) {
assert.NoError(t, err) assert.NoError(t, err)
}, },
@ -168,7 +168,7 @@ func TestBranchCheckout(t *testing.T) {
}, },
{ {
"Checkout forced", "Checkout forced",
oscommands.NewFakeRunner(t).Expect(`git checkout --force "test"`, "", nil), oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"checkout", "--force", "test"}, "", nil),
func(err error) { func(err error) {
assert.NoError(t, err) assert.NoError(t, err)
}, },
@ -214,7 +214,7 @@ func TestBranchCurrentBranchInfo(t *testing.T) {
scenarios := []scenario{ scenarios := []scenario{
{ {
"says we are on the master branch if we are", "says we are on the master branch if we are",
oscommands.NewFakeRunner(t).Expect(`git symbolic-ref --short HEAD`, "master", nil), oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"symbolic-ref", "--short", "HEAD"}, "master", nil),
func(info BranchInfo, err error) { func(info BranchInfo, err error) {
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, "master", info.RefName) assert.EqualValues(t, "master", info.RefName)
@ -225,8 +225,9 @@ func TestBranchCurrentBranchInfo(t *testing.T) {
{ {
"falls back to git `git branch --points-at=HEAD` if symbolic-ref fails", "falls back to git `git branch --points-at=HEAD` if symbolic-ref fails",
oscommands.NewFakeRunner(t). oscommands.NewFakeRunner(t).
Expect(`git symbolic-ref --short HEAD`, "", errors.New("error")). ExpectGitArgs([]string{"symbolic-ref", "--short", "HEAD"}, "", errors.New("error")).
Expect(`git branch --points-at=HEAD --format="%(HEAD)%00%(objectname)%00%(refname)"`, "*\x006f71c57a8d4bd6c11399c3f55f42c815527a73a4\x00(HEAD detached at 6f71c57a)\n", nil), ExpectGitArgs([]string{"branch", "--points-at=HEAD", "--format=%(HEAD)%00%(objectname)%00%(refname)"},
"*\x006f71c57a8d4bd6c11399c3f55f42c815527a73a4\x00(HEAD detached at 6f71c57a)\n", nil),
func(info BranchInfo, err error) { func(info BranchInfo, err error) {
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, "6f71c57a8d4bd6c11399c3f55f42c815527a73a4", info.RefName) assert.EqualValues(t, "6f71c57a8d4bd6c11399c3f55f42c815527a73a4", info.RefName)
@ -237,9 +238,9 @@ func TestBranchCurrentBranchInfo(t *testing.T) {
{ {
"handles a detached head (LANG=zh_CN.UTF-8)", "handles a detached head (LANG=zh_CN.UTF-8)",
oscommands.NewFakeRunner(t). oscommands.NewFakeRunner(t).
Expect(`git symbolic-ref --short HEAD`, "", errors.New("error")). ExpectGitArgs([]string{"symbolic-ref", "--short", "HEAD"}, "", errors.New("error")).
Expect( ExpectGitArgs(
`git branch --points-at=HEAD --format="%(HEAD)%00%(objectname)%00%(refname)"`, []string{"branch", "--points-at=HEAD", "--format=%(HEAD)%00%(objectname)%00%(refname)"},
"*\x00679b0456f3db7c505b398def84e7d023e5b55a8d\x00(头指针在 679b0456 分离)\n"+ "*\x00679b0456f3db7c505b398def84e7d023e5b55a8d\x00(头指针在 679b0456 分离)\n"+
" \x00679b0456f3db7c505b398def84e7d023e5b55a8d\x00refs/heads/master\n", " \x00679b0456f3db7c505b398def84e7d023e5b55a8d\x00refs/heads/master\n",
nil), nil),
@ -253,8 +254,8 @@ func TestBranchCurrentBranchInfo(t *testing.T) {
{ {
"bubbles up error if there is one", "bubbles up error if there is one",
oscommands.NewFakeRunner(t). oscommands.NewFakeRunner(t).
Expect(`git symbolic-ref --short HEAD`, "", errors.New("error")). ExpectGitArgs([]string{"symbolic-ref", "--short", "HEAD"}, "", errors.New("error")).
Expect(`git branch --points-at=HEAD --format="%(HEAD)%00%(objectname)%00%(refname)"`, "", errors.New("error")), ExpectGitArgs([]string{"branch", "--points-at=HEAD", "--format=%(HEAD)%00%(objectname)%00%(refname)"}, "", errors.New("error")),
func(info BranchInfo, err error) { func(info BranchInfo, err error) {
assert.Error(t, err) assert.Error(t, err)
assert.EqualValues(t, "", info.RefName) assert.EqualValues(t, "", info.RefName)

View File

@ -22,27 +22,27 @@ func NewCommitCommands(gitCommon *GitCommon) *CommitCommands {
// ResetAuthor resets the author of the topmost commit // ResetAuthor resets the author of the topmost commit
func (self *CommitCommands) ResetAuthor() error { func (self *CommitCommands) ResetAuthor() error {
cmdStr := NewGitCmd("commit"). cmdArgs := NewGitCmd("commit").
Arg("--allow-empty", "--only", "--no-edit", "--amend", "--reset-author"). Arg("--allow-empty", "--only", "--no-edit", "--amend", "--reset-author").
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
// Sets the commit's author to the supplied value. Value is expected to be of the form 'Name <Email>' // Sets the commit's author to the supplied value. Value is expected to be of the form 'Name <Email>'
func (self *CommitCommands) SetAuthor(value string) error { func (self *CommitCommands) SetAuthor(value string) error {
cmdStr := NewGitCmd("commit"). cmdArgs := NewGitCmd("commit").
Arg("--allow-empty", "--only", "--no-edit", "--amend", "--author="+self.cmd.Quote(value)). Arg("--allow-empty", "--only", "--no-edit", "--amend", "--author="+value).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
// ResetToCommit reset to commit // ResetToCommit reset to commit
func (self *CommitCommands) ResetToCommit(sha string, strength string, envVars []string) error { func (self *CommitCommands) ResetToCommit(sha string, strength string, envVars []string) error {
cmdStr := NewGitCmd("reset").Arg("--"+strength, sha).ToString() cmdArgs := NewGitCmd("reset").Arg("--"+strength, sha).ToArgv()
return self.cmd.New(cmdStr). return self.cmd.New(cmdArgs).
// 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").
@ -55,33 +55,37 @@ func (self *CommitCommands) CommitCmdObj(message string) oscommands.ICmdObj {
skipHookPrefix := self.UserConfig.Git.SkipHookPrefix skipHookPrefix := self.UserConfig.Git.SkipHookPrefix
cmdStr := NewGitCmd("commit"). cmdArgs := NewGitCmd("commit").
ArgIf(skipHookPrefix != "" && strings.HasPrefix(message, skipHookPrefix), "--no-verify"). ArgIf(skipHookPrefix != "" && strings.HasPrefix(message, skipHookPrefix), "--no-verify").
ArgIf(self.signoffFlag() != "", self.signoffFlag()). ArgIf(self.signoffFlag() != "", self.signoffFlag()).
Arg(messageArgs...). Arg(messageArgs...).
ToString() ToArgv()
return self.cmd.New(cmdStr) return self.cmd.New(cmdArgs)
}
func (self *CommitCommands) RewordLastCommitInEditorCmdObj() oscommands.ICmdObj {
return self.cmd.New(NewGitCmd("commit").Arg("--allow-empty", "--amend", "--only").ToArgv())
} }
// RewordLastCommit rewords the topmost commit with the given message // RewordLastCommit rewords the topmost commit with the given message
func (self *CommitCommands) RewordLastCommit(message string) error { func (self *CommitCommands) RewordLastCommit(message string) error {
messageArgs := self.commitMessageArgs(message) messageArgs := self.commitMessageArgs(message)
cmdStr := NewGitCmd("commit"). cmdArgs := NewGitCmd("commit").
Arg("--allow-empty", "--amend", "--only"). Arg("--allow-empty", "--amend", "--only").
Arg(messageArgs...). Arg(messageArgs...).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *CommitCommands) commitMessageArgs(message string) []string { func (self *CommitCommands) commitMessageArgs(message string) []string {
msg, description, _ := strings.Cut(message, "\n") msg, description, _ := strings.Cut(message, "\n")
args := []string{"-m", self.cmd.Quote(msg)} args := []string{"-m", msg}
if description != "" { if description != "" {
args = append(args, "-m", self.cmd.Quote(description)) args = append(args, "-m", description)
} }
return args return args
@ -89,12 +93,12 @@ func (self *CommitCommands) commitMessageArgs(message string) []string {
// runs git commit without the -m argument meaning it will invoke the user's editor // runs git commit without the -m argument meaning it will invoke the user's editor
func (self *CommitCommands) CommitEditorCmdObj() oscommands.ICmdObj { func (self *CommitCommands) CommitEditorCmdObj() oscommands.ICmdObj {
cmdStr := NewGitCmd("commit"). cmdArgs := NewGitCmd("commit").
ArgIf(self.signoffFlag() != "", self.signoffFlag()). ArgIf(self.signoffFlag() != "", self.signoffFlag()).
ArgIf(self.verboseFlag() != "", self.verboseFlag()). ArgIf(self.verboseFlag() != "", self.verboseFlag()).
ToString() ToArgv()
return self.cmd.New(cmdStr) return self.cmd.New(cmdArgs)
} }
func (self *CommitCommands) signoffFlag() string { func (self *CommitCommands) signoffFlag() string {
@ -118,26 +122,26 @@ func (self *CommitCommands) verboseFlag() string {
// Get the subject of the HEAD commit // Get the subject of the HEAD commit
func (self *CommitCommands) GetHeadCommitMessage() (string, error) { func (self *CommitCommands) GetHeadCommitMessage() (string, error) {
cmdStr := NewGitCmd("log").Arg("-1", "--pretty=%s").ToString() cmdArgs := NewGitCmd("log").Arg("-1", "--pretty=%s").ToArgv()
message, err := self.cmd.New(cmdStr).DontLog().RunWithOutput() message, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
return strings.TrimSpace(message), err return strings.TrimSpace(message), err
} }
func (self *CommitCommands) GetCommitMessage(commitSha string) (string, error) { func (self *CommitCommands) GetCommitMessage(commitSha string) (string, error) {
cmdStr := NewGitCmd("rev-list"). cmdArgs := NewGitCmd("rev-list").
Arg("--format=%B", "--max-count=1", commitSha). Arg("--format=%B", "--max-count=1", commitSha).
ToString() ToArgv()
messageWithHeader, err := self.cmd.New(cmdStr).DontLog().RunWithOutput() messageWithHeader, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
message := strings.Join(strings.SplitAfter(messageWithHeader, "\n")[1:], "") message := strings.Join(strings.SplitAfter(messageWithHeader, "\n")[1:], "")
return strings.TrimSpace(message), err return strings.TrimSpace(message), err
} }
func (self *CommitCommands) GetCommitDiff(commitSha string) (string, error) { func (self *CommitCommands) GetCommitDiff(commitSha string) (string, error) {
cmdStr := NewGitCmd("show").Arg("--no-color", commitSha).ToString() cmdArgs := NewGitCmd("show").Arg("--no-color", commitSha).ToArgv()
diff, err := self.cmd.New(cmdStr).DontLog().RunWithOutput() diff, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
return diff, err return diff, err
} }
@ -147,11 +151,11 @@ type Author struct {
} }
func (self *CommitCommands) GetCommitAuthor(commitSha string) (Author, error) { func (self *CommitCommands) GetCommitAuthor(commitSha string) (Author, error) {
cmdStr := NewGitCmd("show"). cmdArgs := NewGitCmd("show").
Arg("--no-patch", "--pretty=format:'%an%x00%ae'", commitSha). Arg("--no-patch", "--pretty=format:'%an%x00%ae'", commitSha).
ToString() ToArgv()
output, err := self.cmd.New(cmdStr).DontLog().RunWithOutput() output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err != nil { if err != nil {
return Author{}, err return Author{}, err
} }
@ -170,21 +174,21 @@ func (self *CommitCommands) GetCommitMessageFirstLine(sha string) (string, error
} }
func (self *CommitCommands) GetCommitMessagesFirstLine(shas []string) (string, error) { func (self *CommitCommands) GetCommitMessagesFirstLine(shas []string) (string, error) {
cmdStr := NewGitCmd("show"). cmdArgs := NewGitCmd("show").
Arg("--no-patch", "--pretty=format:%s"). Arg("--no-patch", "--pretty=format:%s").
Arg(shas...). Arg(shas...).
ToString() ToArgv()
return self.cmd.New(cmdStr).DontLog().RunWithOutput() return self.cmd.New(cmdArgs).DontLog().RunWithOutput()
} }
func (self *CommitCommands) GetCommitsOneline(shas []string) (string, error) { func (self *CommitCommands) GetCommitsOneline(shas []string) (string, error) {
cmdStr := NewGitCmd("show"). cmdArgs := NewGitCmd("show").
Arg("--no-patch", "--oneline"). Arg("--no-patch", "--oneline").
Arg(shas...). Arg(shas...).
ToString() ToArgv()
return self.cmd.New(cmdStr).DontLog().RunWithOutput() return self.cmd.New(cmdArgs).DontLog().RunWithOutput()
} }
// AmendHead amends HEAD with whatever is staged in your working tree // AmendHead amends HEAD with whatever is staged in your working tree
@ -193,17 +197,17 @@ func (self *CommitCommands) AmendHead() error {
} }
func (self *CommitCommands) AmendHeadCmdObj() oscommands.ICmdObj { func (self *CommitCommands) AmendHeadCmdObj() oscommands.ICmdObj {
cmdStr := NewGitCmd("commit"). cmdArgs := NewGitCmd("commit").
Arg("--amend", "--no-edit", "--allow-empty"). Arg("--amend", "--no-edit", "--allow-empty").
ToString() ToArgv()
return self.cmd.New(cmdStr) return self.cmd.New(cmdArgs)
} }
func (self *CommitCommands) ShowCmdObj(sha string, filterPath string, ignoreWhitespace bool) oscommands.ICmdObj { func (self *CommitCommands) ShowCmdObj(sha string, filterPath string, ignoreWhitespace bool) oscommands.ICmdObj {
contextSize := self.UserConfig.Git.DiffContextSize contextSize := self.UserConfig.Git.DiffContextSize
cmdStr := NewGitCmd("show"). cmdArgs := NewGitCmd("show").
Arg("--submodule"). Arg("--submodule").
Arg("--color="+self.UserConfig.Git.Paging.ColorArg). Arg("--color="+self.UserConfig.Git.Paging.ColorArg).
Arg(fmt.Sprintf("--unified=%d", contextSize)). Arg(fmt.Sprintf("--unified=%d", contextSize)).
@ -211,39 +215,39 @@ func (self *CommitCommands) ShowCmdObj(sha string, filterPath string, ignoreWhit
Arg("-p"). Arg("-p").
Arg(sha). Arg(sha).
ArgIf(ignoreWhitespace, "--ignore-all-space"). ArgIf(ignoreWhitespace, "--ignore-all-space").
ArgIf(filterPath != "", "--", self.cmd.Quote(filterPath)). ArgIf(filterPath != "", "--", filterPath).
ToString() ToArgv()
return self.cmd.New(cmdStr).DontLog() return self.cmd.New(cmdArgs).DontLog()
} }
// Revert reverts the selected commit by sha // Revert reverts the selected commit by sha
func (self *CommitCommands) Revert(sha string) error { func (self *CommitCommands) Revert(sha string) error {
cmdStr := NewGitCmd("revert").Arg(sha).ToString() cmdArgs := NewGitCmd("revert").Arg(sha).ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *CommitCommands) RevertMerge(sha string, parentNumber int) error { func (self *CommitCommands) RevertMerge(sha string, parentNumber int) error {
cmdStr := NewGitCmd("revert").Arg(sha, "-m", fmt.Sprintf("%d", parentNumber)). cmdArgs := NewGitCmd("revert").Arg(sha, "-m", fmt.Sprintf("%d", parentNumber)).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
// CreateFixupCommit creates a commit that fixes up a previous commit // CreateFixupCommit creates a commit that fixes up a previous commit
func (self *CommitCommands) CreateFixupCommit(sha string) error { func (self *CommitCommands) CreateFixupCommit(sha string) error {
cmdStr := NewGitCmd("commit").Arg("--fixup=" + sha).ToString() cmdArgs := NewGitCmd("commit").Arg("--fixup=" + sha).ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
// a value of 0 means the head commit, 1 is the parent commit, etc // a value of 0 means the head commit, 1 is the parent commit, etc
func (self *CommitCommands) GetCommitMessageFromHistory(value int) (string, error) { func (self *CommitCommands) GetCommitMessageFromHistory(value int) (string, error) {
cmdStr := NewGitCmd("log").Arg("-1", fmt.Sprintf("--skip=%d", value), "--pretty=%H"). cmdArgs := NewGitCmd("log").Arg("-1", fmt.Sprintf("--skip=%d", value), "--pretty=%H").
ToString() ToArgv()
hash, _ := self.cmd.New(cmdStr).DontLog().RunWithOutput() hash, _ := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
formattedHash := strings.TrimSpace(hash) formattedHash := strings.TrimSpace(hash)
if len(formattedHash) == 0 { if len(formattedHash) == 0 {
return "", ErrInvalidCommitIndex return "", ErrInvalidCommitIndex

View File

@ -24,7 +24,7 @@ func NewCommitFileLoader(common *common.Common, cmd oscommands.ICmdObjBuilder) *
// GetFilesInDiff get the specified commit files // GetFilesInDiff get the specified commit files
func (self *CommitFileLoader) GetFilesInDiff(from string, to string, reverse bool) ([]*models.CommitFile, error) { func (self *CommitFileLoader) GetFilesInDiff(from string, to string, reverse bool) ([]*models.CommitFile, error) {
cmdStr := NewGitCmd("diff"). cmdArgs := NewGitCmd("diff").
Arg("--submodule"). Arg("--submodule").
Arg("--no-ext-diff"). Arg("--no-ext-diff").
Arg("--name-status"). Arg("--name-status").
@ -33,9 +33,9 @@ func (self *CommitFileLoader) GetFilesInDiff(from string, to string, reverse boo
ArgIf(reverse, "-R"). ArgIf(reverse, "-R").
Arg(from). Arg(from).
Arg(to). Arg(to).
ToString() ToArgv()
filenames, err := self.cmd.New(cmdStr).DontLog().RunWithOutput() filenames, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -33,10 +33,11 @@ type CommitLoader struct {
readFile func(filename string) ([]byte, error) readFile func(filename string) ([]byte, error)
walkFiles func(root string, fn filepath.WalkFunc) error walkFiles func(root string, fn filepath.WalkFunc) error
dotGitDir string dotGitDir string
// List of main branches that exist in the repo, quoted for direct use in a git command. // List of main branches that exist in the repo.
// We use these to obtain the merge base of the branch. // We use these to obtain the merge base of the branch.
// When nil, we're yet to obtain the list of main branches. // When nil, we're yet to obtain the list of existing main branches.
quotedMainBranches *string // When an empty slice, we've obtained the list and it's empty.
mainBranches []string
} }
// making our dependencies explicit for the sake of easier testing // making our dependencies explicit for the sake of easier testing
@ -53,7 +54,7 @@ func NewCommitLoader(
readFile: os.ReadFile, readFile: os.ReadFile,
walkFiles: filepath.Walk, walkFiles: filepath.Walk,
dotGitDir: dotGitDir, dotGitDir: dotGitDir,
quotedMainBranches: nil, mainBranches: nil,
} }
} }
@ -205,7 +206,7 @@ func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode
Config("log.showSignature=false"). Config("log.showSignature=false").
Arg("--no-patch", "--oneline", "--abbrev=20", prettyFormat). Arg("--no-patch", "--oneline", "--abbrev=20", prettyFormat).
Arg(commitShas...). Arg(commitShas...).
ToString(), ToArgv(),
).DontLog() ).DontLog()
fullCommits := map[string]*models.Commit{} fullCommits := map[string]*models.Commit{}
@ -364,11 +365,11 @@ func (self *CommitLoader) setCommitMergedStatuses(refName string, commits []*mod
} }
func (self *CommitLoader) getMergeBase(refName string) string { func (self *CommitLoader) getMergeBase(refName string) string {
if self.quotedMainBranches == nil { if self.mainBranches == nil {
self.quotedMainBranches = lo.ToPtr(self.getExistingMainBranches()) self.mainBranches = self.getExistingMainBranches()
} }
if *self.quotedMainBranches == "" { if len(self.mainBranches) == 0 {
return "" return ""
} }
@ -376,30 +377,29 @@ func (self *CommitLoader) getMergeBase(refName string) string {
// return the base commit for the closest one. // return the base commit for the closest one.
output, err := self.cmd.New( output, err := self.cmd.New(
NewGitCmd("merge-base").Arg(self.cmd.Quote(refName), *self.quotedMainBranches). NewGitCmd("merge-base").Arg(refName).Arg(self.mainBranches...).
ToString(), ToArgv(),
).DontLog().RunWithOutput() ).DontLog().RunWithOutput()
if err != nil { if err != nil {
// If there's an error, it must be because one of the main branches that // If there's an error, it must be because one of the main branches that
// used to exist when we called getExistingMainBranches() was deleted // used to exist when we called getExistingMainBranches() was deleted
// meanwhile. To fix this for next time, throw away our cache. // meanwhile. To fix this for next time, throw away our cache.
self.quotedMainBranches = nil self.mainBranches = nil
} }
return ignoringWarnings(output) return ignoringWarnings(output)
} }
func (self *CommitLoader) getExistingMainBranches() string { func (self *CommitLoader) getExistingMainBranches() []string {
return strings.Join( return lo.FilterMap(self.UserConfig.Git.MainBranches,
lo.FilterMap(self.UserConfig.Git.MainBranches,
func(branchName string, _ int) (string, bool) { func(branchName string, _ int) (string, bool) {
quotedRef := self.cmd.Quote("refs/heads/" + branchName) ref := "refs/heads/" + branchName
if err := self.cmd.New( if err := self.cmd.New(
NewGitCmd("rev-parse").Arg("--verify", "--quiet", quotedRef).ToString(), NewGitCmd("rev-parse").Arg("--verify", "--quiet", ref).ToArgv(),
).DontLog().Run(); err != nil { ).DontLog().Run(); err != nil {
return "", false return "", false
} }
return quotedRef, true return ref, true
}), " ") })
} }
func ignoringWarnings(commandOutput string) string { func ignoringWarnings(commandOutput string) string {
@ -415,12 +415,11 @@ 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 (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) { func (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) {
output, err := self.cmd. output, err := self.cmd.New(
New(
NewGitCmd("merge-base"). NewGitCmd("merge-base").
Arg(self.cmd.Quote(refName)). Arg(refName).
Arg(self.cmd.Quote(strings.TrimPrefix(refName, "refs/heads/")) + "@{u}"). Arg(strings.TrimPrefix(refName, "refs/heads/") + "@{u}").
ToString(), ToArgv(),
). ).
DontLog(). DontLog().
RunWithOutput() RunWithOutput()
@ -435,8 +434,8 @@ func (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) {
func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj { func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
config := self.UserConfig.Git.Log config := self.UserConfig.Git.Log
cmdStr := NewGitCmd("log"). cmdArgs := NewGitCmd("log").
Arg(self.cmd.Quote(opts.RefName)). Arg(opts.RefName).
ArgIf(config.Order != "default", "--"+config.Order). ArgIf(config.Order != "default", "--"+config.Order).
ArgIf(opts.All, "--all"). ArgIf(opts.All, "--all").
Arg("--oneline"). Arg("--oneline").
@ -446,10 +445,10 @@ func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
ArgIf(opts.FilterPath != "", "--follow"). ArgIf(opts.FilterPath != "", "--follow").
Arg("--no-show-signature"). Arg("--no-show-signature").
Arg("--"). Arg("--").
ArgIf(opts.FilterPath != "", self.cmd.Quote(opts.FilterPath)). ArgIf(opts.FilterPath != "", opts.FilterPath).
ToString() ToArgv()
return self.cmd.New(cmdStr).DontLog() return self.cmd.New(cmdArgs).DontLog()
} }
const prettyFormat = `--pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s"` const prettyFormat = `--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s`

View File

@ -43,8 +43,8 @@ func TestGetCommits(t *testing.T) {
rebaseMode: enums.REBASE_MODE_NONE, rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false}, opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil). ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, "", nil), ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
expectedCommits: []*models.Commit{}, expectedCommits: []*models.Commit{},
expectedError: nil, expectedError: nil,
@ -55,8 +55,8 @@ func TestGetCommits(t *testing.T) {
rebaseMode: enums.REBASE_MODE_NONE, rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "refs/heads/mybranch", IncludeRebaseCommits: false}, opts: GetCommitsOptions{RefName: "refs/heads/mybranch", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git merge-base "refs/heads/mybranch" "mybranch"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil). ExpectGitArgs([]string{"merge-base", "refs/heads/mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
Expect(`git log "refs/heads/mybranch" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, "", nil), ExpectGitArgs([]string{"log", "refs/heads/mybranch", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
expectedCommits: []*models.Commit{}, expectedCommits: []*models.Commit{},
expectedError: nil, expectedError: nil,
@ -69,14 +69,14 @@ func TestGetCommits(t *testing.T) {
mainBranches: []string{"master", "main"}, mainBranches: []string{"master", "main"},
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
// here it's seeing which commits are yet to be pushed // here it's seeing which commits are yet to be pushed
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil). ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
// here it's actually getting all the commits in a formatted form, one per line // here it's actually getting all the commits in a formatted form, one per line
Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, commitsOutput, nil). ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, commitsOutput, nil).
// here it's testing which of the configured main branches exist // here it's testing which of the configured main branches exist
Expect(`git rev-parse --verify --quiet "refs/heads/master"`, "", nil). // this one does ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/master"}, "", nil). // this one does
Expect(`git rev-parse --verify --quiet "refs/heads/main"`, "", errors.New("error")). // this one doesn't ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/main"}, "", errors.New("error")). // this one doesn't
// here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged' // here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged'
Expect(`git merge-base "HEAD" "refs/heads/master"`, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil), ExpectGitArgs([]string{"merge-base", "HEAD", "refs/heads/master"}, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
expectedCommits: []*models.Commit{ expectedCommits: []*models.Commit{
{ {
@ -202,12 +202,12 @@ func TestGetCommits(t *testing.T) {
mainBranches: []string{"master", "main"}, mainBranches: []string{"master", "main"},
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
// here it's seeing which commits are yet to be pushed // here it's seeing which commits are yet to be pushed
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil). ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
// here it's actually getting all the commits in a formatted form, one per line // here it's actually getting all the commits in a formatted form, one per line
Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, singleCommitOutput, nil). ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
// here it's testing which of the configured main branches exist; neither does // here it's testing which of the configured main branches exist; neither does
Expect(`git rev-parse --verify --quiet "refs/heads/master"`, "", errors.New("error")). ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/master"}, "", errors.New("error")).
Expect(`git rev-parse --verify --quiet "refs/heads/main"`, "", errors.New("error")), ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/main"}, "", errors.New("error")),
expectedCommits: []*models.Commit{ expectedCommits: []*models.Commit{
{ {
@ -235,16 +235,16 @@ func TestGetCommits(t *testing.T) {
mainBranches: []string{"master", "main", "develop", "1.0-hotfixes"}, mainBranches: []string{"master", "main", "develop", "1.0-hotfixes"},
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
// here it's seeing which commits are yet to be pushed // here it's seeing which commits are yet to be pushed
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil). ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
// here it's actually getting all the commits in a formatted form, one per line // here it's actually getting all the commits in a formatted form, one per line
Expect(`git log "HEAD" --topo-order --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, singleCommitOutput, nil). ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
// here it's testing which of the configured main branches exist // here it's testing which of the configured main branches exist
Expect(`git rev-parse --verify --quiet "refs/heads/master"`, "", nil). ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/master"}, "", nil).
Expect(`git rev-parse --verify --quiet "refs/heads/main"`, "", errors.New("error")). ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/main"}, "", errors.New("error")).
Expect(`git rev-parse --verify --quiet "refs/heads/develop"`, "", nil). ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/develop"}, "", nil).
Expect(`git rev-parse --verify --quiet "refs/heads/1.0-hotfixes"`, "", nil). ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/heads/1.0-hotfixes"}, "", nil).
// here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged' // here it's seeing where our branch diverged from the master branch so that we can mark that commit and parent commits as 'merged'
Expect(`git merge-base "HEAD" "refs/heads/master" "refs/heads/develop" "refs/heads/1.0-hotfixes"`, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil), ExpectGitArgs([]string{"merge-base", "HEAD", "refs/heads/master", "refs/heads/develop", "refs/heads/1.0-hotfixes"}, "26c07b1ab33860a1a7591a0638f9925ccf497ffa", nil),
expectedCommits: []*models.Commit{ expectedCommits: []*models.Commit{
{ {
@ -270,8 +270,8 @@ func TestGetCommits(t *testing.T) {
rebaseMode: enums.REBASE_MODE_NONE, rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false}, opts: GetCommitsOptions{RefName: "HEAD", IncludeRebaseCommits: false},
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil). ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
Expect(`git log "HEAD" --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --no-show-signature --`, "", nil), ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
expectedCommits: []*models.Commit{}, expectedCommits: []*models.Commit{},
expectedError: nil, expectedError: nil,
@ -282,8 +282,8 @@ func TestGetCommits(t *testing.T) {
rebaseMode: enums.REBASE_MODE_NONE, rebaseMode: enums.REBASE_MODE_NONE,
opts: GetCommitsOptions{RefName: "HEAD", FilterPath: "src"}, opts: GetCommitsOptions{RefName: "HEAD", FilterPath: "src"},
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git merge-base "HEAD" "HEAD"@{u}`, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil). ExpectGitArgs([]string{"merge-base", "HEAD", "HEAD@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
Expect(`git log "HEAD" --oneline --pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s" --abbrev=40 --follow --no-show-signature -- "src"`, "", nil), ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s", "--abbrev=40", "--follow", "--no-show-signature", "--", "src"}, "", nil),
expectedCommits: []*models.Commit{}, expectedCommits: []*models.Commit{},
expectedError: nil, expectedError: nil,

View File

@ -53,7 +53,7 @@ func TestCommitCommitCmdObj(t *testing.T) {
message string message string
configSignoff bool configSignoff bool
configSkipHookPrefix string configSkipHookPrefix string
expected string expectedArgs []string
} }
scenarios := []scenario{ scenarios := []scenario{
@ -62,35 +62,35 @@ func TestCommitCommitCmdObj(t *testing.T) {
message: "test", message: "test",
configSignoff: false, configSignoff: false,
configSkipHookPrefix: "", configSkipHookPrefix: "",
expected: `git commit -m "test"`, expectedArgs: []string{"commit", "-m", "test"},
}, },
{ {
testName: "Commit with --no-verify flag", testName: "Commit with --no-verify flag",
message: "WIP: test", message: "WIP: test",
configSignoff: false, configSignoff: false,
configSkipHookPrefix: "WIP", configSkipHookPrefix: "WIP",
expected: `git commit --no-verify -m "WIP: test"`, expectedArgs: []string{"commit", "--no-verify", "-m", "WIP: test"},
}, },
{ {
testName: "Commit with multiline message", testName: "Commit with multiline message",
message: "line1\nline2", message: "line1\nline2",
configSignoff: false, configSignoff: false,
configSkipHookPrefix: "", configSkipHookPrefix: "",
expected: `git commit -m "line1" -m "line2"`, expectedArgs: []string{"commit", "-m", "line1", "-m", "line2"},
}, },
{ {
testName: "Commit with signoff", testName: "Commit with signoff",
message: "test", message: "test",
configSignoff: true, configSignoff: true,
configSkipHookPrefix: "", configSkipHookPrefix: "",
expected: `git commit --signoff -m "test"`, expectedArgs: []string{"commit", "--signoff", "-m", "test"},
}, },
{ {
testName: "Commit with signoff and no-verify", testName: "Commit with signoff and no-verify",
message: "WIP: test", message: "WIP: test",
configSignoff: true, configSignoff: true,
configSkipHookPrefix: "WIP", configSkipHookPrefix: "WIP",
expected: `git commit --no-verify --signoff -m "WIP: test"`, expectedArgs: []string{"commit", "--no-verify", "--signoff", "-m", "WIP: test"},
}, },
} }
@ -101,10 +101,11 @@ func TestCommitCommitCmdObj(t *testing.T) {
userConfig.Git.Commit.SignOff = s.configSignoff userConfig.Git.Commit.SignOff = s.configSignoff
userConfig.Git.SkipHookPrefix = s.configSkipHookPrefix userConfig.Git.SkipHookPrefix = s.configSkipHookPrefix
instance := buildCommitCommands(commonDeps{userConfig: userConfig}) runner := oscommands.NewFakeRunner(t).ExpectGitArgs(s.expectedArgs, "", nil)
instance := buildCommitCommands(commonDeps{userConfig: userConfig, runner: runner})
cmdStr := instance.CommitCmdObj(s.message).ToString() assert.NoError(t, instance.CommitCmdObj(s.message).Run())
assert.Equal(t, s.expected, cmdStr) runner.CheckForMissingCalls()
}) })
} }
} }
@ -114,7 +115,7 @@ func TestCommitCommitEditorCmdObj(t *testing.T) {
testName string testName string
configSignoff bool configSignoff bool
configVerbose string configVerbose string
expected string expected []string
} }
scenarios := []scenario{ scenarios := []scenario{
@ -122,31 +123,31 @@ func TestCommitCommitEditorCmdObj(t *testing.T) {
testName: "Commit using editor", testName: "Commit using editor",
configSignoff: false, configSignoff: false,
configVerbose: "default", configVerbose: "default",
expected: `git commit`, expected: []string{"commit"},
}, },
{ {
testName: "Commit with --no-verbose flag", testName: "Commit with --no-verbose flag",
configSignoff: false, configSignoff: false,
configVerbose: "never", configVerbose: "never",
expected: `git commit --no-verbose`, expected: []string{"commit", "--no-verbose"},
}, },
{ {
testName: "Commit with --verbose flag", testName: "Commit with --verbose flag",
configSignoff: false, configSignoff: false,
configVerbose: "always", configVerbose: "always",
expected: `git commit --verbose`, expected: []string{"commit", "--verbose"},
}, },
{ {
testName: "Commit with --signoff", testName: "Commit with --signoff",
configSignoff: true, configSignoff: true,
configVerbose: "default", configVerbose: "default",
expected: `git commit --signoff`, expected: []string{"commit", "--signoff"},
}, },
{ {
testName: "Commit with --signoff and --no-verbose", testName: "Commit with --signoff and --no-verbose",
configSignoff: true, configSignoff: true,
configVerbose: "never", configVerbose: "never",
expected: `git commit --signoff --no-verbose`, expected: []string{"commit", "--signoff", "--no-verbose"},
}, },
} }
@ -157,10 +158,11 @@ func TestCommitCommitEditorCmdObj(t *testing.T) {
userConfig.Git.Commit.SignOff = s.configSignoff userConfig.Git.Commit.SignOff = s.configSignoff
userConfig.Git.Commit.Verbose = s.configVerbose userConfig.Git.Commit.Verbose = s.configVerbose
instance := buildCommitCommands(commonDeps{userConfig: userConfig}) runner := oscommands.NewFakeRunner(t).ExpectGitArgs(s.expected, "", nil)
instance := buildCommitCommands(commonDeps{userConfig: userConfig, runner: runner})
cmdStr := instance.CommitEditorCmdObj().ToString() assert.NoError(t, instance.CommitEditorCmdObj().Run())
assert.Equal(t, s.expected, cmdStr) runner.CheckForMissingCalls()
}) })
} }
} }
@ -178,7 +180,7 @@ func TestCommitCreateFixupCommit(t *testing.T) {
testName: "valid case", testName: "valid case",
sha: "12345", sha: "12345",
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git commit --fixup=12345`, "", nil), ExpectGitArgs([]string{"commit", "--fixup=12345"}, "", nil),
test: func(err error) { test: func(err error) {
assert.NoError(t, err) assert.NoError(t, err)
}, },
@ -201,7 +203,7 @@ func TestCommitShowCmdObj(t *testing.T) {
filterPath string filterPath string
contextSize int contextSize int
ignoreWhitespace bool ignoreWhitespace bool
expected string expected []string
} }
scenarios := []scenario{ scenarios := []scenario{
@ -210,28 +212,28 @@ func TestCommitShowCmdObj(t *testing.T) {
filterPath: "", filterPath: "",
contextSize: 3, contextSize: 3,
ignoreWhitespace: false, ignoreWhitespace: false,
expected: "git show --submodule --color=always --unified=3 --stat -p 1234567890", expected: []string{"show", "--submodule", "--color=always", "--unified=3", "--stat", "-p", "1234567890"},
}, },
{ {
testName: "Default case with filter path", testName: "Default case with filter path",
filterPath: "file.txt", filterPath: "file.txt",
contextSize: 3, contextSize: 3,
ignoreWhitespace: false, ignoreWhitespace: false,
expected: `git show --submodule --color=always --unified=3 --stat -p 1234567890 -- "file.txt"`, expected: []string{"show", "--submodule", "--color=always", "--unified=3", "--stat", "-p", "1234567890", "--", "file.txt"},
}, },
{ {
testName: "Show diff with custom context size", testName: "Show diff with custom context size",
filterPath: "", filterPath: "",
contextSize: 77, contextSize: 77,
ignoreWhitespace: false, ignoreWhitespace: false,
expected: "git show --submodule --color=always --unified=77 --stat -p 1234567890", expected: []string{"show", "--submodule", "--color=always", "--unified=77", "--stat", "-p", "1234567890"},
}, },
{ {
testName: "Show diff, ignoring whitespace", testName: "Show diff, ignoring whitespace",
filterPath: "", filterPath: "",
contextSize: 77, contextSize: 77,
ignoreWhitespace: true, ignoreWhitespace: true,
expected: "git show --submodule --color=always --unified=77 --stat -p 1234567890 --ignore-all-space", expected: []string{"show", "--submodule", "--color=always", "--unified=77", "--stat", "-p", "1234567890", "--ignore-all-space"},
}, },
} }
@ -241,10 +243,11 @@ func TestCommitShowCmdObj(t *testing.T) {
userConfig := config.GetDefaultConfig() userConfig := config.GetDefaultConfig()
userConfig.Git.DiffContextSize = s.contextSize userConfig.Git.DiffContextSize = s.contextSize
instance := buildCommitCommands(commonDeps{userConfig: userConfig}) runner := oscommands.NewFakeRunner(t).ExpectGitArgs(s.expected, "", nil)
instance := buildCommitCommands(commonDeps{userConfig: userConfig, runner: runner})
cmdStr := instance.ShowCmdObj("1234567890", s.filterPath, s.ignoreWhitespace).ToString() assert.NoError(t, instance.ShowCmdObj("1234567890", s.filterPath, s.ignoreWhitespace).Run())
assert.Equal(t, s.expected, cmdStr) runner.CheckForMissingCalls()
}) })
} }
} }
@ -283,7 +286,7 @@ Merge pull request #1750 from mark2185/fix-issue-template
s := s s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildCommitCommands(commonDeps{ instance := buildCommitCommands(commonDeps{
runner: oscommands.NewFakeRunner(t).Expect("git rev-list --format=%B --max-count=1 deadbeef", s.input, nil), runner: oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"rev-list", "--format=%B", "--max-count=1", "deadbeef"}, s.input, nil),
}) })
output, err := instance.GetCommitMessage("deadbeef") output, err := instance.GetCommitMessage("deadbeef")
@ -304,14 +307,14 @@ func TestGetCommitMessageFromHistory(t *testing.T) {
scenarios := []scenario{ scenarios := []scenario{
{ {
"Empty message", "Empty message",
oscommands.NewFakeRunner(t).Expect("git log -1 --skip=2 --pretty=%H", "", nil).Expect("git rev-list --format=%B --max-count=1 ", "", nil), oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"log", "-1", "--skip=2", "--pretty=%H"}, "", nil).ExpectGitArgs([]string{"rev-list", "--format=%B", "--max-count=1"}, "", nil),
func(output string, err error) { func(output string, err error) {
assert.Error(t, err) assert.Error(t, err)
}, },
}, },
{ {
"Default case to retrieve a commit in history", "Default case to retrieve a commit in history",
oscommands.NewFakeRunner(t).Expect("git log -1 --skip=2 --pretty=%H", "sha3 \n", nil).Expect("git rev-list --format=%B --max-count=1 sha3", `commit sha3 oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"log", "-1", "--skip=2", "--pretty=%H"}, "sha3 \n", nil).ExpectGitArgs([]string{"rev-list", "--format=%B", "--max-count=1", "sha3"}, `commit sha3
use generics to DRY up context code`, nil), use generics to DRY up context code`, nil),
func(output string, err error) { func(output string, err error) {
assert.NoError(t, err) assert.NoError(t, err)

View File

@ -1,5 +1,7 @@
package git_commands package git_commands
import "github.com/mgutz/str"
type CustomCommands struct { type CustomCommands struct {
*GitCommon *GitCommon
} }
@ -14,5 +16,5 @@ func NewCustomCommands(gitCommon *GitCommon) *CustomCommands {
// If you want to run a new command, try finding a place for it in one of the neighbouring // If you want to run a new command, try finding a place for it in one of the neighbouring
// files, or creating a new BlahCommands struct to hold it. // files, or creating a new BlahCommands struct to hold it.
func (self *CustomCommands) RunWithOutput(cmdStr string) (string, error) { func (self *CustomCommands) RunWithOutput(cmdStr string) (string, error) {
return self.cmd.New(cmdStr).RunWithOutput() return self.cmd.New(str.ToArgv(cmdStr)).RunWithOutput()
} }

View File

@ -0,0 +1,19 @@
package git_commands
import "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
type DiffCommands struct {
*GitCommon
}
func NewDiffCommands(gitCommon *GitCommon) *DiffCommands {
return &DiffCommands{
GitCommon: gitCommon,
}
}
func (self *DiffCommands) DiffCmdObj(diffArgs []string) oscommands.ICmdObj {
return self.cmd.New(
NewGitCmd("diff").Arg("--submodule", "--no-ext-diff", "--color").Arg(diffArgs...).ToArgv(),
)
}

View File

@ -45,7 +45,7 @@ func (self *FileCommands) GetEditCmdStrLegacy(filename string, lineNumber int) (
editor = self.os.Getenv("EDITOR") editor = self.os.Getenv("EDITOR")
} }
if editor == "" { if editor == "" {
if err := self.cmd.New("which vi").DontLog().Run(); err == nil { if err := self.cmd.New([]string{"which", "vi"}).DontLog().Run(); err == nil {
editor = "vi" editor = "vi"
} }
} }

View File

@ -82,14 +82,14 @@ type FileStatus struct {
} }
func (c *FileLoader) gitStatus(opts GitStatusOptions) ([]FileStatus, error) { func (c *FileLoader) gitStatus(opts GitStatusOptions) ([]FileStatus, error) {
cmdStr := NewGitCmd("status"). cmdArgs := NewGitCmd("status").
Arg(opts.UntrackedFilesArg). Arg(opts.UntrackedFilesArg).
Arg("--porcelain"). Arg("--porcelain").
Arg("-z"). Arg("-z").
ArgIf(opts.NoRenames, "--no-renames"). ArgIf(opts.NoRenames, "--no-renames").
ToString() ToArgv()
statusLines, _, err := c.cmd.New(cmdStr).DontLog().RunWithOutputs() statusLines, _, err := c.cmd.New(cmdArgs).DontLog().RunWithOutputs()
if err != nil { if err != nil {
return []FileStatus{}, err return []FileStatus{}, err
} }

View File

@ -20,14 +20,13 @@ func TestFileGetStatusFiles(t *testing.T) {
{ {
"No files found", "No files found",
oscommands.NewFakeRunner(t). oscommands.NewFakeRunner(t).
Expect(`git status --untracked-files=yes --porcelain -z`, "", nil), ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z"}, "", nil),
[]*models.File{}, []*models.File{},
}, },
{ {
"Several files found", "Several files found",
oscommands.NewFakeRunner(t). oscommands.NewFakeRunner(t).
Expect( ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z"},
`git status --untracked-files=yes --porcelain -z`,
"MM file1.txt\x00A file3.txt\x00AM file2.txt\x00?? file4.txt\x00UU file5.txt", "MM file1.txt\x00A file3.txt\x00AM file2.txt\x00?? file4.txt\x00UU file5.txt",
nil, nil,
), ),
@ -102,7 +101,7 @@ func TestFileGetStatusFiles(t *testing.T) {
{ {
"File with new line char", "File with new line char",
oscommands.NewFakeRunner(t). oscommands.NewFakeRunner(t).
Expect(`git status --untracked-files=yes --porcelain -z`, "MM a\nb.txt", nil), ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z"}, "MM a\nb.txt", nil),
[]*models.File{ []*models.File{
{ {
Name: "a\nb.txt", Name: "a\nb.txt",
@ -122,8 +121,7 @@ func TestFileGetStatusFiles(t *testing.T) {
{ {
"Renamed files", "Renamed files",
oscommands.NewFakeRunner(t). oscommands.NewFakeRunner(t).
Expect( ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z"},
`git status --untracked-files=yes --porcelain -z`,
"R after1.txt\x00before1.txt\x00RM after2.txt\x00before2.txt", "R after1.txt\x00before1.txt\x00RM after2.txt\x00before2.txt",
nil, nil,
), ),
@ -161,8 +159,7 @@ func TestFileGetStatusFiles(t *testing.T) {
{ {
"File with arrow in name", "File with arrow in name",
oscommands.NewFakeRunner(t). oscommands.NewFakeRunner(t).
Expect( ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z"},
`git status --untracked-files=yes --porcelain -z`,
`?? a -> b.txt`, `?? a -> b.txt`,
nil, nil,
), ),

View File

@ -27,7 +27,7 @@ func TestEditFileCmdStrLegacy(t *testing.T) {
configEditCommand: "", configEditCommand: "",
configEditCommandTemplate: "{{editor}} {{filename}}", configEditCommandTemplate: "{{editor}} {{filename}}",
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`which vi`, "", errors.New("error")), ExpectArgs([]string{"which", "vi"}, "", errors.New("error")),
getenv: func(env string) string { getenv: func(env string) string {
return "" return ""
}, },
@ -105,7 +105,7 @@ func TestEditFileCmdStrLegacy(t *testing.T) {
configEditCommand: "", configEditCommand: "",
configEditCommandTemplate: "{{editor}} {{filename}}", configEditCommandTemplate: "{{editor}} {{filename}}",
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`which vi`, "/usr/bin/vi", nil), ExpectArgs([]string{"which", "vi"}, "/usr/bin/vi", nil),
getenv: func(env string) string { getenv: func(env string) string {
return "" return ""
}, },
@ -120,7 +120,7 @@ func TestEditFileCmdStrLegacy(t *testing.T) {
configEditCommand: "", configEditCommand: "",
configEditCommandTemplate: "{{editor}} {{filename}}", configEditCommandTemplate: "{{editor}} {{filename}}",
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`which vi`, "/usr/bin/vi", nil), ExpectArgs([]string{"which", "vi"}, "/usr/bin/vi", nil),
getenv: func(env string) string { getenv: func(env string) string {
return "" return ""
}, },

View File

@ -49,13 +49,13 @@ func (self *FlowCommands) FinishCmdObj(branchName string) (oscommands.ICmdObj, e
return nil, errors.New(self.Tr.NotAGitFlowBranch) return nil, errors.New(self.Tr.NotAGitFlowBranch)
} }
cmdStr := NewGitCmd("flow").Arg(branchType, "finish", suffix).ToString() cmdArgs := NewGitCmd("flow").Arg(branchType, "finish", suffix).ToArgv()
return self.cmd.New(cmdStr), nil return self.cmd.New(cmdArgs), nil
} }
func (self *FlowCommands) StartCmdObj(branchType string, name string) oscommands.ICmdObj { func (self *FlowCommands) StartCmdObj(branchType string, name string) oscommands.ICmdObj {
cmdStr := NewGitCmd("flow").Arg(branchType, "start", name).ToString() cmdArgs := NewGitCmd("flow").Arg(branchType, "start", name).ToArgv()
return self.cmd.New(cmdStr) return self.cmd.New(cmdArgs)
} }

View File

@ -12,13 +12,13 @@ func TestStartCmdObj(t *testing.T) {
testName string testName string
branchType string branchType string
name string name string
expected string expected []string
}{ }{
{ {
testName: "basic", testName: "basic",
branchType: "feature", branchType: "feature",
name: "test", name: "test",
expected: "git flow feature start test", expected: []string{"git", "flow", "feature", "start", "test"},
}, },
} }
@ -28,7 +28,7 @@ func TestStartCmdObj(t *testing.T) {
instance := buildFlowCommands(commonDeps{}) instance := buildFlowCommands(commonDeps{})
assert.Equal(t, assert.Equal(t,
instance.StartCmdObj(s.branchType, s.name).ToString(), instance.StartCmdObj(s.branchType, s.name).Args(),
s.expected, s.expected,
) )
}) })
@ -39,28 +39,28 @@ func TestFinishCmdObj(t *testing.T) {
scenarios := []struct { scenarios := []struct {
testName string testName string
branchName string branchName string
expected string expected []string
expectedError string expectedError string
gitConfigMockResponses map[string]string gitConfigMockResponses map[string]string
}{ }{
{ {
testName: "not a git flow branch", testName: "not a git flow branch",
branchName: "mybranch", branchName: "mybranch",
expected: "", expected: nil,
expectedError: "This does not seem to be a git flow branch", expectedError: "This does not seem to be a git flow branch",
gitConfigMockResponses: nil, gitConfigMockResponses: nil,
}, },
{ {
testName: "feature branch without config", testName: "feature branch without config",
branchName: "feature/mybranch", branchName: "feature/mybranch",
expected: "", expected: nil,
expectedError: "This does not seem to be a git flow branch", expectedError: "This does not seem to be a git flow branch",
gitConfigMockResponses: nil, gitConfigMockResponses: nil,
}, },
{ {
testName: "feature branch with config", testName: "feature branch with config",
branchName: "feature/mybranch", branchName: "feature/mybranch",
expected: "git flow feature finish mybranch", expected: []string{"git", "flow", "feature", "finish", "mybranch"},
expectedError: "", expectedError: "",
gitConfigMockResponses: map[string]string{ gitConfigMockResponses: map[string]string{
"--local --get-regexp gitflow.prefix": "gitflow.prefix.feature feature/", "--local --get-regexp gitflow.prefix": "gitflow.prefix.feature feature/",
@ -85,7 +85,7 @@ func TestFinishCmdObj(t *testing.T) {
} }
} else { } else {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, cmd.ToString(), s.expected) assert.Equal(t, cmd.Args(), s.expected)
} }
}) })
} }

View File

@ -49,6 +49,10 @@ func (self *GitCommandBuilder) RepoPath(value string) *GitCommandBuilder {
return self return self
} }
func (self *GitCommandBuilder) ToString() string { func (self *GitCommandBuilder) ToArgv() []string {
return "git " + strings.Join(self.args, " ") return append([]string{"git"}, self.args...)
}
func (self *GitCommandBuilder) ToString() string {
return strings.Join(self.ToArgv(), " ")
} }

View File

@ -8,8 +8,8 @@ import (
func TestGitCommandBuilder(t *testing.T) { func TestGitCommandBuilder(t *testing.T) {
scenarios := []struct { scenarios := []struct {
input string input []string
expected string expected []string
}{ }{
{ {
input: NewGitCmd("push"). input: NewGitCmd("push").
@ -17,36 +17,36 @@ func TestGitCommandBuilder(t *testing.T) {
Arg("--set-upstream"). Arg("--set-upstream").
Arg("origin"). Arg("origin").
Arg("master"). Arg("master").
ToString(), ToArgv(),
expected: "git push --force-with-lease --set-upstream origin master", expected: []string{"git", "push", "--force-with-lease", "--set-upstream", "origin", "master"},
}, },
{ {
input: NewGitCmd("push").ArgIf(true, "--test").ToString(), input: NewGitCmd("push").ArgIf(true, "--test").ToArgv(),
expected: "git push --test", expected: []string{"git", "push", "--test"},
}, },
{ {
input: NewGitCmd("push").ArgIf(false, "--test").ToString(), input: NewGitCmd("push").ArgIf(false, "--test").ToArgv(),
expected: "git push", expected: []string{"git", "push"},
}, },
{ {
input: NewGitCmd("push").ArgIfElse(true, "-b", "-a").ToString(), input: NewGitCmd("push").ArgIfElse(true, "-b", "-a").ToArgv(),
expected: "git push -b", expected: []string{"git", "push", "-b"},
}, },
{ {
input: NewGitCmd("push").ArgIfElse(false, "-a", "-b").ToString(), input: NewGitCmd("push").ArgIfElse(false, "-a", "-b").ToArgv(),
expected: "git push -b", expected: []string{"git", "push", "-b"},
}, },
{ {
input: NewGitCmd("push").Arg("-a", "-b").ToString(), input: NewGitCmd("push").Arg("-a", "-b").ToArgv(),
expected: "git push -a -b", expected: []string{"git", "push", "-a", "-b"},
}, },
{ {
input: NewGitCmd("push").Config("user.name=foo").Config("user.email=bar").ToString(), input: NewGitCmd("push").Config("user.name=foo").Config("user.email=bar").ToArgv(),
expected: "git -c user.email=bar -c user.name=foo push", expected: []string{"git", "-c", "user.email=bar", "-c", "user.name=foo", "push"},
}, },
{ {
input: NewGitCmd("push").RepoPath("a/b/c").ToString(), input: NewGitCmd("push").RepoPath("a/b/c").ToArgv(),
expected: "git -C a/b/c push", expected: []string{"git", "-C", "a/b/c", "push"},
}, },
} }

View File

@ -69,15 +69,15 @@ func (self *PatchCommands) ApplyPatch(patch string, opts ApplyPatchOpts) error {
} }
func (self *PatchCommands) applyPatchFile(filepath string, opts ApplyPatchOpts) error { func (self *PatchCommands) applyPatchFile(filepath string, opts ApplyPatchOpts) error {
cmdStr := NewGitCmd("apply"). cmdArgs := NewGitCmd("apply").
ArgIf(opts.ThreeWay, "--3way"). ArgIf(opts.ThreeWay, "--3way").
ArgIf(opts.Cached, "--cached"). ArgIf(opts.Cached, "--cached").
ArgIf(opts.Index, "--index"). ArgIf(opts.Index, "--index").
ArgIf(opts.Reverse, "--reverse"). ArgIf(opts.Reverse, "--reverse").
Arg(self.cmd.Quote(filepath)). Arg(filepath).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *PatchCommands) SaveTemporaryPatch(patch string) (string, error) { func (self *PatchCommands) SaveTemporaryPatch(patch string) (string, error) {
@ -320,7 +320,7 @@ func (self *PatchCommands) PullPatchIntoNewCommit(commits []*models.Commit, comm
// only some lines of a range of adjacent added lines. To solve this, we // only some lines of a range of adjacent added lines. To solve this, we
// get the diff of HEAD and the original commit and then apply that. // get the diff of HEAD and the original commit and then apply that.
func (self *PatchCommands) diffHeadAgainstCommit(commit *models.Commit) (string, error) { func (self *PatchCommands) diffHeadAgainstCommit(commit *models.Commit) (string, error) {
cmdStr := NewGitCmd("diff").Arg("HEAD.." + commit.Sha).ToString() cmdArgs := NewGitCmd("diff").Arg("HEAD.." + commit.Sha).ToArgv()
return self.cmd.New(cmdStr).RunWithOutput() return self.cmd.New(cmdArgs).RunWithOutput()
} }

View File

@ -3,7 +3,6 @@ package git_commands
import ( import (
"fmt" "fmt"
"os" "os"
"regexp"
"testing" "testing"
"github.com/go-errors/errors" "github.com/go-errors/errors"
@ -11,21 +10,22 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestWorkingTreeApplyPatch(t *testing.T) { func TestPatchApplyPatch(t *testing.T) {
type scenario struct { type scenario struct {
testName string testName string
opts ApplyPatchOpts
runner *oscommands.FakeCmdObjRunner runner *oscommands.FakeCmdObjRunner
test func(error) test func(error)
} }
expectFn := func(regexStr string, errToReturn error) func(cmdObj oscommands.ICmdObj) (string, error) { // expectedArgs excludes the last argument which is an indeterminate filename
expectFn := func(expectedArgs []string, errToReturn error) func(cmdObj oscommands.ICmdObj) (string, error) {
return func(cmdObj oscommands.ICmdObj) (string, error) { return func(cmdObj oscommands.ICmdObj) (string, error) {
re := regexp.MustCompile(regexStr) args := cmdObj.Args()
cmdStr := cmdObj.ToString()
matches := re.FindStringSubmatch(cmdStr)
assert.Equal(t, 2, len(matches), fmt.Sprintf("unexpected command: %s", cmdStr))
filename := matches[1] assert.Equal(t, len(args), len(expectedArgs)+1, fmt.Sprintf("unexpected command: %s", cmdObj.ToString()))
filename := args[len(args)-1]
content, err := os.ReadFile(filename) content, err := os.ReadFile(filename)
assert.NoError(t, err) assert.NoError(t, err)
@ -39,16 +39,18 @@ func TestWorkingTreeApplyPatch(t *testing.T) {
scenarios := []scenario{ scenarios := []scenario{
{ {
testName: "valid case", testName: "valid case",
opts: ApplyPatchOpts{Cached: true},
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectFunc(expectFn(`git apply --cached "(.*)"`, nil)), ExpectFunc(expectFn([]string{"git", "apply", "--cached"}, nil)),
test: func(err error) { test: func(err error) {
assert.NoError(t, err) assert.NoError(t, err)
}, },
}, },
{ {
testName: "command returns error", testName: "command returns error",
opts: ApplyPatchOpts{Cached: true},
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
ExpectFunc(expectFn(`git apply --cached "(.*)"`, errors.New("error"))), ExpectFunc(expectFn([]string{"git", "apply", "--cached"}, errors.New("error"))),
test: func(err error) { test: func(err error) {
assert.Error(t, err) assert.Error(t, err)
}, },
@ -59,7 +61,7 @@ func TestWorkingTreeApplyPatch(t *testing.T) {
s := s s := s
t.Run(s.testName, func(t *testing.T) { t.Run(s.testName, func(t *testing.T) {
instance := buildPatchCommands(commonDeps{runner: s.runner}) instance := buildPatchCommands(commonDeps{runner: s.runner})
s.test(instance.ApplyPatch("test", ApplyPatchOpts{Cached: true})) s.test(instance.ApplyPatch("test", s.opts))
s.runner.CheckForMissingCalls() s.runner.CheckForMissingCalls()
}) })
} }

View File

@ -177,7 +177,7 @@ type PrepareInteractiveRebaseCommandOpts struct {
func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteractiveRebaseCommandOpts) oscommands.ICmdObj { func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteractiveRebaseCommandOpts) oscommands.ICmdObj {
ex := oscommands.GetLazygitPath() ex := oscommands.GetLazygitPath()
cmdStr := NewGitCmd("rebase"). cmdArgs := NewGitCmd("rebase").
Arg("--interactive"). Arg("--interactive").
Arg("--autostash"). Arg("--autostash").
Arg("--keep-empty"). Arg("--keep-empty").
@ -185,16 +185,16 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteract
Arg("--no-autosquash"). Arg("--no-autosquash").
ArgIf(!self.version.IsOlderThan(2, 22, 0), "--rebase-merges"). ArgIf(!self.version.IsOlderThan(2, 22, 0), "--rebase-merges").
Arg(opts.baseShaOrRoot). Arg(opts.baseShaOrRoot).
ToString() ToArgv()
debug := "FALSE" debug := "FALSE"
if self.Debug { if self.Debug {
debug = "TRUE" debug = "TRUE"
} }
self.Log.WithField("command", cmdStr).Debug("RunCommand") self.Log.WithField("command", cmdArgs).Debug("RunCommand")
cmdObj := self.cmd.New(cmdStr) cmdObj := self.cmd.New(cmdArgs)
gitSequenceEditor := ex gitSequenceEditor := ex
@ -227,8 +227,8 @@ func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) e
} }
// Get the sha of the commit we just created // Get the sha of the commit we just created
cmdStr := NewGitCmd("rev-parse").Arg("--verify", "HEAD").ToString() cmdArgs := NewGitCmd("rev-parse").Arg("--verify", "HEAD").ToArgv()
fixupSha, err := self.cmd.New(cmdStr).RunWithOutput() fixupSha, err := self.cmd.New(cmdArgs).RunWithOutput()
if err != nil { if err != nil {
return err return err
} }
@ -265,11 +265,11 @@ func (self *RebaseCommands) SquashAllAboveFixupCommits(commit *models.Commit) er
shaOrRoot = "--root" shaOrRoot = "--root"
} }
cmdStr := NewGitCmd("rebase"). cmdArgs := NewGitCmd("rebase").
Arg("--interactive", "--rebase-merges", "--autostash", "--autosquash", shaOrRoot). Arg("--interactive", "--rebase-merges", "--autostash", "--autosquash", shaOrRoot).
ToString() ToArgv()
return self.runSkipEditorCommand(self.cmd.New(cmdStr)) return self.runSkipEditorCommand(self.cmd.New(cmdArgs))
} }
// BeginInteractiveRebaseForCommit starts an interactive rebase to edit the current // BeginInteractiveRebaseForCommit starts an interactive rebase to edit the current
@ -308,9 +308,9 @@ func (self *RebaseCommands) RebaseBranch(branchName string) error {
} }
func (self *RebaseCommands) GenericMergeOrRebaseActionCmdObj(commandType string, command string) oscommands.ICmdObj { func (self *RebaseCommands) GenericMergeOrRebaseActionCmdObj(commandType string, command string) oscommands.ICmdObj {
cmdStr := NewGitCmd(commandType).Arg("--" + command).ToString() cmdArgs := NewGitCmd(commandType).Arg("--" + command).ToArgv()
return self.cmd.New(cmdStr) return self.cmd.New(cmdArgs)
} }
func (self *RebaseCommands) ContinueRebase() error { func (self *RebaseCommands) ContinueRebase() error {
@ -367,9 +367,9 @@ func (self *RebaseCommands) DiscardOldFileChanges(commits []*models.Commit, comm
} }
// 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)
cmdStr := NewGitCmd("cat-file").Arg("-e", "HEAD^:"+self.cmd.Quote(fileName)).ToString() cmdArgs := NewGitCmd("cat-file").Arg("-e", "HEAD^:"+fileName).ToArgv()
if err := self.cmd.New(cmdStr).Run(); err != nil { if err := self.cmd.New(cmdArgs).Run(); err != nil {
if err := self.os.Remove(fileName); err != nil { if err := self.os.Remove(fileName); err != nil {
return err return err
} }

View File

@ -29,7 +29,7 @@ func TestRebaseRebaseBranch(t *testing.T) {
arg: "master", arg: "master",
gitVersion: &GitVersion{2, 26, 0, ""}, gitVersion: &GitVersion{2, 26, 0, ""},
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git rebase --interactive --autostash --keep-empty --no-autosquash --rebase-merges master`, "", nil), ExpectGitArgs([]string{"rebase", "--interactive", "--autostash", "--keep-empty", "--no-autosquash", "--rebase-merges", "master"}, "", nil),
test: func(err error) { test: func(err error) {
assert.NoError(t, err) assert.NoError(t, err)
}, },
@ -39,7 +39,7 @@ func TestRebaseRebaseBranch(t *testing.T) {
arg: "master", arg: "master",
gitVersion: &GitVersion{2, 26, 0, ""}, gitVersion: &GitVersion{2, 26, 0, ""},
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git rebase --interactive --autostash --keep-empty --no-autosquash --rebase-merges master`, "", errors.New("error")), ExpectGitArgs([]string{"rebase", "--interactive", "--autostash", "--keep-empty", "--no-autosquash", "--rebase-merges", "master"}, "", errors.New("error")),
test: func(err error) { test: func(err error) {
assert.Error(t, err) assert.Error(t, err)
}, },
@ -49,7 +49,7 @@ func TestRebaseRebaseBranch(t *testing.T) {
arg: "master", arg: "master",
gitVersion: &GitVersion{2, 25, 5, ""}, gitVersion: &GitVersion{2, 25, 5, ""},
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git rebase --interactive --autostash --keep-empty --no-autosquash --rebase-merges master`, "", nil), ExpectGitArgs([]string{"rebase", "--interactive", "--autostash", "--keep-empty", "--no-autosquash", "--rebase-merges", "master"}, "", nil),
test: func(err error) { test: func(err error) {
assert.NoError(t, err) assert.NoError(t, err)
}, },
@ -59,7 +59,7 @@ func TestRebaseRebaseBranch(t *testing.T) {
arg: "master", arg: "master",
gitVersion: &GitVersion{2, 21, 9, ""}, gitVersion: &GitVersion{2, 21, 9, ""},
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git rebase --interactive --autostash --keep-empty --no-autosquash master`, "", nil), ExpectGitArgs([]string{"rebase", "--interactive", "--autostash", "--keep-empty", "--no-autosquash", "master"}, "", nil),
test: func(err error) { test: func(err error) {
assert.NoError(t, err) assert.NoError(t, err)
}, },
@ -78,9 +78,9 @@ func TestRebaseRebaseBranch(t *testing.T) {
// TestRebaseSkipEditorCommand confirms that SkipEditorCommand injects // TestRebaseSkipEditorCommand confirms that SkipEditorCommand injects
// environment variables that suppress an interactive editor // environment variables that suppress an interactive editor
func TestRebaseSkipEditorCommand(t *testing.T) { func TestRebaseSkipEditorCommand(t *testing.T) {
commandStr := "git blah" cmdArgs := []string{"git", "blah"}
runner := oscommands.NewFakeRunner(t).ExpectFunc(func(cmdObj oscommands.ICmdObj) (string, error) { runner := oscommands.NewFakeRunner(t).ExpectFunc(func(cmdObj oscommands.ICmdObj) (string, error) {
assert.Equal(t, commandStr, cmdObj.ToString()) assert.EqualValues(t, cmdArgs, cmdObj.Args())
envVars := cmdObj.GetEnvVars() envVars := cmdObj.GetEnvVars()
for _, regexStr := range []string{ for _, regexStr := range []string{
`^VISUAL=.*$`, `^VISUAL=.*$`,
@ -100,7 +100,7 @@ func TestRebaseSkipEditorCommand(t *testing.T) {
return "", nil return "", nil
}) })
instance := buildRebaseCommands(commonDeps{runner: runner}) instance := buildRebaseCommands(commonDeps{runner: runner})
err := instance.runSkipEditorCommand(instance.cmd.New(commandStr)) err := instance.runSkipEditorCommand(instance.cmd.New(cmdArgs))
assert.NoError(t, err) assert.NoError(t, err)
runner.CheckForMissingCalls() runner.CheckForMissingCalls()
} }
@ -149,11 +149,11 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) {
commitIndex: 0, commitIndex: 0,
fileName: "test999.txt", fileName: "test999.txt",
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git rebase --interactive --autostash --keep-empty --no-autosquash --rebase-merges abcdef`, "", nil). ExpectGitArgs([]string{"rebase", "--interactive", "--autostash", "--keep-empty", "--no-autosquash", "--rebase-merges", "abcdef"}, "", nil).
Expect(`git cat-file -e HEAD^:"test999.txt"`, "", nil). ExpectGitArgs([]string{"cat-file", "-e", "HEAD^:test999.txt"}, "", nil).
Expect(`git checkout HEAD^ -- "test999.txt"`, "", nil). ExpectGitArgs([]string{"checkout", "HEAD^", "--", "test999.txt"}, "", nil).
Expect(`git commit --amend --no-edit --allow-empty`, "", nil). ExpectGitArgs([]string{"commit", "--amend", "--no-edit", "--allow-empty"}, "", nil).
Expect(`git rebase --continue`, "", nil), ExpectGitArgs([]string{"rebase", "--continue"}, "", nil),
test: func(err error) { test: func(err error) {
assert.NoError(t, err) assert.NoError(t, err)
}, },

View File

@ -1,7 +1,6 @@
package git_commands package git_commands
import ( import (
"fmt"
"strconv" "strconv"
"strings" "strings"
@ -27,12 +26,15 @@ func NewReflogCommitLoader(common *common.Common, cmd oscommands.ICmdObjBuilder)
func (self *ReflogCommitLoader) GetReflogCommits(lastReflogCommit *models.Commit, filterPath string) ([]*models.Commit, bool, error) { func (self *ReflogCommitLoader) GetReflogCommits(lastReflogCommit *models.Commit, filterPath string) ([]*models.Commit, bool, error) {
commits := make([]*models.Commit, 0) commits := make([]*models.Commit, 0)
filterPathArg := "" cmdArgs := NewGitCmd("log").
if filterPath != "" { Config("log.showSignature=false").
filterPathArg = fmt.Sprintf(" --follow -- %s", self.cmd.Quote(filterPath)) Arg("-g").
} Arg("--abbrev=40").
Arg("--format=%h%x00%ct%x00%gs%x00%p").
ArgIf(filterPath != "", "--follow", "--", filterPath).
ToArgv()
cmdObj := self.cmd.New(fmt.Sprintf(`git -c log.showSignature=false log -g --abbrev=40 --format="%s"%s`, "%h%x00%ct%x00%gs%x00%p", filterPathArg)).DontLog() cmdObj := self.cmd.New(cmdArgs).DontLog()
onlyObtainedNewReflogCommits := false onlyObtainedNewReflogCommits := false
err := cmdObj.RunAndProcessLines(func(line string) (bool, error) { err := cmdObj.RunAndProcessLines(func(line string) (bool, error) {
fields := strings.SplitN(line, "\x00", 4) fields := strings.SplitN(line, "\x00", 4)

View File

@ -34,7 +34,7 @@ func TestGetReflogCommits(t *testing.T) {
{ {
testName: "no reflog entries", testName: "no reflog entries",
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git -c log.showSignature=false log -g --abbrev=40 --format="%h%x00%ct%x00%gs%x00%p"`, "", nil), ExpectGitArgs([]string{"-c", "log.showSignature=false", "log", "-g", "--abbrev=40", "--format=%h%x00%ct%x00%gs%x00%p"}, "", nil),
lastReflogCommit: nil, lastReflogCommit: nil,
expectedCommits: []*models.Commit{}, expectedCommits: []*models.Commit{},
@ -44,7 +44,7 @@ func TestGetReflogCommits(t *testing.T) {
{ {
testName: "some reflog entries", testName: "some reflog entries",
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git -c log.showSignature=false log -g --abbrev=40 --format="%h%x00%ct%x00%gs%x00%p"`, reflogOutput, nil), ExpectGitArgs([]string{"-c", "log.showSignature=false", "log", "-g", "--abbrev=40", "--format=%h%x00%ct%x00%gs%x00%p"}, reflogOutput, nil),
lastReflogCommit: nil, lastReflogCommit: nil,
expectedCommits: []*models.Commit{ expectedCommits: []*models.Commit{
@ -90,7 +90,7 @@ func TestGetReflogCommits(t *testing.T) {
{ {
testName: "some reflog entries where last commit is given", testName: "some reflog entries where last commit is given",
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git -c log.showSignature=false log -g --abbrev=40 --format="%h%x00%ct%x00%gs%x00%p"`, reflogOutput, nil), ExpectGitArgs([]string{"-c", "log.showSignature=false", "log", "-g", "--abbrev=40", "--format=%h%x00%ct%x00%gs%x00%p"}, reflogOutput, nil),
lastReflogCommit: &models.Commit{ lastReflogCommit: &models.Commit{
Sha: "c3c4b66b64c97ffeecde", Sha: "c3c4b66b64c97ffeecde",
@ -114,7 +114,7 @@ func TestGetReflogCommits(t *testing.T) {
{ {
testName: "when passing filterPath", testName: "when passing filterPath",
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git -c log.showSignature=false log -g --abbrev=40 --format="%h%x00%ct%x00%gs%x00%p" --follow -- "path"`, reflogOutput, nil), ExpectGitArgs([]string{"-c", "log.showSignature=false", "log", "-g", "--abbrev=40", "--format=%h%x00%ct%x00%gs%x00%p", "--follow", "--", "path"}, reflogOutput, nil),
lastReflogCommit: &models.Commit{ lastReflogCommit: &models.Commit{
Sha: "c3c4b66b64c97ffeecde", Sha: "c3c4b66b64c97ffeecde",
@ -139,7 +139,7 @@ func TestGetReflogCommits(t *testing.T) {
{ {
testName: "when command returns error", testName: "when command returns error",
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git -c log.showSignature=false log -g --abbrev=40 --format="%h%x00%ct%x00%gs%x00%p"`, "", errors.New("haha")), ExpectGitArgs([]string{"-c", "log.showSignature=false", "log", "-g", "--abbrev=40", "--format=%h%x00%ct%x00%gs%x00%p"}, "", errors.New("haha")),
lastReflogCommit: nil, lastReflogCommit: nil,
filterPath: "", filterPath: "",

View File

@ -15,52 +15,52 @@ func NewRemoteCommands(gitCommon *GitCommon) *RemoteCommands {
} }
func (self *RemoteCommands) AddRemote(name string, url string) error { func (self *RemoteCommands) AddRemote(name string, url string) error {
cmdStr := NewGitCmd("remote"). cmdArgs := NewGitCmd("remote").
Arg("add", self.cmd.Quote(name), self.cmd.Quote(url)). Arg("add", name, url).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *RemoteCommands) RemoveRemote(name string) error { func (self *RemoteCommands) RemoveRemote(name string) error {
cmdStr := NewGitCmd("remote"). cmdArgs := NewGitCmd("remote").
Arg("remove", self.cmd.Quote(name)). Arg("remove", name).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *RemoteCommands) RenameRemote(oldRemoteName string, newRemoteName string) error { func (self *RemoteCommands) RenameRemote(oldRemoteName string, newRemoteName string) error {
cmdStr := NewGitCmd("remote"). cmdArgs := NewGitCmd("remote").
Arg("rename", self.cmd.Quote(oldRemoteName), self.cmd.Quote(newRemoteName)). Arg("rename", oldRemoteName, newRemoteName).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *RemoteCommands) UpdateRemoteUrl(remoteName string, updatedUrl string) error { func (self *RemoteCommands) UpdateRemoteUrl(remoteName string, updatedUrl string) error {
cmdStr := NewGitCmd("remote"). cmdArgs := NewGitCmd("remote").
Arg("set-url", self.cmd.Quote(remoteName), self.cmd.Quote(updatedUrl)). Arg("set-url", remoteName, updatedUrl).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *RemoteCommands) DeleteRemoteBranch(remoteName string, branchName string) error { func (self *RemoteCommands) DeleteRemoteBranch(remoteName string, branchName string) error {
cmdStr := NewGitCmd("push"). cmdArgs := NewGitCmd("push").
Arg(self.cmd.Quote(remoteName), "--delete", self.cmd.Quote(branchName)). Arg(remoteName, "--delete", branchName).
ToString() ToArgv()
return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run() return self.cmd.New(cmdArgs).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
} }
// CheckRemoteBranchExists Returns remote branch // CheckRemoteBranchExists Returns remote branch
func (self *RemoteCommands) CheckRemoteBranchExists(branchName string) bool { func (self *RemoteCommands) CheckRemoteBranchExists(branchName string) bool {
cmdStr := NewGitCmd("show-ref"). cmdArgs := NewGitCmd("show-ref").
Arg("--verify", "--", fmt.Sprintf("refs/remotes/origin/%s", self.cmd.Quote(branchName))). Arg("--verify", "--", fmt.Sprintf("refs/remotes/origin/%s", branchName)).
ToString() ToArgv()
_, err := self.cmd.New(cmdStr).DontLog().RunWithOutput() _, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
return err == nil return err == nil
} }

View File

@ -31,8 +31,8 @@ func NewRemoteLoader(
} }
func (self *RemoteLoader) GetRemotes() ([]*models.Remote, error) { func (self *RemoteLoader) GetRemotes() ([]*models.Remote, error) {
cmdStr := NewGitCmd("branch").Arg("-r").ToString() cmdArgs := NewGitCmd("branch").Arg("-r").ToArgv()
remoteBranchesStr, err := self.cmd.New(cmdStr).DontLog().RunWithOutput() remoteBranchesStr, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -26,84 +26,84 @@ func NewStashCommands(
} }
func (self *StashCommands) DropNewest() error { func (self *StashCommands) DropNewest() error {
cmdStr := NewGitCmd("stash").Arg("drop").ToString() cmdArgs := NewGitCmd("stash").Arg("drop").ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *StashCommands) Drop(index int) error { func (self *StashCommands) Drop(index int) error {
cmdStr := NewGitCmd("stash").Arg("drop", fmt.Sprintf("stash@{%d}", index)). cmdArgs := NewGitCmd("stash").Arg("drop", fmt.Sprintf("stash@{%d}", index)).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *StashCommands) Pop(index int) error { func (self *StashCommands) Pop(index int) error {
cmdStr := NewGitCmd("stash").Arg("pop", fmt.Sprintf("stash@{%d}", index)). cmdArgs := NewGitCmd("stash").Arg("pop", fmt.Sprintf("stash@{%d}", index)).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *StashCommands) Apply(index int) error { func (self *StashCommands) Apply(index int) error {
cmdStr := NewGitCmd("stash").Arg("apply", fmt.Sprintf("stash@{%d}", index)). cmdArgs := NewGitCmd("stash").Arg("apply", fmt.Sprintf("stash@{%d}", index)).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
// Save save stash // Save save stash
func (self *StashCommands) Save(message string) error { func (self *StashCommands) Save(message string) error {
cmdStr := NewGitCmd("stash").Arg("save", self.cmd.Quote(message)). cmdArgs := NewGitCmd("stash").Arg("save", message).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *StashCommands) Store(sha string, message string) error { func (self *StashCommands) Store(sha string, message string) error {
trimmedMessage := strings.Trim(message, " \t") trimmedMessage := strings.Trim(message, " \t")
cmdStr := NewGitCmd("stash").Arg("store", self.cmd.Quote(sha)). cmdArgs := NewGitCmd("stash").Arg("store", sha).
ArgIf(trimmedMessage != "", "-m", self.cmd.Quote(trimmedMessage)). ArgIf(trimmedMessage != "", "-m", trimmedMessage).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *StashCommands) Sha(index int) (string, error) { func (self *StashCommands) Sha(index int) (string, error) {
cmdStr := NewGitCmd("rev-parse"). cmdArgs := NewGitCmd("rev-parse").
Arg(fmt.Sprintf("refs/stash@{%d}", index)). Arg(fmt.Sprintf("refs/stash@{%d}", index)).
ToString() ToArgv()
sha, _, err := self.cmd.New(cmdStr).DontLog().RunWithOutputs() sha, _, err := self.cmd.New(cmdArgs).DontLog().RunWithOutputs()
return strings.Trim(sha, "\r\n"), err return strings.Trim(sha, "\r\n"), err
} }
func (self *StashCommands) ShowStashEntryCmdObj(index int, ignoreWhitespace bool) oscommands.ICmdObj { func (self *StashCommands) ShowStashEntryCmdObj(index int, ignoreWhitespace bool) oscommands.ICmdObj {
cmdStr := NewGitCmd("stash").Arg("show"). cmdArgs := NewGitCmd("stash").Arg("show").
Arg("-p"). Arg("-p").
Arg("--stat"). Arg("--stat").
Arg(fmt.Sprintf("--color=%s", self.UserConfig.Git.Paging.ColorArg)). Arg(fmt.Sprintf("--color=%s", self.UserConfig.Git.Paging.ColorArg)).
Arg(fmt.Sprintf("--unified=%d", self.UserConfig.Git.DiffContextSize)). Arg(fmt.Sprintf("--unified=%d", self.UserConfig.Git.DiffContextSize)).
ArgIf(ignoreWhitespace, "--ignore-all-space"). ArgIf(ignoreWhitespace, "--ignore-all-space").
Arg(fmt.Sprintf("stash@{%d}", index)). Arg(fmt.Sprintf("stash@{%d}", index)).
ToString() ToArgv()
return self.cmd.New(cmdStr).DontLog() return self.cmd.New(cmdArgs).DontLog()
} }
func (self *StashCommands) StashAndKeepIndex(message string) error { func (self *StashCommands) StashAndKeepIndex(message string) error {
cmdStr := NewGitCmd("stash").Arg("save", self.cmd.Quote(message), "--keep-index"). cmdArgs := NewGitCmd("stash").Arg("save", message, "--keep-index").
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *StashCommands) StashUnstagedChanges(message string) error { func (self *StashCommands) StashUnstagedChanges(message string) error {
if err := self.cmd.New( if err := self.cmd.New(
NewGitCmd("commit"). NewGitCmd("commit").
Arg("--no-verify", "-m", self.cmd.Quote("[lazygit] stashing unstaged changes")). Arg("--no-verify", "-m", "[lazygit] stashing unstaged changes").
ToString(), ToArgv(),
).Run(); err != nil { ).Run(); err != nil {
return err return err
} }
@ -112,7 +112,7 @@ func (self *StashCommands) StashUnstagedChanges(message string) error {
} }
if err := self.cmd.New( if err := self.cmd.New(
NewGitCmd("reset").Arg("--soft", "HEAD^").ToString(), NewGitCmd("reset").Arg("--soft", "HEAD^").ToArgv(),
).Run(); err != nil { ).Run(); err != nil {
return err return err
} }
@ -124,7 +124,7 @@ func (self *StashCommands) StashUnstagedChanges(message string) error {
func (self *StashCommands) SaveStagedChanges(message string) error { func (self *StashCommands) SaveStagedChanges(message string) error {
// wrap in 'writing', which uses a mutex // wrap in 'writing', which uses a mutex
if err := self.cmd.New( if err := self.cmd.New(
NewGitCmd("stash").Arg("--keep-index").ToString(), NewGitCmd("stash").Arg("--keep-index").ToArgv(),
).Run(); err != nil { ).Run(); err != nil {
return err return err
} }
@ -134,20 +134,20 @@ func (self *StashCommands) SaveStagedChanges(message string) error {
} }
if err := self.cmd.New( if err := self.cmd.New(
NewGitCmd("stash").Arg("apply", "stash@{1}").ToString(), NewGitCmd("stash").Arg("apply", "stash@{1}").ToArgv(),
).Run(); err != nil { ).Run(); err != nil {
return err return err
} }
if err := self.os.PipeCommands( if err := self.os.PipeCommands(
NewGitCmd("stash").Arg("show", "-p").ToString(), self.cmd.New(NewGitCmd("stash").Arg("show", "-p").ToArgv()),
NewGitCmd("apply").Arg("-R").ToString(), self.cmd.New(NewGitCmd("apply").Arg("-R").ToArgv()),
); err != nil { ); err != nil {
return err return err
} }
if err := self.cmd.New( if err := self.cmd.New(
NewGitCmd("stash").Arg("drop", "stash@{1}").ToString(), NewGitCmd("stash").Arg("drop", "stash@{1}").ToArgv(),
).Run(); err != nil { ).Run(); err != nil {
return err return err
} }
@ -171,8 +171,8 @@ func (self *StashCommands) SaveStagedChanges(message string) error {
func (self *StashCommands) StashIncludeUntrackedChanges(message string) error { func (self *StashCommands) StashIncludeUntrackedChanges(message string) error {
return self.cmd.New( return self.cmd.New(
NewGitCmd("stash").Arg("save", self.cmd.Quote(message), "--include-untracked"). NewGitCmd("stash").Arg("save", message, "--include-untracked").
ToString(), ToArgv(),
).Run() ).Run()
} }

View File

@ -32,8 +32,8 @@ func (self *StashLoader) GetStashEntries(filterPath string) []*models.StashEntry
return self.getUnfilteredStashEntries() return self.getUnfilteredStashEntries()
} }
cmdStr := NewGitCmd("stash").Arg("list", "--name-only").ToString() cmdArgs := NewGitCmd("stash").Arg("list", "--name-only").ToArgv()
rawString, err := self.cmd.New(cmdStr).DontLog().RunWithOutput() rawString, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err != nil { if err != nil {
return self.getUnfilteredStashEntries() return self.getUnfilteredStashEntries()
} }
@ -66,9 +66,9 @@ outer:
} }
func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry { func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry {
cmdStr := NewGitCmd("stash").Arg("list", "-z", "--pretty='%gs'").ToString() cmdArgs := NewGitCmd("stash").Arg("list", "-z", "--pretty=%gs").ToArgv()
rawString, _ := self.cmd.New(cmdStr).DontLog().RunWithOutput() rawString, _ := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
return slices.MapWithIndex(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry { return slices.MapWithIndex(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry {
return self.stashEntryFromLine(line, index) return self.stashEntryFromLine(line, index)
}) })

View File

@ -22,15 +22,14 @@ func TestGetStashEntries(t *testing.T) {
"No stash entries found", "No stash entries found",
"", "",
oscommands.NewFakeRunner(t). oscommands.NewFakeRunner(t).
Expect(`git stash list -z --pretty='%gs'`, "", nil), ExpectGitArgs([]string{"stash", "list", "-z", "--pretty=%gs"}, "", nil),
[]*models.StashEntry{}, []*models.StashEntry{},
}, },
{ {
"Several stash entries found", "Several stash entries found",
"", "",
oscommands.NewFakeRunner(t). oscommands.NewFakeRunner(t).
Expect( ExpectGitArgs([]string{"stash", "list", "-z", "--pretty=%gs"},
`git stash list -z --pretty='%gs'`,
"WIP on add-pkg-commands-test: 55c6af2 increase parallel build\x00WIP on master: bb86a3f update github template\x00", "WIP on add-pkg-commands-test: 55c6af2 increase parallel build\x00WIP on master: bb86a3f update github template\x00",
nil, nil,
), ),

View File

@ -103,7 +103,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
index int index int
contextSize int contextSize int
ignoreWhitespace bool ignoreWhitespace bool
expected string expected []string
} }
scenarios := []scenario{ scenarios := []scenario{
@ -112,21 +112,21 @@ func TestStashStashEntryCmdObj(t *testing.T) {
index: 5, index: 5,
contextSize: 3, contextSize: 3,
ignoreWhitespace: false, ignoreWhitespace: false,
expected: "git stash show -p --stat --color=always --unified=3 stash@{5}", expected: []string{"git", "stash", "show", "-p", "--stat", "--color=always", "--unified=3", "stash@{5}"},
}, },
{ {
testName: "Show diff with custom context size", testName: "Show diff with custom context size",
index: 5, index: 5,
contextSize: 77, contextSize: 77,
ignoreWhitespace: false, ignoreWhitespace: false,
expected: "git stash show -p --stat --color=always --unified=77 stash@{5}", expected: []string{"git", "stash", "show", "-p", "--stat", "--color=always", "--unified=77", "stash@{5}"},
}, },
{ {
testName: "Default case", testName: "Default case",
index: 5, index: 5,
contextSize: 3, contextSize: 3,
ignoreWhitespace: true, ignoreWhitespace: true,
expected: "git stash show -p --stat --color=always --unified=3 --ignore-all-space stash@{5}", expected: []string{"git", "stash", "show", "-p", "--stat", "--color=always", "--unified=3", "--ignore-all-space", "stash@{5}"},
}, },
} }
@ -137,7 +137,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
userConfig.Git.DiffContextSize = s.contextSize userConfig.Git.DiffContextSize = s.contextSize
instance := buildStashCommands(commonDeps{userConfig: userConfig}) instance := buildStashCommands(commonDeps{userConfig: userConfig})
cmdStr := instance.ShowStashEntryCmdObj(s.index, s.ignoreWhitespace).ToString() cmdStr := instance.ShowStashEntryCmdObj(s.index, s.ignoreWhitespace).Args()
assert.Equal(t, s.expected, cmdStr) assert.Equal(t, s.expected, cmdStr)
}) })
} }

View File

@ -57,7 +57,7 @@ func (self *StatusCommands) IsBareRepo() (bool, error) {
func IsBareRepo(osCommand *oscommands.OSCommand) (bool, error) { func IsBareRepo(osCommand *oscommands.OSCommand) (bool, error) {
res, err := osCommand.Cmd.New( res, err := osCommand.Cmd.New(
NewGitCmd("rev-parse").Arg("--is-bare-repository").ToString(), NewGitCmd("rev-parse").Arg("--is-bare-repository").ToArgv(),
).DontLog().RunWithOutput() ).DontLog().RunWithOutput()
if err != nil { if err != nil {
return false, err return false, err

View File

@ -81,27 +81,27 @@ func (self *SubmoduleCommands) Stash(submodule *models.SubmoduleConfig) error {
return nil return nil
} }
cmdStr := NewGitCmd("stash"). cmdArgs := NewGitCmd("stash").
RepoPath(self.cmd.Quote(submodule.Path)). RepoPath(submodule.Path).
Arg("--include-untracked"). Arg("--include-untracked").
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *SubmoduleCommands) Reset(submodule *models.SubmoduleConfig) error { func (self *SubmoduleCommands) Reset(submodule *models.SubmoduleConfig) error {
cmdStr := NewGitCmd("submodule"). cmdArgs := NewGitCmd("submodule").
Arg("update", "--init", "--force", "--", self.cmd.Quote(submodule.Path)). Arg("update", "--init", "--force", "--", submodule.Path).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *SubmoduleCommands) UpdateAll() error { func (self *SubmoduleCommands) UpdateAll() 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
cmdStr := NewGitCmd("submodule").Arg("update", "--force").ToString() cmdArgs := NewGitCmd("submodule").Arg("update", "--force").ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error { func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error {
@ -109,7 +109,7 @@ func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error {
if err := self.cmd.New( if err := self.cmd.New(
NewGitCmd("submodule"). NewGitCmd("submodule").
Arg("deinit", "--force", "--", self.cmd.Quote(submodule.Path)).ToString(), Arg("deinit", "--force", "--", submodule.Path).ToArgv(),
).Run(); err != nil { ).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") {
return err return err
@ -117,23 +117,23 @@ func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error {
if err := self.cmd.New( if err := self.cmd.New(
NewGitCmd("config"). NewGitCmd("config").
Arg("--file", ".gitmodules", "--remove-section", "submodule."+self.cmd.Quote(submodule.Path)). Arg("--file", ".gitmodules", "--remove-section", "submodule."+submodule.Path).
ToString(), ToArgv(),
).Run(); err != nil { ).Run(); err != nil {
return err return err
} }
if err := self.cmd.New( if err := self.cmd.New(
NewGitCmd("config"). NewGitCmd("config").
Arg("--remove-section", "submodule."+self.cmd.Quote(submodule.Path)). Arg("--remove-section", "submodule."+submodule.Path).
ToString(), ToArgv(),
).Run(); err != nil { ).Run(); err != nil {
return err return err
} }
} }
if err := self.cmd.New( if err := self.cmd.New(
NewGitCmd("rm").Arg("--force", "-r", submodule.Path).ToString(), NewGitCmd("rm").Arg("--force", "-r", submodule.Path).ToArgv(),
).Run(); err != nil { ).Run(); err != nil {
// if the directory isn't there then that's fine // if the directory isn't there then that's fine
self.Log.Error(err) self.Log.Error(err)
@ -143,33 +143,33 @@ func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error {
} }
func (self *SubmoduleCommands) Add(name string, path string, url string) error { func (self *SubmoduleCommands) Add(name string, path string, url string) error {
cmdStr := NewGitCmd("submodule"). cmdArgs := NewGitCmd("submodule").
Arg("add"). Arg("add").
Arg("--force"). Arg("--force").
Arg("--name"). Arg("--name").
Arg(self.cmd.Quote(name)). Arg(name).
Arg("--"). Arg("--").
Arg(self.cmd.Quote(url)). Arg(url).
Arg(self.cmd.Quote(path)). Arg(path).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *SubmoduleCommands) UpdateUrl(name string, path string, newUrl string) error { func (self *SubmoduleCommands) UpdateUrl(name string, path string, newUrl string) error {
setUrlCmdStr := NewGitCmd("config"). setUrlCmdStr := NewGitCmd("config").
Arg( Arg(
"--file", ".gitmodules", "submodule."+self.cmd.Quote(name)+".url", self.cmd.Quote(newUrl), "--file", ".gitmodules", "submodule."+name+".url", newUrl,
). ).
ToString() ToArgv()
// 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 := self.cmd.New(setUrlCmdStr).Run(); err != nil { if err := self.cmd.New(setUrlCmdStr).Run(); err != nil {
return err return err
} }
syncCmdStr := NewGitCmd("submodule").Arg("sync", "--", self.cmd.Quote(path)). syncCmdStr := NewGitCmd("submodule").Arg("sync", "--", path).
ToString() ToArgv()
if err := self.cmd.New(syncCmdStr).Run(); err != nil { if err := self.cmd.New(syncCmdStr).Run(); err != nil {
return err return err
@ -179,45 +179,45 @@ func (self *SubmoduleCommands) UpdateUrl(name string, path string, newUrl string
} }
func (self *SubmoduleCommands) Init(path string) error { func (self *SubmoduleCommands) Init(path string) error {
cmdStr := NewGitCmd("submodule").Arg("init", "--", self.cmd.Quote(path)). cmdArgs := NewGitCmd("submodule").Arg("init", "--", path).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *SubmoduleCommands) Update(path string) error { func (self *SubmoduleCommands) Update(path string) error {
cmdStr := NewGitCmd("submodule").Arg("update", "--init", "--", self.cmd.Quote(path)). cmdArgs := NewGitCmd("submodule").Arg("update", "--init", "--", path).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *SubmoduleCommands) BulkInitCmdObj() oscommands.ICmdObj { func (self *SubmoduleCommands) BulkInitCmdObj() oscommands.ICmdObj {
cmdStr := NewGitCmd("submodule").Arg("init"). cmdArgs := NewGitCmd("submodule").Arg("init").
ToString() ToArgv()
return self.cmd.New(cmdStr) return self.cmd.New(cmdArgs)
} }
func (self *SubmoduleCommands) BulkUpdateCmdObj() oscommands.ICmdObj { func (self *SubmoduleCommands) BulkUpdateCmdObj() oscommands.ICmdObj {
cmdStr := NewGitCmd("submodule").Arg("update"). cmdArgs := NewGitCmd("submodule").Arg("update").
ToString() ToArgv()
return self.cmd.New(cmdStr) return self.cmd.New(cmdArgs)
} }
func (self *SubmoduleCommands) ForceBulkUpdateCmdObj() oscommands.ICmdObj { func (self *SubmoduleCommands) ForceBulkUpdateCmdObj() oscommands.ICmdObj {
cmdStr := NewGitCmd("submodule").Arg("update", "--force"). cmdArgs := NewGitCmd("submodule").Arg("update", "--force").
ToString() ToArgv()
return self.cmd.New(cmdStr) return self.cmd.New(cmdArgs)
} }
func (self *SubmoduleCommands) BulkDeinitCmdObj() oscommands.ICmdObj { func (self *SubmoduleCommands) BulkDeinitCmdObj() oscommands.ICmdObj {
cmdStr := NewGitCmd("submodule").Arg("deinit", "--all", "--force"). cmdArgs := NewGitCmd("submodule").Arg("deinit", "--all", "--force").
ToString() ToArgv()
return self.cmd.New(cmdStr) return self.cmd.New(cmdArgs)
} }
func (self *SubmoduleCommands) ResetSubmodules(submodules []*models.SubmoduleConfig) error { func (self *SubmoduleCommands) ResetSubmodules(submodules []*models.SubmoduleConfig) error {

View File

@ -28,14 +28,14 @@ func (self *SyncCommands) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error)
return nil, errors.New(self.Tr.MustSpecifyOriginError) return nil, errors.New(self.Tr.MustSpecifyOriginError)
} }
cmdStr := NewGitCmd("push"). cmdArgs := NewGitCmd("push").
ArgIf(opts.Force, "--force-with-lease"). ArgIf(opts.Force, "--force-with-lease").
ArgIf(opts.SetUpstream, "--set-upstream"). ArgIf(opts.SetUpstream, "--set-upstream").
ArgIf(opts.UpstreamRemote != "", self.cmd.Quote(opts.UpstreamRemote)). ArgIf(opts.UpstreamRemote != "", opts.UpstreamRemote).
ArgIf(opts.UpstreamBranch != "", self.cmd.Quote(opts.UpstreamBranch)). ArgIf(opts.UpstreamBranch != "", opts.UpstreamBranch).
ToString() ToArgv()
cmdObj := self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex) cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest().WithMutex(self.syncMutex)
return cmdObj, nil return cmdObj, nil
} }
@ -56,12 +56,12 @@ type FetchOptions struct {
// Fetch fetch git repo // Fetch fetch git repo
func (self *SyncCommands) Fetch(opts FetchOptions) error { func (self *SyncCommands) Fetch(opts FetchOptions) error {
cmdStr := NewGitCmd("fetch"). cmdArgs := NewGitCmd("fetch").
ArgIf(opts.RemoteName != "", self.cmd.Quote(opts.RemoteName)). ArgIf(opts.RemoteName != "", opts.RemoteName).
ArgIf(opts.BranchName != "", self.cmd.Quote(opts.BranchName)). ArgIf(opts.BranchName != "", opts.BranchName).
ToString() ToArgv()
cmdObj := self.cmd.New(cmdStr) cmdObj := self.cmd.New(cmdArgs)
if opts.Background { if opts.Background {
cmdObj.DontLog().FailOnCredentialRequest() cmdObj.DontLog().FailOnCredentialRequest()
} else { } else {
@ -77,31 +77,31 @@ type PullOptions struct {
} }
func (self *SyncCommands) Pull(opts PullOptions) error { func (self *SyncCommands) Pull(opts PullOptions) error {
cmdStr := NewGitCmd("pull"). cmdArgs := NewGitCmd("pull").
Arg("--no-edit"). Arg("--no-edit").
ArgIf(opts.FastForwardOnly, "--ff-only"). ArgIf(opts.FastForwardOnly, "--ff-only").
ArgIf(opts.RemoteName != "", self.cmd.Quote(opts.RemoteName)). ArgIf(opts.RemoteName != "", opts.RemoteName).
ArgIf(opts.BranchName != "", self.cmd.Quote(opts.BranchName)). ArgIf(opts.BranchName != "", opts.BranchName).
ToString() ToArgv()
// 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.
return self.cmd.New(cmdStr).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest().WithMutex(self.syncMutex).Run() return self.cmd.New(cmdArgs).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
} }
func (self *SyncCommands) FastForward(branchName string, remoteName string, remoteBranchName string) error { func (self *SyncCommands) FastForward(branchName string, remoteName string, remoteBranchName string) error {
cmdStr := NewGitCmd("fetch"). cmdArgs := NewGitCmd("fetch").
Arg(self.cmd.Quote(remoteName)). Arg(remoteName).
Arg(self.cmd.Quote(remoteBranchName) + ":" + self.cmd.Quote(branchName)). Arg(remoteBranchName + ":" + branchName).
ToString() ToArgv()
return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run() return self.cmd.New(cmdArgs).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
} }
func (self *SyncCommands) FetchRemote(remoteName string) error { func (self *SyncCommands) FetchRemote(remoteName string) error {
cmdStr := NewGitCmd("fetch"). cmdArgs := NewGitCmd("fetch").
Arg(self.cmd.Quote(remoteName)). Arg(remoteName).
ToString() ToArgv()
return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run() return self.cmd.New(cmdArgs).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
} }

View File

@ -19,7 +19,7 @@ func TestSyncPush(t *testing.T) {
testName: "Push with force disabled", testName: "Push with force disabled",
opts: PushOpts{Force: false}, opts: PushOpts{Force: false},
test: func(cmdObj oscommands.ICmdObj, err error) { test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.ToString(), "git push") assert.Equal(t, cmdObj.Args(), []string{"git", "push"})
assert.NoError(t, err) assert.NoError(t, err)
}, },
}, },
@ -27,7 +27,7 @@ func TestSyncPush(t *testing.T) {
testName: "Push with force enabled", testName: "Push with force enabled",
opts: PushOpts{Force: true}, opts: PushOpts{Force: true},
test: func(cmdObj oscommands.ICmdObj, err error) { test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.ToString(), "git push --force-with-lease") assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force-with-lease"})
assert.NoError(t, err) assert.NoError(t, err)
}, },
}, },
@ -39,7 +39,7 @@ func TestSyncPush(t *testing.T) {
UpstreamBranch: "master", UpstreamBranch: "master",
}, },
test: func(cmdObj oscommands.ICmdObj, err error) { test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.ToString(), `git push "origin" "master"`) assert.Equal(t, cmdObj.Args(), []string{"git", "push", "origin", "master"})
assert.NoError(t, err) assert.NoError(t, err)
}, },
}, },
@ -52,7 +52,7 @@ func TestSyncPush(t *testing.T) {
SetUpstream: true, SetUpstream: true,
}, },
test: func(cmdObj oscommands.ICmdObj, err error) { test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.ToString(), `git push --set-upstream "origin" "master"`) assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--set-upstream", "origin", "master"})
assert.NoError(t, err) assert.NoError(t, err)
}, },
}, },
@ -65,7 +65,7 @@ func TestSyncPush(t *testing.T) {
SetUpstream: true, SetUpstream: true,
}, },
test: func(cmdObj oscommands.ICmdObj, err error) { test: func(cmdObj oscommands.ICmdObj, err error) {
assert.Equal(t, cmdObj.ToString(), `git push --force-with-lease --set-upstream "origin" "master"`) assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force-with-lease", "--set-upstream", "origin", "master"})
assert.NoError(t, err) assert.NoError(t, err)
}, },
}, },

View File

@ -11,32 +11,32 @@ func NewTagCommands(gitCommon *GitCommon) *TagCommands {
} }
func (self *TagCommands) CreateLightweight(tagName string, ref string) error { func (self *TagCommands) CreateLightweight(tagName string, ref string) error {
cmdStr := NewGitCmd("tag").Arg("--", self.cmd.Quote(tagName)). cmdArgs := NewGitCmd("tag").Arg("--", tagName).
ArgIf(len(ref) > 0, self.cmd.Quote(ref)). ArgIf(len(ref) > 0, ref).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *TagCommands) CreateAnnotated(tagName, ref, msg string) error { func (self *TagCommands) CreateAnnotated(tagName, ref, msg string) error {
cmdStr := NewGitCmd("tag").Arg(self.cmd.Quote(tagName)). cmdArgs := NewGitCmd("tag").Arg(tagName).
ArgIf(len(ref) > 0, self.cmd.Quote(ref)). ArgIf(len(ref) > 0, ref).
Arg("-m", self.cmd.Quote(msg)). Arg("-m", msg).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *TagCommands) Delete(tagName string) error { func (self *TagCommands) Delete(tagName string) error {
cmdStr := NewGitCmd("tag").Arg("-d", self.cmd.Quote(tagName)). cmdArgs := NewGitCmd("tag").Arg("-d", tagName).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *TagCommands) Push(remoteName string, tagName string) error { func (self *TagCommands) Push(remoteName string, tagName string) error {
cmdStr := NewGitCmd("push").Arg(self.cmd.Quote(remoteName), "tag", self.cmd.Quote(tagName)). cmdArgs := NewGitCmd("push").Arg(remoteName, "tag", tagName).
ToString() ToArgv()
return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run() return self.cmd.New(cmdArgs).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run()
} }

View File

@ -28,8 +28,8 @@ func NewTagLoader(
func (self *TagLoader) GetTags() ([]*models.Tag, error) { func (self *TagLoader) 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
cmdStr := NewGitCmd("tag").Arg("--list", "-n", "--sort=-creatordate").ToString() cmdArgs := NewGitCmd("tag").Arg("--list", "-n", "--sort=-creatordate").ToArgv()
tagsOutput, err := self.cmd.New(cmdStr).DontLog().RunWithOutput() tagsOutput, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -26,14 +26,14 @@ func TestGetTags(t *testing.T) {
{ {
testName: "should return no tags if there are none", testName: "should return no tags if there are none",
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git tag --list -n --sort=-creatordate`, "", nil), ExpectGitArgs([]string{"tag", "--list", "-n", "--sort=-creatordate"}, "", nil),
expectedTags: []*models.Tag{}, expectedTags: []*models.Tag{},
expectedError: nil, expectedError: nil,
}, },
{ {
testName: "should return tags if present", testName: "should return tags if present",
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git tag --list -n --sort=-creatordate`, tagsOutput, nil), ExpectGitArgs([]string{"tag", "--list", "-n", "--sort=-creatordate"}, tagsOutput, nil),
expectedTags: []*models.Tag{ expectedTags: []*models.Tag{
{Name: "tag1", Message: "this is my message"}, {Name: "tag1", Message: "this is my message"},
{Name: "tag2", Message: ""}, {Name: "tag2", Message: ""},

View File

@ -15,7 +15,7 @@ type GitVersion struct {
} }
func GetGitVersion(osCommand *oscommands.OSCommand) (*GitVersion, error) { func GetGitVersion(osCommand *oscommands.OSCommand) (*GitVersion, error) {
versionStr, _, err := osCommand.Cmd.New(NewGitCmd("--version").ToString()).RunWithOutputs() versionStr, _, err := osCommand.Cmd.New(NewGitCmd("--version").ToArgv()).RunWithOutputs()
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -5,7 +5,6 @@ import (
"os" "os"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/jesseduffield/generics/slices"
"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"
) )
@ -29,7 +28,7 @@ func NewWorkingTreeCommands(
} }
func (self *WorkingTreeCommands) OpenMergeToolCmdObj() oscommands.ICmdObj { func (self *WorkingTreeCommands) OpenMergeToolCmdObj() oscommands.ICmdObj {
return self.cmd.New(NewGitCmd("mergetool").ToString()) return self.cmd.New(NewGitCmd("mergetool").ToArgv())
} }
func (self *WorkingTreeCommands) OpenMergeTool() error { func (self *WorkingTreeCommands) OpenMergeTool() error {
@ -42,25 +41,21 @@ func (self *WorkingTreeCommands) StageFile(path string) error {
} }
func (self *WorkingTreeCommands) StageFiles(paths []string) error { func (self *WorkingTreeCommands) StageFiles(paths []string) error {
quotedPaths := slices.Map(paths, func(path string) string { cmdArgs := NewGitCmd("add").Arg("--").Arg(paths...).ToArgv()
return self.cmd.Quote(path)
})
cmdStr := NewGitCmd("add").Arg("--").Arg(quotedPaths...).ToString() return self.cmd.New(cmdArgs).Run()
return self.cmd.New(cmdStr).Run()
} }
// StageAll stages all files // StageAll stages all files
func (self *WorkingTreeCommands) StageAll() error { func (self *WorkingTreeCommands) StageAll() error {
cmdStr := NewGitCmd("add").Arg("-A").ToString() cmdArgs := NewGitCmd("add").Arg("-A").ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
// UnstageAll unstages all files // UnstageAll unstages all files
func (self *WorkingTreeCommands) UnstageAll() error { func (self *WorkingTreeCommands) UnstageAll() error {
return self.cmd.New(NewGitCmd("reset").ToString()).Run() return self.cmd.New(NewGitCmd("reset").ToArgv()).Run()
} }
// UnStageFile unstages a file // UnStageFile unstages a file
@ -68,14 +63,14 @@ func (self *WorkingTreeCommands) UnstageAll() error {
// we accept the current name and the previous name // we accept the current name and the previous name
func (self *WorkingTreeCommands) UnStageFile(fileNames []string, reset bool) error { func (self *WorkingTreeCommands) UnStageFile(fileNames []string, reset bool) error {
for _, name := range fileNames { for _, name := range fileNames {
var cmdStr string var cmdArgs []string
if reset { if reset {
cmdStr = NewGitCmd("reset").Arg("HEAD", "--", self.cmd.Quote(name)).ToString() cmdArgs = NewGitCmd("reset").Arg("HEAD", "--", name).ToArgv()
} else { } else {
cmdStr = NewGitCmd("rm").Arg("--cached", "--force", "--", self.cmd.Quote(name)).ToString() cmdArgs = NewGitCmd("rm").Arg("--cached", "--force", "--", name).ToArgv()
} }
err := self.cmd.New(cmdStr).Run() err := self.cmd.New(cmdArgs).Run()
if err != nil { if err != nil {
return err return err
} }
@ -137,17 +132,15 @@ func (self *WorkingTreeCommands) DiscardAllFileChanges(file *models.File) error
return nil return nil
} }
quotedFileName := self.cmd.Quote(file.Name)
if file.ShortStatus == "AA" { if file.ShortStatus == "AA" {
if err := self.cmd.New( if err := self.cmd.New(
NewGitCmd("checkout").Arg("--ours", "--", quotedFileName).ToString(), NewGitCmd("checkout").Arg("--ours", "--", file.Name).ToArgv(),
).Run(); err != nil { ).Run(); err != nil {
return err return err
} }
if err := self.cmd.New( if err := self.cmd.New(
NewGitCmd("add").Arg("--", quotedFileName).ToString(), NewGitCmd("add").Arg("--", file.Name).ToArgv(),
).Run(); err != nil { ).Run(); err != nil {
return err return err
} }
@ -156,14 +149,14 @@ func (self *WorkingTreeCommands) DiscardAllFileChanges(file *models.File) error
if file.ShortStatus == "DU" { if file.ShortStatus == "DU" {
return self.cmd.New( return self.cmd.New(
NewGitCmd("rm").Arg("rm", "--", quotedFileName).ToString(), NewGitCmd("rm").Arg("rm", "--", file.Name).ToArgv(),
).Run() ).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 := self.cmd.New( if err := self.cmd.New(
NewGitCmd("reset").Arg("--", quotedFileName).ToString(), NewGitCmd("reset").Arg("--", file.Name).ToArgv(),
).Run(); err != nil { ).Run(); err != nil {
return err return err
} }
@ -195,9 +188,8 @@ func (self *WorkingTreeCommands) DiscardUnstagedDirChanges(node IFileNode) error
return err return err
} }
quotedPath := self.cmd.Quote(node.GetPath()) cmdArgs := NewGitCmd("checkout").Arg("--", node.GetPath()).ToArgv()
cmdStr := NewGitCmd("checkout").Arg("--", quotedPath).ToString() if err := self.cmd.New(cmdArgs).Run(); err != nil {
if err := self.cmd.New(cmdStr).Run(); err != nil {
return err return err
} }
@ -221,9 +213,8 @@ func (self *WorkingTreeCommands) RemoveUntrackedDirFiles(node IFileNode) error {
// DiscardUnstagedFileChanges directly // DiscardUnstagedFileChanges directly
func (self *WorkingTreeCommands) DiscardUnstagedFileChanges(file *models.File) error { func (self *WorkingTreeCommands) DiscardUnstagedFileChanges(file *models.File) error {
quotedFileName := self.cmd.Quote(file.Name) cmdArgs := NewGitCmd("checkout").Arg("--", file.Name).ToArgv()
cmdStr := NewGitCmd("checkout").Arg("--", quotedFileName).ToString() return self.cmd.New(cmdArgs).Run()
return self.cmd.New(cmdStr).Run()
} }
// Ignore adds a file to the gitignore for the repo // Ignore adds a file to the gitignore for the repo
@ -253,7 +244,7 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain
prevPath := node.GetPreviousPath() prevPath := node.GetPreviousPath()
noIndex := !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile() noIndex := !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile()
cmdStr := NewGitCmd("diff"). cmdArgs := NewGitCmd("diff").
Arg("--submodule"). Arg("--submodule").
Arg("--no-ext-diff"). Arg("--no-ext-diff").
Arg(fmt.Sprintf("--unified=%d", contextSize)). Arg(fmt.Sprintf("--unified=%d", contextSize)).
@ -263,11 +254,11 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain
ArgIf(noIndex, "--no-index"). ArgIf(noIndex, "--no-index").
Arg("--"). Arg("--").
ArgIf(noIndex, "/dev/null"). ArgIf(noIndex, "/dev/null").
Arg(self.cmd.Quote(node.GetPath())). Arg(node.GetPath()).
ArgIf(prevPath != "", self.cmd.Quote(prevPath)). ArgIf(prevPath != "", prevPath).
ToString() ToArgv()
return self.cmd.New(cmdStr).DontLog() return self.cmd.New(cmdArgs).DontLog()
} }
// 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
@ -288,7 +279,7 @@ func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reve
colorArg = "never" colorArg = "never"
} }
cmdStr := NewGitCmd("diff"). cmdArgs := NewGitCmd("diff").
Arg("--submodule"). Arg("--submodule").
Arg("--no-ext-diff"). Arg("--no-ext-diff").
Arg(fmt.Sprintf("--unified=%d", contextSize)). Arg(fmt.Sprintf("--unified=%d", contextSize)).
@ -299,41 +290,41 @@ func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reve
ArgIf(reverse, "-R"). ArgIf(reverse, "-R").
ArgIf(ignoreWhitespace, "--ignore-all-space"). ArgIf(ignoreWhitespace, "--ignore-all-space").
Arg("--"). Arg("--").
Arg(self.cmd.Quote(fileName)). Arg(fileName).
ToString() ToArgv()
return self.cmd.New(cmdStr).DontLog() return self.cmd.New(cmdArgs).DontLog()
} }
// CheckoutFile checks out the file for the given commit // CheckoutFile checks out the file for the given commit
func (self *WorkingTreeCommands) CheckoutFile(commitSha, fileName string) error { func (self *WorkingTreeCommands) CheckoutFile(commitSha, fileName string) error {
cmdStr := NewGitCmd("checkout").Arg(commitSha, "--", self.cmd.Quote(fileName)). cmdArgs := NewGitCmd("checkout").Arg(commitSha, "--", fileName).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
// DiscardAnyUnstagedFileChanges discards any unstaged file changes via `git checkout -- .` // DiscardAnyUnstagedFileChanges discards any unstaged file changes via `git checkout -- .`
func (self *WorkingTreeCommands) DiscardAnyUnstagedFileChanges() error { func (self *WorkingTreeCommands) DiscardAnyUnstagedFileChanges() error {
cmdStr := NewGitCmd("checkout").Arg("--", "."). cmdArgs := NewGitCmd("checkout").Arg("--", ".").
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).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 (self *WorkingTreeCommands) RemoveTrackedFiles(name string) error { func (self *WorkingTreeCommands) RemoveTrackedFiles(name string) error {
cmdStr := NewGitCmd("rm").Arg("-r", "--cached", "--", self.cmd.Quote(name)). cmdArgs := NewGitCmd("rm").Arg("-r", "--cached", "--", name).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
// RemoveUntrackedFiles runs `git clean -fd` // RemoveUntrackedFiles runs `git clean -fd`
func (self *WorkingTreeCommands) RemoveUntrackedFiles() error { func (self *WorkingTreeCommands) RemoveUntrackedFiles() error {
cmdStr := NewGitCmd("clean").Arg("-fd").ToString() cmdArgs := NewGitCmd("clean").Arg("-fd").ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
// ResetAndClean removes all unstaged changes and removes all untracked files // ResetAndClean removes all unstaged changes and removes all untracked files
@ -358,23 +349,23 @@ func (self *WorkingTreeCommands) ResetAndClean() error {
// ResetHardHead runs `git reset --hard` // ResetHardHead runs `git reset --hard`
func (self *WorkingTreeCommands) ResetHard(ref string) error { func (self *WorkingTreeCommands) ResetHard(ref string) error {
cmdStr := NewGitCmd("reset").Arg("--hard", self.cmd.Quote(ref)). cmdArgs := NewGitCmd("reset").Arg("--hard", ref).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
// ResetSoft runs `git reset --soft HEAD` // ResetSoft runs `git reset --soft HEAD`
func (self *WorkingTreeCommands) ResetSoft(ref string) error { func (self *WorkingTreeCommands) ResetSoft(ref string) error {
cmdStr := NewGitCmd("reset").Arg("--soft", self.cmd.Quote(ref)). cmdArgs := NewGitCmd("reset").Arg("--soft", ref).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }
func (self *WorkingTreeCommands) ResetMixed(ref string) error { func (self *WorkingTreeCommands) ResetMixed(ref string) error {
cmdStr := NewGitCmd("reset").Arg("--mixed", self.cmd.Quote(ref)). cmdArgs := NewGitCmd("reset").Arg("--mixed", ref).
ToString() ToArgv()
return self.cmd.New(cmdStr).Run() return self.cmd.New(cmdArgs).Run()
} }

View File

@ -13,7 +13,7 @@ import (
func TestWorkingTreeStageFile(t *testing.T) { func TestWorkingTreeStageFile(t *testing.T) {
runner := oscommands.NewFakeRunner(t). runner := oscommands.NewFakeRunner(t).
Expect(`git add -- "test.txt"`, "", nil) ExpectGitArgs([]string{"add", "--", "test.txt"}, "", nil)
instance := buildWorkingTreeCommands(commonDeps{runner: runner}) instance := buildWorkingTreeCommands(commonDeps{runner: runner})
@ -23,7 +23,7 @@ func TestWorkingTreeStageFile(t *testing.T) {
func TestWorkingTreeStageFiles(t *testing.T) { func TestWorkingTreeStageFiles(t *testing.T) {
runner := oscommands.NewFakeRunner(t). runner := oscommands.NewFakeRunner(t).
Expect(`git add -- "test.txt" "test2.txt"`, "", nil) ExpectGitArgs([]string{"add", "--", "test.txt", "test2.txt"}, "", nil)
instance := buildWorkingTreeCommands(commonDeps{runner: runner}) instance := buildWorkingTreeCommands(commonDeps{runner: runner})
@ -44,7 +44,7 @@ func TestWorkingTreeUnstageFile(t *testing.T) {
testName: "Remove an untracked file from staging", testName: "Remove an untracked file from staging",
reset: false, reset: false,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git rm --cached --force -- "test.txt"`, "", nil), ExpectGitArgs([]string{"rm", "--cached", "--force", "--", "test.txt"}, "", nil),
test: func(err error) { test: func(err error) {
assert.NoError(t, err) assert.NoError(t, err)
}, },
@ -53,7 +53,7 @@ func TestWorkingTreeUnstageFile(t *testing.T) {
testName: "Remove a tracked file from staging", testName: "Remove a tracked file from staging",
reset: true, reset: true,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git reset HEAD -- "test.txt"`, "", nil), ExpectGitArgs([]string{"reset", "HEAD", "--", "test.txt"}, "", nil),
test: func(err error) { test: func(err error) {
assert.NoError(t, err) assert.NoError(t, err)
}, },
@ -90,7 +90,7 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
}, },
removeFile: func(string) error { return nil }, removeFile: func(string) error { return nil },
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git reset -- "test"`, "", errors.New("error")), ExpectGitArgs([]string{"reset", "--", "test"}, "", errors.New("error")),
expectedError: "error", expectedError: "error",
}, },
{ {
@ -115,7 +115,7 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
}, },
removeFile: func(string) error { return nil }, removeFile: func(string) error { return nil },
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git checkout -- "test"`, "", errors.New("error")), ExpectGitArgs([]string{"checkout", "--", "test"}, "", errors.New("error")),
expectedError: "error", expectedError: "error",
}, },
{ {
@ -127,7 +127,7 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
}, },
removeFile: func(string) error { return nil }, removeFile: func(string) error { return nil },
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git checkout -- "test"`, "", nil), ExpectGitArgs([]string{"checkout", "--", "test"}, "", nil),
expectedError: "", expectedError: "",
}, },
{ {
@ -139,8 +139,8 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
}, },
removeFile: func(string) error { return nil }, removeFile: func(string) error { return nil },
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git reset -- "test"`, "", nil). ExpectGitArgs([]string{"reset", "--", "test"}, "", nil).
Expect(`git checkout -- "test"`, "", nil), ExpectGitArgs([]string{"checkout", "--", "test"}, "", nil),
expectedError: "", expectedError: "",
}, },
{ {
@ -152,8 +152,8 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
}, },
removeFile: func(string) error { return nil }, removeFile: func(string) error { return nil },
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git reset -- "test"`, "", nil). ExpectGitArgs([]string{"reset", "--", "test"}, "", nil).
Expect(`git checkout -- "test"`, "", nil), ExpectGitArgs([]string{"checkout", "--", "test"}, "", nil),
expectedError: "", expectedError: "",
}, },
{ {
@ -169,7 +169,7 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
return nil return nil
}, },
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git reset -- "test"`, "", nil), ExpectGitArgs([]string{"reset", "--", "test"}, "", nil),
expectedError: "", expectedError: "",
}, },
{ {
@ -231,7 +231,7 @@ func TestWorkingTreeDiff(t *testing.T) {
ignoreWhitespace: false, ignoreWhitespace: false,
contextSize: 3, contextSize: 3,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git diff --submodule --no-ext-diff --unified=3 --color=always -- "test.txt"`, expectedResult, nil), ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--", "test.txt"}, expectedResult, nil),
}, },
{ {
testName: "cached", testName: "cached",
@ -245,7 +245,7 @@ func TestWorkingTreeDiff(t *testing.T) {
ignoreWhitespace: false, ignoreWhitespace: false,
contextSize: 3, contextSize: 3,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git diff --submodule --no-ext-diff --unified=3 --color=always --cached -- "test.txt"`, expectedResult, nil), ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--cached", "--", "test.txt"}, expectedResult, nil),
}, },
{ {
testName: "plain", testName: "plain",
@ -259,7 +259,7 @@ func TestWorkingTreeDiff(t *testing.T) {
ignoreWhitespace: false, ignoreWhitespace: false,
contextSize: 3, contextSize: 3,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git diff --submodule --no-ext-diff --unified=3 --color=never -- "test.txt"`, expectedResult, nil), ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=never", "--", "test.txt"}, expectedResult, nil),
}, },
{ {
testName: "File not tracked and file has no staged changes", testName: "File not tracked and file has no staged changes",
@ -273,7 +273,7 @@ func TestWorkingTreeDiff(t *testing.T) {
ignoreWhitespace: false, ignoreWhitespace: false,
contextSize: 3, contextSize: 3,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git diff --submodule --no-ext-diff --unified=3 --color=always --no-index -- /dev/null "test.txt"`, expectedResult, nil), ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--no-index", "--", "/dev/null", "test.txt"}, expectedResult, nil),
}, },
{ {
testName: "Default case (ignore whitespace)", testName: "Default case (ignore whitespace)",
@ -287,7 +287,7 @@ func TestWorkingTreeDiff(t *testing.T) {
ignoreWhitespace: true, ignoreWhitespace: true,
contextSize: 3, contextSize: 3,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git diff --submodule --no-ext-diff --unified=3 --color=always --ignore-all-space -- "test.txt"`, expectedResult, nil), ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil),
}, },
{ {
testName: "Show diff with custom context size", testName: "Show diff with custom context size",
@ -301,7 +301,7 @@ func TestWorkingTreeDiff(t *testing.T) {
ignoreWhitespace: false, ignoreWhitespace: false,
contextSize: 17, contextSize: 17,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git diff --submodule --no-ext-diff --unified=17 --color=always -- "test.txt"`, expectedResult, nil), ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=17", "--color=always", "--", "test.txt"}, expectedResult, nil),
}, },
} }
@ -343,7 +343,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
ignoreWhitespace: false, ignoreWhitespace: false,
contextSize: 3, contextSize: 3,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git diff --submodule --no-ext-diff --unified=3 --no-renames --color=always 1234567890 0987654321 -- "test.txt"`, expectedResult, nil), ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
}, },
{ {
testName: "Show diff with custom context size", testName: "Show diff with custom context size",
@ -354,7 +354,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
ignoreWhitespace: false, ignoreWhitespace: false,
contextSize: 123, contextSize: 123,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git diff --submodule --no-ext-diff --unified=123 --no-renames --color=always 1234567890 0987654321 -- "test.txt"`, expectedResult, nil), ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=123", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
}, },
{ {
testName: "Default case (ignore whitespace)", testName: "Default case (ignore whitespace)",
@ -365,7 +365,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
ignoreWhitespace: true, ignoreWhitespace: true,
contextSize: 3, contextSize: 3,
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git diff --submodule --no-ext-diff --unified=3 --no-renames --color=always 1234567890 0987654321 --ignore-all-space -- "test.txt"`, expectedResult, nil), ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil),
}, },
} }
@ -400,7 +400,7 @@ func TestWorkingTreeCheckoutFile(t *testing.T) {
commitSha: "11af912", commitSha: "11af912",
fileName: "test999.txt", fileName: "test999.txt",
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git checkout 11af912 -- "test999.txt"`, "", nil), ExpectGitArgs([]string{"checkout", "11af912", "--", "test999.txt"}, "", nil),
test: func(err error) { test: func(err error) {
assert.NoError(t, err) assert.NoError(t, err)
}, },
@ -410,7 +410,7 @@ func TestWorkingTreeCheckoutFile(t *testing.T) {
commitSha: "11af912", commitSha: "11af912",
fileName: "test999.txt", fileName: "test999.txt",
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git checkout 11af912 -- "test999.txt"`, "", errors.New("error")), ExpectGitArgs([]string{"checkout", "11af912", "--", "test999.txt"}, "", errors.New("error")),
test: func(err error) { test: func(err error) {
assert.Error(t, err) assert.Error(t, err)
}, },
@ -441,7 +441,7 @@ func TestWorkingTreeDiscardUnstagedFileChanges(t *testing.T) {
testName: "valid case", testName: "valid case",
file: &models.File{Name: "test.txt"}, file: &models.File{Name: "test.txt"},
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git checkout -- "test.txt"`, "", nil), ExpectGitArgs([]string{"checkout", "--", "test.txt"}, "", nil),
test: func(err error) { test: func(err error) {
assert.NoError(t, err) assert.NoError(t, err)
}, },
@ -469,7 +469,7 @@ func TestWorkingTreeDiscardAnyUnstagedFileChanges(t *testing.T) {
{ {
testName: "valid case", testName: "valid case",
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git checkout -- .`, "", nil), ExpectGitArgs([]string{"checkout", "--", "."}, "", nil),
test: func(err error) { test: func(err error) {
assert.NoError(t, err) assert.NoError(t, err)
}, },
@ -497,7 +497,7 @@ func TestWorkingTreeRemoveUntrackedFiles(t *testing.T) {
{ {
testName: "valid case", testName: "valid case",
runner: oscommands.NewFakeRunner(t). runner: oscommands.NewFakeRunner(t).
Expect(`git clean -fd`, "", nil), ExpectGitArgs([]string{"clean", "-fd"}, "", nil),
test: func(err error) { test: func(err error) {
assert.NoError(t, err) assert.NoError(t, err)
}, },
@ -527,7 +527,7 @@ func TestWorkingTreeResetHard(t *testing.T) {
"valid case", "valid case",
"HEAD", "HEAD",
oscommands.NewFakeRunner(t). oscommands.NewFakeRunner(t).
Expect(`git reset --hard "HEAD"`, "", nil), ExpectGitArgs([]string{"reset", "--hard", "HEAD"}, "", nil),
func(err error) { func(err error) {
assert.NoError(t, err) assert.NoError(t, err)
}, },

View File

@ -2,7 +2,9 @@ package oscommands
import ( import (
"os/exec" "os/exec"
"strings"
"github.com/samber/lo"
"github.com/sasha-s/go-deadlock" "github.com/sasha-s/go-deadlock"
) )
@ -15,6 +17,9 @@ type ICmdObj interface {
// into a terminal e.g. 'sh -c git commit' as opposed to 'sh -c "git commit"' // into a terminal e.g. 'sh -c git commit' as opposed to 'sh -c "git commit"'
ToString() string ToString() string
// outputs args vector e.g. ["git", "commit", "-m", "my message"]
Args() []string
AddEnvVars(...string) ICmdObj AddEnvVars(...string) ICmdObj
GetEnvVars() []string GetEnvVars() []string
@ -61,7 +66,10 @@ type ICmdObj interface {
} }
type CmdObj struct { type CmdObj struct {
cmdStr string // the secureexec package will swap out the first arg with the full path to the binary,
// so we store these args separately so that ToString() will output the original
args []string
cmd *exec.Cmd cmd *exec.Cmd
runner ICmdObjRunner runner ICmdObjRunner
@ -104,7 +112,19 @@ func (self *CmdObj) GetCmd() *exec.Cmd {
} }
func (self *CmdObj) ToString() string { func (self *CmdObj) ToString() string {
return self.cmdStr // if a given arg contains a space, we need to wrap it in quotes
quotedArgs := lo.Map(self.args, func(arg string, _ int) string {
if strings.Contains(arg, " ") {
return `"` + arg + `"`
}
return arg
})
return strings.Join(quotedArgs, " ")
}
func (self *CmdObj) Args() []string {
return self.args
} }
func (self *CmdObj) AddEnvVars(vars ...string) ICmdObj { func (self *CmdObj) AddEnvVars(vars ...string) ICmdObj {

View File

@ -10,12 +10,10 @@ import (
) )
type ICmdObjBuilder interface { type ICmdObjBuilder interface {
// New returns a new command object based on the string provided // NewFromArgs takes a slice of strings like []string{"git", "commit"} and returns a new command object.
New(cmdStr string) ICmdObj New(args []string) ICmdObj
// NewShell takes a string like `git commit` and returns an executable shell command for it e.g. `sh -c 'git commit'` // 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 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 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 Quote(str string) string
} }
@ -28,24 +26,12 @@ type CmdObjBuilder struct {
// poor man's version of explicitly saying that struct X implements interface Y // poor man's version of explicitly saying that struct X implements interface Y
var _ ICmdObjBuilder = &CmdObjBuilder{} var _ ICmdObjBuilder = &CmdObjBuilder{}
func (self *CmdObjBuilder) New(cmdStr string) ICmdObj { func (self *CmdObjBuilder) New(args []string) ICmdObj {
args := str.ToArgv(cmdStr)
cmd := secureexec.Command(args[0], args[1:]...) cmd := secureexec.Command(args[0], args[1:]...)
cmd.Env = os.Environ() cmd.Env = os.Environ()
return &CmdObj{ return &CmdObj{
cmdStr: cmdStr, args: args,
cmd: cmd,
runner: self.runner,
}
}
func (self *CmdObjBuilder) NewFromArgs(args []string) ICmdObj {
cmd := secureexec.Command(args[0], args[1:]...)
cmd.Env = os.Environ()
return &CmdObj{
cmdStr: strings.Join(args, " "),
cmd: cmd, cmd: cmd,
runner: self.runner, runner: self.runner,
} }
@ -67,8 +53,9 @@ func (self *CmdObjBuilder) NewShell(commandStr string) ICmdObj {
quotedCommand = self.Quote(commandStr) quotedCommand = self.Quote(commandStr)
} }
shellCommand := fmt.Sprintf("%s %s %s", self.platform.Shell, self.platform.ShellArg, quotedCommand) cmdArgs := str.ToArgv(fmt.Sprintf("%s %s %s", self.platform.Shell, self.platform.ShellArg, quotedCommand))
return self.New(shellCommand)
return self.New(cmdArgs)
} }
func (self *CmdObjBuilder) CloneWithNewRunner(decorate func(ICmdObjRunner) ICmdObjRunner) *CmdObjBuilder { func (self *CmdObjBuilder) CloneWithNewRunner(decorate func(ICmdObjRunner) ICmdObjRunner) *CmdObjBuilder {
@ -80,6 +67,9 @@ func (self *CmdObjBuilder) CloneWithNewRunner(decorate func(ICmdObjRunner) ICmdO
} }
} }
const CHARS_REQUIRING_QUOTES = "\"\\$` "
// If you update this method, be sure to update CHARS_REQUIRING_QUOTES
func (self *CmdObjBuilder) Quote(message string) string { func (self *CmdObjBuilder) Quote(message string) string {
var quote string var quote string
if self.platform.OS == "windows" { if self.platform.OS == "windows" {

View File

@ -0,0 +1,33 @@
package oscommands
import (
"testing"
)
func TestCmdObjToString(t *testing.T) {
quote := func(s string) string {
return "\"" + s + "\""
}
scenarios := []struct {
cmdArgs []string
expected string
}{
{
cmdArgs: []string{"git", "push", "myfile.txt"},
expected: "git push myfile.txt",
},
{
cmdArgs: []string{"git", "push", "my file.txt"},
expected: "git push \"my file.txt\"",
},
}
for _, scenario := range scenarios {
cmdObj := &CmdObj{args: scenario.cmdArgs}
actual := cmdObj.ToString()
if actual != scenario.expected {
t.Errorf("Expected %s, got %s", quote(scenario.expected), quote(actual))
}
}
}

View File

@ -4,6 +4,7 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"regexp" "regexp"
"runtime"
"strings" "strings"
"testing" "testing"
@ -91,7 +92,18 @@ func (self *FakeCmdObjRunner) Expect(expectedCmdStr string, output string, err e
func (self *FakeCmdObjRunner) ExpectArgs(expectedArgs []string, output string, err error) *FakeCmdObjRunner { func (self *FakeCmdObjRunner) ExpectArgs(expectedArgs []string, output string, err error) *FakeCmdObjRunner {
self.ExpectFunc(func(cmdObj ICmdObj) (string, error) { self.ExpectFunc(func(cmdObj ICmdObj) (string, error) {
args := cmdObj.GetCmd().Args args := cmdObj.GetCmd().Args
assert.EqualValues(self.t, expectedArgs, args, fmt.Sprintf("command %d did not match expectation", self.expectedCmdIndex+1))
if runtime.GOOS == "windows" {
// thanks to the secureexec package, the first arg is something like
// '"C:\\Program Files\\Git\\mingw64\\bin\\<command>.exe"
// on windows so we'll just ensure it contains our program
assert.Contains(self.t, args[0], expectedArgs[0])
} else {
// first arg is the program name
assert.Equal(self.t, expectedArgs[0], args[0])
}
assert.EqualValues(self.t, expectedArgs[1:], args[1:], fmt.Sprintf("command %d did not match expectation", self.expectedCmdIndex+1))
return output, err return output, err
}) })

View File

@ -10,6 +10,7 @@ import (
"sync" "sync"
"github.com/go-errors/errors" "github.com/go-errors/errors"
"github.com/samber/lo"
"github.com/atotto/clipboard" "github.com/atotto/clipboard"
"github.com/jesseduffield/generics/slices" "github.com/jesseduffield/generics/slices"
@ -187,12 +188,18 @@ func (c *OSCommand) FileExists(path string) (bool, error) {
} }
// PipeCommands runs a heap of commands and pipes their inputs/outputs together like A | B | C // PipeCommands runs a heap of commands and pipes their inputs/outputs together like A | B | C
func (c *OSCommand) PipeCommands(commandStrings ...string) error { func (c *OSCommand) PipeCommands(cmdObjs ...ICmdObj) error {
cmds := slices.Map(commandStrings, func(cmdString string) *exec.Cmd { cmds := slices.Map(cmdObjs, func(cmdObj ICmdObj) *exec.Cmd {
return c.Cmd.New(cmdString).GetCmd() return cmdObj.GetCmd()
}) })
logCmdStr := strings.Join(commandStrings, " | ") logCmdStr := strings.Join(
lo.Map(cmdObjs, func(cmdObj ICmdObj, _ int) string {
return cmdObj.ToString()
}),
" | ",
)
c.LogCommand(logCmdStr, true) c.LogCommand(logCmdStr, true)
for i := 0; i < len(cmds)-1; i++ { for i := 0; i < len(cmds)-1; i++ {

View File

@ -12,20 +12,20 @@ import (
func TestOSCommandRunWithOutput(t *testing.T) { func TestOSCommandRunWithOutput(t *testing.T) {
type scenario struct { type scenario struct {
command string args []string
test func(string, error) test func(string, error)
} }
scenarios := []scenario{ scenarios := []scenario{
{ {
"echo -n '123'", []string{"echo", "-n", "123"},
func(output string, err error) { func(output string, err error) {
assert.NoError(t, err) assert.NoError(t, err)
assert.EqualValues(t, "123", output) assert.EqualValues(t, "123", output)
}, },
}, },
{ {
"rmdir unexisting-folder", []string{"rmdir", "unexisting-folder"},
func(output string, err error) { func(output string, err error) {
assert.Regexp(t, "rmdir.*unexisting-folder.*", err.Error()) assert.Regexp(t, "rmdir.*unexisting-folder.*", err.Error())
}, },
@ -34,7 +34,7 @@ func TestOSCommandRunWithOutput(t *testing.T) {
for _, s := range scenarios { for _, s := range scenarios {
c := NewDummyOSCommand() c := NewDummyOSCommand()
s.test(c.Cmd.New(s.command).RunWithOutput()) s.test(c.Cmd.New(s.args).RunWithOutput())
} }
} }

View File

@ -10,13 +10,13 @@ import (
func TestOSCommandRun(t *testing.T) { func TestOSCommandRun(t *testing.T) {
type scenario struct { type scenario struct {
command string args []string
test func(error) test func(error)
} }
scenarios := []scenario{ scenarios := []scenario{
{ {
"rmdir unexisting-folder", []string{"rmdir", "unexisting-folder"},
func(err error) { func(err error) {
assert.Regexp(t, "rmdir.*unexisting-folder.*", err.Error()) assert.Regexp(t, "rmdir.*unexisting-folder.*", err.Error())
}, },
@ -25,7 +25,7 @@ func TestOSCommandRun(t *testing.T) {
for _, s := range scenarios { for _, s := range scenarios {
c := NewDummyOSCommand() c := NewDummyOSCommand()
s.test(c.Cmd.New(s.command).Run()) s.test(c.Cmd.New(s.args).Run())
} }
} }

View File

@ -1,8 +1,6 @@
package helpers package helpers
import ( import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/modes/diffing" "github.com/jesseduffield/lazygit/pkg/gui/modes/diffing"
"github.com/jesseduffield/lazygit/pkg/gui/types" "github.com/jesseduffield/lazygit/pkg/gui/types"
@ -19,27 +17,29 @@ func NewDiffHelper(c *HelperCommon) *DiffHelper {
} }
} }
func (self *DiffHelper) DiffStr() string { func (self *DiffHelper) DiffArgs() []string {
output := self.c.Modes().Diffing.Ref output := []string{self.c.Modes().Diffing.Ref}
right := self.currentDiffTerminal() right := self.currentDiffTerminal()
if right != "" { if right != "" {
output += " " + right output = append(output, right)
} }
if self.c.Modes().Diffing.Reverse { if self.c.Modes().Diffing.Reverse {
output += " -R" output = append(output, "-R")
} }
if self.c.State().GetIgnoreWhitespaceInDiffView() { if self.c.State().GetIgnoreWhitespaceInDiffView() {
output += " --ignore-all-space" output = append(output, "--ignore-all-space")
} }
output = append(output, "--")
file := self.currentlySelectedFilename() file := self.currentlySelectedFilename()
if file != "" { if file != "" {
output += " -- " + file output = append(output, file)
} else if self.c.Modes().Filtering.Active() { } else if self.c.Modes().Filtering.Active() {
output += " -- " + self.c.Modes().Filtering.GetPath() output = append(output, self.c.Modes().Filtering.GetPath())
} }
return output return output
@ -51,9 +51,7 @@ func (self *DiffHelper) ExitDiffMode() error {
} }
func (self *DiffHelper) RenderDiff() error { func (self *DiffHelper) RenderDiff() error {
cmdObj := self.c.OS().Cmd.New( cmdObj := self.c.Git().Diff.DiffCmdObj(self.DiffArgs())
fmt.Sprintf("git diff --submodule --no-ext-diff --color %s", self.DiffStr()),
)
task := types.NewRunPtyTask(cmdObj.GetCmd()) task := types.NewRunPtyTask(cmdObj.GetCmd())
return self.c.RenderToMainViews(types.RefreshMainOpts{ return self.c.RenderToMainViews(types.RefreshMainOpts{

View File

@ -21,11 +21,10 @@ func NewGpgHelper(c *HelperCommon) *GpgHelper {
// WithWaitingStatus we get stuck there and can't return to lazygit. We could // WithWaitingStatus we get stuck there and can't return to lazygit. We could
// fix this bug, or just stop running subprocesses from within there, given that // fix this bug, or just stop running subprocesses from within there, given that
// we don't need to see a loading status if we're in a subprocess. // we don't need to see a loading status if we're in a subprocess.
// TODO: we shouldn't need to use a shell here, but looks like that NewShell function contains some windows specific quoting stuff. We should centralise that.
func (self *GpgHelper) WithGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error { func (self *GpgHelper) WithGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error {
useSubprocess := self.c.Git().Config.UsingGpg() useSubprocess := self.c.Git().Config.UsingGpg()
if useSubprocess { if useSubprocess {
success, err := self.c.RunSubprocess(self.c.OS().Cmd.NewShell(cmdObj.ToString())) success, err := self.c.RunSubprocess(cmdObj)
if success && onSuccess != nil { if success && onSuccess != nil {
if err := onSuccess(); err != nil { if err := onSuccess(); err != nil {
return err return err
@ -42,8 +41,6 @@ func (self *GpgHelper) WithGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus
} }
func (self *GpgHelper) runAndStream(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error { func (self *GpgHelper) runAndStream(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error {
cmdObj = self.c.OS().Cmd.NewShell(cmdObj.ToString())
return self.c.WithWaitingStatus(waitingStatus, func() error { return self.c.WithWaitingStatus(waitingStatus, func() error {
if err := cmdObj.StreamOutput().Run(); err != nil { if err := cmdObj.StreamOutput().Run(); err != nil {
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC}) _ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})

View File

@ -2,6 +2,7 @@ package helpers
import ( import (
"fmt" "fmt"
"strings"
"github.com/jesseduffield/generics/slices" "github.com/jesseduffield/generics/slices"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums" "github.com/jesseduffield/lazygit/pkg/commands/types/enums"
@ -53,7 +54,7 @@ func (self *ModeHelper) Statuses() []ModeStatus {
fmt.Sprintf( fmt.Sprintf(
"%s %s", "%s %s",
self.c.Tr.LcShowingGitDiff, self.c.Tr.LcShowingGitDiff,
"git diff "+self.diffHelper.DiffStr(), "git diff "+strings.Join(self.diffHelper.DiffArgs(), " "),
), ),
style.FgMagenta, style.FgMagenta,
) )

View File

@ -288,7 +288,7 @@ func (self *LocalCommitsController) doRewordEditor() error {
self.c.LogAction(self.c.Tr.Actions.RewordCommit) self.c.LogAction(self.c.Tr.Actions.RewordCommit)
if self.isHeadCommit() { if self.isHeadCommit() {
return self.c.RunSubprocessAndRefresh(self.c.OS().Cmd.New("git commit --allow-empty --amend --only")) return self.c.RunSubprocessAndRefresh(self.c.Git().Commit.RewordLastCommitInEditorCmdObj())
} }
subProcess, err := self.c.Git().Rebase.RewordCommitInEditor( subProcess, err := self.c.Git().Rebase.RewordCommitInEditor(

View File

@ -11,18 +11,18 @@ type Git struct {
} }
func (self *Git) CurrentBranchName(expectedName string) *Git { func (self *Git) CurrentBranchName(expectedName string) *Git {
return self.assert("git rev-parse --abbrev-ref HEAD", expectedName) return self.assert([]string{"git", "rev-parse", "--abbrev-ref", "HEAD"}, expectedName)
} }
func (self *Git) TagNamesAt(ref string, expectedNames []string) *Git { func (self *Git) TagNamesAt(ref string, expectedNames []string) *Git {
return self.assert(fmt.Sprintf(`git tag --sort=v:refname --points-at "%s"`, ref), strings.Join(expectedNames, "\n")) return self.assert([]string{"git", "tag", "--sort=v:refname", "--points-at", ref}, strings.Join(expectedNames, "\n"))
} }
func (self *Git) assert(cmdStr string, expected string) *Git { func (self *Git) assert(cmdArgs []string, expected string) *Git {
self.assertWithRetries(func() (bool, string) { self.assertWithRetries(func() (bool, string) {
output, err := self.shell.runCommandWithOutput(cmdStr) output, err := self.shell.runCommandWithOutput(cmdArgs)
if err != nil { if err != nil {
return false, fmt.Sprintf("Unexpected error running command: `%s`. Error: %s", cmdStr, err.Error()) return false, fmt.Sprintf("Unexpected error running command: `%v`. Error: %s", cmdArgs, err.Error())
} }
actual := strings.TrimSpace(output) actual := strings.TrimSpace(output)
return actual == expected, fmt.Sprintf("Expected current branch name to be '%s', but got '%s'", expected, actual) return actual == expected, fmt.Sprintf("Expected current branch name to be '%s', but got '%s'", expected, actual)

View File

@ -134,14 +134,14 @@ func buildLazygit() error {
// return nil // return nil
osCommand := oscommands.NewDummyOSCommand() osCommand := oscommands.NewDummyOSCommand()
return osCommand.Cmd.New(fmt.Sprintf( return osCommand.Cmd.New([]string{
"go build -o %s pkg/integration/clients/injector/main.go", tempLazygitPath(), "go", "build", "-o", tempLazygitPath(), filepath.FromSlash("pkg/integration/clients/injector/main.go"),
)).Run() }).Run()
} }
func createFixture(test *IntegrationTest, paths Paths, rootDir string) error { func createFixture(test *IntegrationTest, paths Paths, rootDir string) error {
shell := NewShell(paths.ActualRepo(), func(errorMsg string) { panic(errorMsg) }) shell := NewShell(paths.ActualRepo(), func(errorMsg string) { panic(errorMsg) })
shell.RunCommand("git init -b master") shell.Init("master")
os.Setenv(GIT_CONFIG_GLOBAL_ENV_VAR, globalGitConfigPath(rootDir)) os.Setenv(GIT_CONFIG_GLOBAL_ENV_VAR, globalGitConfigPath(rootDir))
@ -156,7 +156,7 @@ func globalGitConfigPath(rootDir string) string {
func getGitVersion() (*git_commands.GitVersion, error) { func getGitVersion() (*git_commands.GitVersion, error) {
osCommand := oscommands.NewDummyOSCommand() osCommand := oscommands.NewDummyOSCommand()
cmdObj := osCommand.Cmd.New("git --version") cmdObj := osCommand.Cmd.New([]string{"git", "--version"})
versionStr, err := cmdObj.RunWithOutput() versionStr, err := cmdObj.RunWithOutput()
if err != nil { if err != nil {
return nil, err return nil, err
@ -178,9 +178,10 @@ func getLazygitCommand(test *IntegrationTest, paths Paths, rootDir string, sandb
return nil, err return nil, err
} }
cmdStr := fmt.Sprintf("%s -debug --use-config-dir=%s --path=%s %s", tempLazygitPath(), paths.Config(), paths.ActualRepo(), test.ExtraCmdArgs()) cmdArgs := []string{tempLazygitPath(), "-debug", "--use-config-dir=" + paths.Config(), "--path=" + paths.ActualRepo()}
cmdArgs = append(cmdArgs, test.ExtraCmdArgs()...)
cmdObj := osCommand.Cmd.New(cmdStr) cmdObj := osCommand.Cmd.New(cmdArgs)
cmdObj.AddEnvVars(fmt.Sprintf("%s=%s", TEST_NAME_ENV_VAR, test.Name())) cmdObj.AddEnvVars(fmt.Sprintf("%s=%s", TEST_NAME_ENV_VAR, test.Name()))
if sandbox { if sandbox {

View File

@ -2,11 +2,12 @@ package components
import ( import (
"fmt" "fmt"
"io"
"os" "os"
"path/filepath" "path/filepath"
"runtime"
"github.com/jesseduffield/lazygit/pkg/secureexec" "github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/mgutz/str"
) )
// this is for running shell commands, mostly for the sake of setting up the repo // this is for running shell commands, mostly for the sake of setting up the repo
@ -24,31 +25,25 @@ func NewShell(dir string, fail func(string)) *Shell {
return &Shell{dir: dir, fail: fail} return &Shell{dir: dir, fail: fail}
} }
func (self *Shell) RunCommand(cmdStr string) *Shell { func (self *Shell) RunCommand(args []string) *Shell {
args := str.ToArgv(cmdStr) output, err := self.runCommandWithOutput(args)
cmd := secureexec.Command(args[0], args[1:]...)
cmd.Env = os.Environ()
cmd.Dir = self.dir
output, err := cmd.CombinedOutput()
if err != nil { if err != nil {
self.fail(fmt.Sprintf("error running command: %s\n%s", cmdStr, string(output))) self.fail(fmt.Sprintf("error running command: %v\n%s", args, output))
} }
return self return self
} }
// Help files are located at test/files from the root the lazygit repo. func (self *Shell) RunCommandExpectError(args []string) *Shell {
// E.g. You may want to create a pre-commit hook file there, then call this output, err := self.runCommandWithOutput(args)
// function to copy it into your test repo. if err == nil {
func (self *Shell) CopyHelpFile(source string, destination string) *Shell { self.fail(fmt.Sprintf("Expected error running shell command: %v\n%s", args, output))
self.RunCommand(fmt.Sprintf("cp ../../../../../files/%s %s", source, destination)) }
return self return self
} }
func (self *Shell) runCommandWithOutput(cmdStr string) (string, error) { func (self *Shell) runCommandWithOutput(args []string) (string, error) {
args := str.ToArgv(cmdStr)
cmd := secureexec.Command(args[0], args[1:]...) cmd := secureexec.Command(args[0], args[1:]...)
cmd.Env = os.Environ() cmd.Env = os.Environ()
cmd.Dir = self.dir cmd.Dir = self.dir
@ -59,7 +54,14 @@ func (self *Shell) runCommandWithOutput(cmdStr string) (string, error) {
} }
func (self *Shell) RunShellCommand(cmdStr string) *Shell { func (self *Shell) RunShellCommand(cmdStr string) *Shell {
cmd := secureexec.Command("sh", "-c", cmdStr) shell := "sh"
shellArg := "-c"
if runtime.GOOS == "windows" {
shell = "cmd"
shellArg = "/C"
}
cmd := secureexec.Command(shell, shellArg, cmdStr)
cmd.Env = os.Environ() cmd.Env = os.Environ()
cmd.Dir = self.dir cmd.Dir = self.dir
@ -71,19 +73,6 @@ func (self *Shell) RunShellCommand(cmdStr string) *Shell {
return self return self
} }
func (self *Shell) RunShellCommandExpectError(cmdStr string) *Shell {
cmd := secureexec.Command("sh", "-c", cmdStr)
cmd.Env = os.Environ()
cmd.Dir = self.dir
output, err := cmd.CombinedOutput()
if err == nil {
self.fail(fmt.Sprintf("Expected error running shell command: %s\n%s", cmdStr, string(output)))
}
return self
}
func (self *Shell) CreateFile(path string, content string) *Shell { func (self *Shell) CreateFile(path string, content string) *Shell {
fullPath := filepath.Join(self.dir, path) fullPath := filepath.Join(self.dir, path)
err := os.WriteFile(fullPath, []byte(content), 0o644) err := os.WriteFile(fullPath, []byte(content), 0o644)
@ -124,47 +113,47 @@ func (self *Shell) UpdateFile(path string, content string) *Shell {
} }
func (self *Shell) NewBranch(name string) *Shell { func (self *Shell) NewBranch(name string) *Shell {
return self.RunCommand("git checkout -b " + name) return self.RunCommand([]string{"git", "checkout", "-b", name})
} }
func (self *Shell) Checkout(name string) *Shell { func (self *Shell) Checkout(name string) *Shell {
return self.RunCommand("git checkout " + name) return self.RunCommand([]string{"git", "checkout", name})
} }
func (self *Shell) Merge(name string) *Shell { func (self *Shell) Merge(name string) *Shell {
return self.RunCommand("git merge --commit --no-ff " + name) return self.RunCommand([]string{"git", "merge", "--commit", "--no-ff", name})
} }
func (self *Shell) ContinueMerge() *Shell { func (self *Shell) ContinueMerge() *Shell {
return self.RunCommand("git -c core.editor=true merge --continue") return self.RunCommand([]string{"git", "-c", "core.editor=true", "merge", "--continue"})
} }
func (self *Shell) GitAdd(path string) *Shell { func (self *Shell) GitAdd(path string) *Shell {
return self.RunCommand(fmt.Sprintf("git add \"%s\"", path)) return self.RunCommand([]string{"git", "add", path})
} }
func (self *Shell) GitAddAll() *Shell { func (self *Shell) GitAddAll() *Shell {
return self.RunCommand("git add -A") return self.RunCommand([]string{"git", "add", "-A"})
} }
func (self *Shell) Commit(message string) *Shell { func (self *Shell) Commit(message string) *Shell {
return self.RunCommand(fmt.Sprintf("git commit -m \"%s\"", message)) return self.RunCommand([]string{"git", "commit", "-m", message})
} }
func (self *Shell) EmptyCommit(message string) *Shell { func (self *Shell) EmptyCommit(message string) *Shell {
return self.RunCommand(fmt.Sprintf("git commit --allow-empty -m \"%s\"", message)) return self.RunCommand([]string{"git", "commit", "--allow-empty", "-m", message})
} }
func (self *Shell) Revert(ref string) *Shell { func (self *Shell) Revert(ref string) *Shell {
return self.RunCommand(fmt.Sprintf("git revert %s", ref)) return self.RunCommand([]string{"git", "revert", ref})
} }
func (self *Shell) CreateLightweightTag(name string, ref string) *Shell { func (self *Shell) CreateLightweightTag(name string, ref string) *Shell {
return self.RunCommand(fmt.Sprintf("git tag %s %s", name, ref)) return self.RunCommand([]string{"git", "tag", name, ref})
} }
func (self *Shell) CreateAnnotatedTag(name string, message string, ref string) *Shell { func (self *Shell) CreateAnnotatedTag(name string, message string, ref string) *Shell {
return self.RunCommand(fmt.Sprintf("git tag -a %s -m \"%s\" %s", name, message, ref)) return self.RunCommand([]string{"git", "tag", "-a", name, "-m", message, ref})
} }
// convenience method for creating a file and adding it // convenience method for creating a file and adding it
@ -208,60 +197,115 @@ func (self *Shell) CreateNCommitsStartingAt(n, startIndex int) *Shell {
} }
func (self *Shell) StashWithMessage(message string) *Shell { func (self *Shell) StashWithMessage(message string) *Shell {
self.RunCommand(fmt.Sprintf(`git stash -m "%s"`, message)) self.RunCommand([]string{"git", "stash", "-m", message})
return self return self
} }
func (self *Shell) SetConfig(key string, value string) *Shell { func (self *Shell) SetConfig(key string, value string) *Shell {
self.RunCommand(fmt.Sprintf(`git config --local "%s" "%s"`, key, value)) self.RunCommand([]string{"git", "config", "--local", key, value})
return self return self
} }
// creates a clone of the repo in a sibling directory and adds the clone
// as a remote, then fetches it.
func (self *Shell) CloneIntoRemote(name string) *Shell { func (self *Shell) CloneIntoRemote(name string) *Shell {
self.Clone(name) self.Clone(name)
self.RunCommand(fmt.Sprintf("git remote add %s ../%s", name, name)) self.RunCommand([]string{"git", "remote", "add", name, "../" + name})
self.RunCommand(fmt.Sprintf("git fetch %s", name)) self.RunCommand([]string{"git", "fetch", name})
return self return self
} }
func (self *Shell) CloneIntoSubmodule(submoduleName string) *Shell { func (self *Shell) CloneIntoSubmodule(submoduleName string) *Shell {
self.Clone("other_repo") self.Clone("other_repo")
self.RunCommand(fmt.Sprintf("git submodule add ../other_repo %s", submoduleName)) self.RunCommand([]string{"git", "submodule", "add", "../other_repo", submoduleName})
return self return self
} }
// clones repo into a sibling directory
func (self *Shell) Clone(repoName string) *Shell { func (self *Shell) Clone(repoName string) *Shell {
self.RunCommand(fmt.Sprintf("git clone --bare . ../%s", repoName)) self.RunCommand([]string{"git", "clone", "--bare", ".", "../" + repoName})
return self return self
} }
// e.g. branch: 'master', upstream: 'origin/master'
func (self *Shell) SetBranchUpstream(branch string, upstream string) *Shell { func (self *Shell) SetBranchUpstream(branch string, upstream string) *Shell {
self.RunCommand(fmt.Sprintf("git branch --set-upstream-to=%s %s", upstream, branch)) self.RunCommand([]string{"git", "branch", "--set-upstream-to=" + upstream, branch})
return self return self
} }
func (self *Shell) RemoveRemoteBranch(remoteName string, branch string) *Shell { func (self *Shell) RemoveRemoteBranch(remoteName string, branch string) *Shell {
self.RunCommand(fmt.Sprintf("git -C ../%s branch -d %s", remoteName, branch)) self.RunCommand([]string{"git", "-C", "../" + remoteName, "branch", "-d", branch})
return self return self
} }
func (self *Shell) HardReset(ref string) *Shell { func (self *Shell) HardReset(ref string) *Shell {
self.RunCommand(fmt.Sprintf("git reset --hard %s", ref)) self.RunCommand([]string{"git", "reset", "--hard", ref})
return self return self
} }
func (self *Shell) Stash(message string) *Shell { func (self *Shell) Stash(message string) *Shell {
self.RunCommand(fmt.Sprintf("git stash -m \"%s\"", message)) self.RunCommand([]string{"git", "stash", "-m", message})
return self
}
func (self *Shell) StartBisect(good string, bad string) *Shell {
self.RunCommand([]string{"git", "bisect", "start", good, bad})
return self
}
func (self *Shell) Init(mainBranch string) *Shell {
self.RunCommand([]string{"git", "init", "-b", mainBranch})
return self
}
func (self *Shell) MakeExecutable(path string) *Shell {
// 0755 sets the executable permission for owner, and read/execute permissions for group and others
err := os.Chmod(filepath.Join(self.dir, path), 0o755)
if err != nil {
panic(err)
}
return self
}
// Help files are located at test/files from the root the lazygit repo.
// E.g. You may want to create a pre-commit hook file there, then call this
// function to copy it into your test repo.
func (self *Shell) CopyHelpFile(source string, destination string) *Shell {
return self.CopyFile(fmt.Sprintf("../../../../../files/%s", source), destination)
}
func (self *Shell) CopyFile(source string, destination string) *Shell {
absSourcePath := filepath.Join(self.dir, source)
absDestPath := filepath.Join(self.dir, destination)
sourceFile, err := os.Open(absSourcePath)
if err != nil {
self.fail(err.Error())
}
defer sourceFile.Close()
destinationFile, err := os.Create(absDestPath)
if err != nil {
self.fail(err.Error())
}
defer destinationFile.Close()
_, err = io.Copy(destinationFile, sourceFile)
if err != nil {
self.fail(err.Error())
}
// copy permissions to destination file too
sourceFileInfo, err := os.Stat(absSourcePath)
if err != nil {
self.fail(err.Error())
}
err = os.Chmod(absDestPath, sourceFileInfo.Mode())
if err != nil {
self.fail(err.Error())
}
return self return self
} }

View File

@ -22,7 +22,7 @@ const unitTestDescription = "test test"
type IntegrationTest struct { type IntegrationTest struct {
name string name string
description string description string
extraCmdArgs string extraCmdArgs []string
skip bool skip bool
setupRepo func(shell *Shell) setupRepo func(shell *Shell)
setupConfig func(config *config.AppConfig) setupConfig func(config *config.AppConfig)
@ -45,7 +45,7 @@ type NewIntegrationTestArgs struct {
// runs the test // runs the test
Run func(t *TestDriver, keys config.KeybindingConfig) Run func(t *TestDriver, keys config.KeybindingConfig)
// additional args passed to lazygit // additional args passed to lazygit
ExtraCmdArgs string ExtraCmdArgs []string
// for when a test is flakey // for when a test is flakey
Skip bool Skip bool
// to run a test only on certain git versions // to run a test only on certain git versions
@ -128,7 +128,7 @@ func (self *IntegrationTest) Description() string {
return self.description return self.description
} }
func (self *IntegrationTest) ExtraCmdArgs() string { func (self *IntegrationTest) ExtraCmdArgs() []string {
return self.extraCmdArgs return self.extraCmdArgs
} }

View File

@ -7,7 +7,7 @@ import (
var Basic = NewIntegrationTest(NewIntegrationTestArgs{ var Basic = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Start a git bisect to find a bad commit", Description: "Start a git bisect to find a bad commit",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {
shell. shell.

View File

@ -7,7 +7,7 @@ import (
var FromOtherBranch = NewIntegrationTest(NewIntegrationTestArgs{ var FromOtherBranch = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Opening lazygit when bisect has been started from another branch. There's an issue where we don't reselect the current branch if we mark the current branch as bad so this test side-steps that problem", Description: "Opening lazygit when bisect has been started from another branch. There's an issue where we don't reselect the current branch if we mark the current branch as bad so this test side-steps that problem",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {
shell. shell.
@ -15,7 +15,7 @@ var FromOtherBranch = NewIntegrationTest(NewIntegrationTestArgs{
NewBranch("other"). NewBranch("other").
CreateNCommits(10). CreateNCommits(10).
Checkout("master"). Checkout("master").
RunCommand("git bisect start other~2 other~5") StartBisect("other~2", "other~5")
}, },
SetupConfig: func(cfg *config.AppConfig) {}, SetupConfig: func(cfg *config.AppConfig) {},
Run: func(t *TestDriver, keys config.KeybindingConfig) { Run: func(t *TestDriver, keys config.KeybindingConfig) {

View File

@ -7,7 +7,7 @@ import (
var CheckoutByName = NewIntegrationTest(NewIntegrationTestArgs{ var CheckoutByName = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Try to checkout branch by name. Verify that it also works on the branch with the special name @.", Description: "Try to checkout branch by name. Verify that it also works on the branch with the special name @.",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var CreateTag = NewIntegrationTest(NewIntegrationTestArgs{ var CreateTag = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Create a new tag on branch", Description: "Create a new tag on branch",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var Delete = NewIntegrationTest(NewIntegrationTestArgs{ var Delete = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Try to delete the checked out branch first (to no avail), and then delete another branch.", Description: "Try to delete the checked out branch first (to no avail), and then delete another branch.",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var DetachedHead = NewIntegrationTest(NewIntegrationTestArgs{ var DetachedHead = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Create a new branch on detached head", Description: "Create a new branch on detached head",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var OpenWithCliArg = NewIntegrationTest(NewIntegrationTestArgs{ var OpenWithCliArg = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Open straight to branches panel using a CLI arg", Description: "Open straight to branches panel using a CLI arg",
ExtraCmdArgs: "branch", ExtraCmdArgs: []string{"branch"},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -8,7 +8,7 @@ import (
var Rebase = NewIntegrationTest(NewIntegrationTestArgs{ var Rebase = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Rebase onto another branch, deal with the conflicts.", Description: "Rebase onto another branch, deal with the conflicts.",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -8,7 +8,7 @@ import (
var RebaseAndDrop = NewIntegrationTest(NewIntegrationTestArgs{ var RebaseAndDrop = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Rebase onto another branch, deal with the conflicts. Also mark a commit to be dropped before continuing.", Description: "Rebase onto another branch, deal with the conflicts. Also mark a commit to be dropped before continuing.",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var RebaseDoesNotAutosquash = NewIntegrationTest(NewIntegrationTestArgs{ var RebaseDoesNotAutosquash = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Rebase a branch that has fixups onto another branch, and verify that the fixups are not squashed even if rebase.autoSquash is enabled globally.", Description: "Rebase a branch that has fixups onto another branch, and verify that the fixups are not squashed even if rebase.autoSquash is enabled globally.",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var Reset = NewIntegrationTest(NewIntegrationTestArgs{ var Reset = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Hard reset to another branch", Description: "Hard reset to another branch",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var ResetUpstream = NewIntegrationTest(NewIntegrationTestArgs{ var ResetUpstream = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Reset the upstream of a branch", Description: "Reset the upstream of a branch",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var SetUpstream = NewIntegrationTest(NewIntegrationTestArgs{ var SetUpstream = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Set the upstream of a branch", Description: "Set the upstream of a branch",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var Suggestions = NewIntegrationTest(NewIntegrationTestArgs{ var Suggestions = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Checking out a branch with name suggestions", Description: "Checking out a branch with name suggestions",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var CherryPick = NewIntegrationTest(NewIntegrationTestArgs{ var CherryPick = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Cherry pick commits from the subcommits view, without conflicts", Description: "Cherry pick commits from the subcommits view, without conflicts",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -8,7 +8,7 @@ import (
var CherryPickConflicts = NewIntegrationTest(NewIntegrationTestArgs{ var CherryPickConflicts = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Cherry pick commits from the subcommits view, with conflicts", Description: "Cherry pick commits from the subcommits view, with conflicts",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var Amend = NewIntegrationTest(NewIntegrationTestArgs{ var Amend = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Amends the last commit from the files panel", Description: "Amends the last commit from the files panel",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var Commit = NewIntegrationTest(NewIntegrationTestArgs{ var Commit = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Staging a couple files and committing", Description: "Staging a couple files and committing",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var CommitMultiline = NewIntegrationTest(NewIntegrationTestArgs{ var CommitMultiline = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Commit with a multi-line commit message", Description: "Commit with a multi-line commit message",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var CommitWipWithPrefix = NewIntegrationTest(NewIntegrationTestArgs{ var CommitWipWithPrefix = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Commit with skip hook and config commitPrefix is defined. Prefix is ignored when creating WIP commits.", Description: "Commit with skip hook and config commitPrefix is defined. Prefix is ignored when creating WIP commits.",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(testConfig *config.AppConfig) { SetupConfig: func(testConfig *config.AppConfig) {
testConfig.UserConfig.Git.CommitPrefixes = map[string]config.CommitPrefixConfig{"repo": {Pattern: "^\\w+\\/(\\w+-\\w+).*", Replace: "[$1]: "}} testConfig.UserConfig.Git.CommitPrefixes = map[string]config.CommitPrefixConfig{"repo": {Pattern: "^\\w+\\/(\\w+-\\w+).*", Replace: "[$1]: "}}

View File

@ -7,7 +7,7 @@ import (
var CommitWithPrefix = NewIntegrationTest(NewIntegrationTestArgs{ var CommitWithPrefix = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Commit with defined config commitPrefix", Description: "Commit with defined config commitPrefix",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(testConfig *config.AppConfig) { SetupConfig: func(testConfig *config.AppConfig) {
testConfig.UserConfig.Git.CommitPrefixes = map[string]config.CommitPrefixConfig{"repo": {Pattern: "^\\w+\\/(\\w+-\\w+).*", Replace: "[$1]: "}} testConfig.UserConfig.Git.CommitPrefixes = map[string]config.CommitPrefixConfig{"repo": {Pattern: "^\\w+\\/(\\w+-\\w+).*", Replace: "[$1]: "}}

View File

@ -7,7 +7,7 @@ import (
var CreateTag = NewIntegrationTest(NewIntegrationTestArgs{ var CreateTag = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Create a new tag on a commit", Description: "Create a new tag on a commit",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var DiscardOldFileChange = NewIntegrationTest(NewIntegrationTestArgs{ var DiscardOldFileChange = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Discarding a single file from an old commit (does rebase in background to remove the file but retain the other one)", Description: "Discarding a single file from an old commit (does rebase in background to remove the file but retain the other one)",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var History = NewIntegrationTest(NewIntegrationTestArgs{ var History = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Cycling through commit message history in the commit message panel", Description: "Cycling through commit message history in the commit message panel",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var HistoryComplex = NewIntegrationTest(NewIntegrationTestArgs{ var HistoryComplex = NewIntegrationTest(NewIntegrationTestArgs{
Description: "More complex flow for cycling commit message history", Description: "More complex flow for cycling commit message history",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var NewBranch = NewIntegrationTest(NewIntegrationTestArgs{ var NewBranch = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Creating a new branch from a commit", Description: "Creating a new branch from a commit",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var ResetAuthor = NewIntegrationTest(NewIntegrationTestArgs{ var ResetAuthor = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Reset author on a commit", Description: "Reset author on a commit",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var Revert = NewIntegrationTest(NewIntegrationTestArgs{ var Revert = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Reverts a commit", Description: "Reverts a commit",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -8,7 +8,7 @@ import (
var RevertMerge = NewIntegrationTest(NewIntegrationTestArgs{ var RevertMerge = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Reverts a merge commit and chooses to revert to the parent commit", Description: "Reverts a merge commit and chooses to revert to the parent commit",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var Reword = NewIntegrationTest(NewIntegrationTestArgs{ var Reword = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Staging a couple files and committing", Description: "Staging a couple files and committing",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var Search = NewIntegrationTest(NewIntegrationTestArgs{ var Search = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Search for a commit", Description: "Search for a commit",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var SetAuthor = NewIntegrationTest(NewIntegrationTestArgs{ var SetAuthor = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Set author on a commit", Description: "Set author on a commit",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var StageRangeOfLines = NewIntegrationTest(NewIntegrationTestArgs{ var StageRangeOfLines = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Staging a range of lines", Description: "Staging a range of lines",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var Staged = NewIntegrationTest(NewIntegrationTestArgs{ var Staged = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Staging a couple files, going in the staged files menu, unstaging a line then committing", Description: "Staging a couple files, going in the staged files menu, unstaging a line then committing",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var StagedWithoutHooks = NewIntegrationTest(NewIntegrationTestArgs{ var StagedWithoutHooks = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Staging a couple files, going in the staged files menu, unstaging a line then committing without pre-commit hooks", Description: "Staging a couple files, going in the staged files menu, unstaging a line then committing without pre-commit hooks",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var Unstaged = NewIntegrationTest(NewIntegrationTestArgs{ var Unstaged = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Staging a couple files, going in the unstaged files menu, staging a line and committing", Description: "Staging a couple files, going in the unstaged files menu, staging a line and committing",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var RemoteNamedStar = NewIntegrationTest(NewIntegrationTestArgs{ var RemoteNamedStar = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Having a config remote.*", Description: "Having a config remote.*",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {
shell. shell.

View File

@ -8,7 +8,7 @@ import (
var Filter = NewIntegrationTest(NewIntegrationTestArgs{ var Filter = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Ensures that when there are merge conflicts, the files panel only shows conflicted files", Description: "Ensures that when there are merge conflicts, the files panel only shows conflicted files",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -8,7 +8,7 @@ import (
var ResolveExternally = NewIntegrationTest(NewIntegrationTestArgs{ var ResolveExternally = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Ensures that when merge conflicts are resolved outside of lazygit, lazygit prompts you to continue", Description: "Ensures that when merge conflicts are resolved outside of lazygit, lazygit prompts you to continue",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -8,7 +8,7 @@ import (
var ResolveMultipleFiles = NewIntegrationTest(NewIntegrationTestArgs{ var ResolveMultipleFiles = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Ensures that upon resolving conflicts for one file, the next file is selected", Description: "Ensures that upon resolving conflicts for one file, the next file is selected",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -8,7 +8,7 @@ import (
var UndoChooseHunk = NewIntegrationTest(NewIntegrationTestArgs{ var UndoChooseHunk = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Chooses a hunk when resolving a merge conflict and then undoes the choice", Description: "Chooses a hunk when resolving a merge conflict and then undoes the choice",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupConfig: func(config *config.AppConfig) {}, SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {

View File

@ -7,7 +7,7 @@ import (
var BasicCmdAtRuntime = NewIntegrationTest(NewIntegrationTestArgs{ var BasicCmdAtRuntime = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Using a custom command provided at runtime to create a new file", Description: "Using a custom command provided at runtime to create a new file",
ExtraCmdArgs: "", ExtraCmdArgs: []string{},
Skip: false, Skip: false,
SetupRepo: func(shell *Shell) { SetupRepo: func(shell *Shell) {
shell.EmptyCommit("blah") shell.EmptyCommit("blah")

Some files were not shown because too many files have changed in this diff Show More