diff --git a/pkg/commands/git_commands/bisect.go b/pkg/commands/git_commands/bisect.go index b09a50bf5..898151d9c 100644 --- a/pkg/commands/git_commands/bisect.go +++ b/pkg/commands/git_commands/bisect.go @@ -1,7 +1,6 @@ package git_commands import ( - "fmt" "os" "path/filepath" "strings" @@ -98,13 +97,15 @@ func (self *BisectCommands) GetInfo() *BisectInfo { } func (self *BisectCommands) Reset() error { - return self.cmd.New("git bisect reset").StreamOutput().Run() + cmdStr := NewGitCmd("bisect").Arg("reset").ToString() + + return self.cmd.New(cmdStr).StreamOutput().Run() } func (self *BisectCommands) Mark(ref string, term string) error { - return self.cmd.New( - fmt.Sprintf("git bisect %s %s", term, ref), - ). + cmdStr := NewGitCmd("bisect").Arg(term, ref).ToString() + + return self.cmd.New(cmdStr). IgnoreEmptyError(). StreamOutput(). Run() @@ -115,7 +116,9 @@ func (self *BisectCommands) Skip(ref string) error { } func (self *BisectCommands) Start() error { - return self.cmd.New("git bisect start").StreamOutput().Run() + cmdStr := NewGitCmd("bisect").Arg("start").ToString() + + return self.cmd.New(cmdStr).StreamOutput().Run() } // tells us whether we've found our problem commit(s). We return a string slice of @@ -137,7 +140,8 @@ func (self *BisectCommands) IsDone() (bool, []string, error) { done := false candidates := []string{} - err := self.cmd.New(fmt.Sprintf("git rev-list %s", newSha)).RunAndProcessLines(func(line string) (bool, error) { + cmdStr := NewGitCmd("rev-list").Arg(newSha).ToString() + err := self.cmd.New(cmdStr).RunAndProcessLines(func(line string) (bool, error) { sha := strings.TrimSpace(line) if status, ok := info.statusMap[sha]; ok { @@ -167,9 +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 // render the commits from the bad commit. func (self *BisectCommands) ReachableFromStart(bisectInfo *BisectInfo) bool { - err := self.cmd.New( - fmt.Sprintf("git merge-base --is-ancestor %s %s", bisectInfo.GetNewSha(), bisectInfo.GetStartSha()), - ).DontLog().Run() + cmdStr := NewGitCmd("merge-base"). + Arg("--is-ancestor", bisectInfo.GetNewSha(), bisectInfo.GetStartSha()). + ToString() + + err := self.cmd.New(cmdStr).DontLog().Run() return err == nil } diff --git a/pkg/commands/git_commands/branch.go b/pkg/commands/git_commands/branch.go index a71e365ea..0952e59e1 100644 --- a/pkg/commands/git_commands/branch.go +++ b/pkg/commands/git_commands/branch.go @@ -20,12 +20,20 @@ func NewBranchCommands(gitCommon *GitCommon) *BranchCommands { // New creates a new branch func (self *BranchCommands) New(name string, base string) error { - return self.cmd.New(fmt.Sprintf("git checkout -b %s %s", self.cmd.Quote(name), self.cmd.Quote(base))).Run() + cmdStr := NewGitCmd("checkout"). + Arg("-b", self.cmd.Quote(name), self.cmd.Quote(base)). + ToString() + + return self.cmd.New(cmdStr).Run() } // CurrentBranchInfo get the current branch information. func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) { - branchName, err := self.cmd.New("git symbolic-ref --short HEAD").DontLog().RunWithOutput() + branchName, err := self.cmd.New( + NewGitCmd("symbolic-ref"). + Arg("--short", "HEAD"). + ToString(), + ).DontLog().RunWithOutput() if err == nil && branchName != "HEAD\n" { trimmedBranchName := strings.TrimSpace(branchName) return BranchInfo{ @@ -34,7 +42,11 @@ func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) { DetachedHead: false, }, nil } - output, err := self.cmd.New(`git branch --points-at=HEAD --format="%(HEAD)%00%(objectname)%00%(refname)"`).DontLog().RunWithOutput() + output, err := self.cmd.New( + NewGitCmd("branch"). + Arg("--points-at=HEAD", "--format=\"%(HEAD)%00%(objectname)%00%(refname)\""). + ToString(), + ).DontLog().RunWithOutput() if err != nil { return BranchInfo{}, err } @@ -59,13 +71,12 @@ func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) { // Delete delete branch func (self *BranchCommands) Delete(branch string, force bool) error { - command := "git branch -d" + cmdStr := NewGitCmd("branch"). + ArgIfElse(force, "-D", "-d"). + Arg(self.cmd.Quote(branch)). + ToString() - if force { - command = "git branch -D" - } - - return self.cmd.New(fmt.Sprintf("%s %s", command, self.cmd.Quote(branch))).Run() + return self.cmd.New(cmdStr).Run() } // Checkout checks out a branch (or commit), with --force if you set the force arg to true @@ -75,12 +86,12 @@ type CheckoutOptions struct { } func (self *BranchCommands) Checkout(branch string, options CheckoutOptions) error { - forceArg := "" - if options.Force { - forceArg = " --force" - } + cmdStr := NewGitCmd("checkout"). + ArgIf(options.Force, "--force"). + Arg(self.cmd.Quote(branch)). + ToString() - return self.cmd.New(fmt.Sprintf("git checkout%s %s", forceArg, self.cmd.Quote(branch))). + return self.cmd.New(cmdStr). // prevents git from prompting us for input which would freeze the program // TODO: see if this is actually needed here AddEnvVars("GIT_TERMINAL_PROMPT=0"). @@ -104,15 +115,27 @@ func (self *BranchCommands) GetGraphCmdObj(branchName string) oscommands.ICmdObj } func (self *BranchCommands) SetCurrentBranchUpstream(remoteName string, remoteBranchName string) error { - return self.cmd.New(fmt.Sprintf("git branch --set-upstream-to=%s/%s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName))).Run() + cmdStr := NewGitCmd("branch"). + Arg(fmt.Sprintf("--set-upstream-to=%s/%s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName))). + ToString() + + return self.cmd.New(cmdStr).Run() } func (self *BranchCommands) SetUpstream(remoteName string, remoteBranchName string, branchName string) error { - return self.cmd.New(fmt.Sprintf("git branch --set-upstream-to=%s/%s %s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName), self.cmd.Quote(branchName))).Run() + cmdStr := NewGitCmd("branch"). + Arg(fmt.Sprintf("--set-upstream-to=%s/%s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName))). + Arg(self.cmd.Quote(branchName)). + ToString() + + return self.cmd.New(cmdStr).Run() } func (self *BranchCommands) UnsetUpstream(branchName string) error { - return self.cmd.New(fmt.Sprintf("git branch --unset-upstream %s", self.cmd.Quote(branchName))).Run() + cmdStr := NewGitCmd("branch").Arg("--unset-upstream", self.cmd.Quote(branchName)). + ToString() + + return self.cmd.New(cmdStr).Run() } func (self *BranchCommands) GetCurrentBranchUpstreamDifferenceCount() (string, string) { @@ -126,29 +149,49 @@ func (self *BranchCommands) GetUpstreamDifferenceCount(branchName string) (strin // GetCommitDifferences checks how many pushables/pullables there are for the // current branch func (self *BranchCommands) GetCommitDifferences(from, to string) (string, string) { - command := "git rev-list %s..%s --count" - pushableCount, err := self.cmd.New(fmt.Sprintf(command, to, from)).DontLog().RunWithOutput() + pushableCount, err := self.countDifferences(to, from) if err != nil { return "?", "?" } - pullableCount, err := self.cmd.New(fmt.Sprintf(command, from, to)).DontLog().RunWithOutput() + pullableCount, err := self.countDifferences(from, to) if err != nil { return "?", "?" } return strings.TrimSpace(pushableCount), strings.TrimSpace(pullableCount) } +func (self *BranchCommands) countDifferences(from, to string) (string, error) { + cmdStr := NewGitCmd("rev-list"). + Arg(fmt.Sprintf("%s..%s", from, to)). + Arg("--count"). + ToString() + + return self.cmd.New(cmdStr).DontLog().RunWithOutput() +} + func (self *BranchCommands) IsHeadDetached() bool { - err := self.cmd.New("git symbolic-ref -q HEAD").DontLog().Run() + cmdStr := NewGitCmd("symbolic-ref").Arg("-q", "HEAD").ToString() + + err := self.cmd.New(cmdStr).DontLog().Run() return err != nil } func (self *BranchCommands) Rename(oldName string, newName string) error { - return self.cmd.New(fmt.Sprintf("git branch --move %s %s", self.cmd.Quote(oldName), self.cmd.Quote(newName))).Run() + cmdStr := NewGitCmd("branch"). + Arg("--move", self.cmd.Quote(oldName), self.cmd.Quote(newName)). + ToString() + + return self.cmd.New(cmdStr).Run() } func (self *BranchCommands) GetRawBranches() (string, error) { - return self.cmd.New(`git for-each-ref --sort=-committerdate --format="%(HEAD)%00%(refname:short)%00%(upstream:short)%00%(upstream:track)" refs/heads`).DontLog().RunWithOutput() + cmdStr := NewGitCmd("for-each-ref"). + Arg("--sort=-committerdate"). + Arg(`--format="%(HEAD)%00%(refname:short)%00%(upstream:short)%00%(upstream:track)"`). + Arg("refs/heads"). + ToString() + + return self.cmd.New(cmdStr).DontLog().RunWithOutput() } type MergeOpts struct { @@ -156,15 +199,12 @@ type MergeOpts struct { } func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error { - mergeArg := "" - if self.UserConfig.Git.Merging.Args != "" { - mergeArg = " " + self.UserConfig.Git.Merging.Args - } - - command := fmt.Sprintf("git merge --no-edit%s %s", mergeArg, self.cmd.Quote(branchName)) - if opts.FastForwardOnly { - command = fmt.Sprintf("%s --ff-only", command) - } + command := NewGitCmd("merge"). + Arg("--no-edit"). + ArgIf(self.UserConfig.Git.Merging.Args != "", self.UserConfig.Git.Merging.Args). + ArgIf(opts.FastForwardOnly, "--ff-only"). + Arg(self.cmd.Quote(branchName)). + ToString() return self.cmd.New(command).Run() } diff --git a/pkg/commands/git_commands/commit.go b/pkg/commands/git_commands/commit.go index 0c5008e38..bca68c3ac 100644 --- a/pkg/commands/git_commands/commit.go +++ b/pkg/commands/git_commands/commit.go @@ -22,18 +22,27 @@ func NewCommitCommands(gitCommon *GitCommon) *CommitCommands { // ResetAuthor resets the author of the topmost commit func (self *CommitCommands) ResetAuthor() error { - return self.cmd.New("git commit --allow-empty --only --no-edit --amend --reset-author").Run() + cmdStr := NewGitCmd("commit"). + Arg("--allow-empty", "--only", "--no-edit", "--amend", "--reset-author"). + ToString() + + return self.cmd.New(cmdStr).Run() } // Sets the commit's author to the supplied value. Value is expected to be of the form 'Name ' func (self *CommitCommands) SetAuthor(value string) error { - commandStr := fmt.Sprintf("git commit --allow-empty --only --no-edit --amend --author=%s", self.cmd.Quote(value)) - return self.cmd.New(commandStr).Run() + cmdStr := NewGitCmd("commit"). + Arg("--allow-empty", "--only", "--no-edit", "--amend", "--author="+self.cmd.Quote(value)). + ToString() + + return self.cmd.New(cmdStr).Run() } // ResetToCommit reset to commit func (self *CommitCommands) ResetToCommit(sha string, strength string, envVars []string) error { - return self.cmd.New(fmt.Sprintf("git reset --%s %s", strength, sha)). + cmdStr := NewGitCmd("reset").Arg("--"+strength, sha).ToString() + + return self.cmd.New(cmdStr). // prevents git from prompting us for input which would freeze the program // TODO: see if this is actually needed here AddEnvVars("GIT_TERMINAL_PROMPT=0"). @@ -45,38 +54,52 @@ func (self *CommitCommands) CommitCmdObj(message string) oscommands.ICmdObj { messageArgs := self.commitMessageArgs(message) skipHookPrefix := self.UserConfig.Git.SkipHookPrefix - noVerifyFlag := "" - if skipHookPrefix != "" && strings.HasPrefix(message, skipHookPrefix) { - noVerifyFlag = " --no-verify" - } - return self.cmd.New(fmt.Sprintf("git commit%s%s%s", noVerifyFlag, self.signoffFlag(), messageArgs)) + cmdStr := NewGitCmd("commit"). + ArgIf(skipHookPrefix != "" && strings.HasPrefix(message, skipHookPrefix), "--no-verify"). + ArgIf(self.signoffFlag() != "", self.signoffFlag()). + Arg(messageArgs...). + ToString() + + return self.cmd.New(cmdStr) } // RewordLastCommit rewords the topmost commit with the given message func (self *CommitCommands) RewordLastCommit(message string) error { messageArgs := self.commitMessageArgs(message) - return self.cmd.New(fmt.Sprintf("git commit --allow-empty --amend --only%s", messageArgs)).Run() + + cmdStr := NewGitCmd("commit"). + Arg("--allow-empty", "--amend", "--only"). + Arg(messageArgs...). + ToString() + + return self.cmd.New(cmdStr).Run() } -func (self *CommitCommands) commitMessageArgs(message string) string { +func (self *CommitCommands) commitMessageArgs(message string) []string { msg, description, _ := strings.Cut(message, "\n") - descriptionArgs := "" + args := []string{"-m", self.cmd.Quote(msg)} + if description != "" { - descriptionArgs = fmt.Sprintf(" -m %s", self.cmd.Quote(description)) + args = append(args, "-m", self.cmd.Quote(description)) } - return fmt.Sprintf(" -m %s%s", self.cmd.Quote(msg), descriptionArgs) + return args } // runs git commit without the -m argument meaning it will invoke the user's editor func (self *CommitCommands) CommitEditorCmdObj() oscommands.ICmdObj { - return self.cmd.New(fmt.Sprintf("git commit%s%s", self.signoffFlag(), self.verboseFlag())) + cmdStr := NewGitCmd("commit"). + ArgIf(self.signoffFlag() != "", self.signoffFlag()). + ArgIf(self.verboseFlag() != "", self.verboseFlag()). + ToString() + + return self.cmd.New(cmdStr) } func (self *CommitCommands) signoffFlag() string { if self.UserConfig.Git.Commit.SignOff { - return " --signoff" + return "--signoff" } else { return "" } @@ -85,9 +108,9 @@ func (self *CommitCommands) signoffFlag() string { func (self *CommitCommands) verboseFlag() string { switch self.config.UserConfig.Git.Commit.Verbose { case "always": - return " --verbose" + return "--verbose" case "never": - return " --no-verbose" + return "--no-verbose" default: return "" } @@ -95,19 +118,25 @@ func (self *CommitCommands) verboseFlag() string { // Get the subject of the HEAD commit func (self *CommitCommands) GetHeadCommitMessage() (string, error) { - message, err := self.cmd.New("git log -1 --pretty=%s").DontLog().RunWithOutput() + cmdStr := NewGitCmd("log").Arg("-1", "--pretty=%s").ToString() + + message, err := self.cmd.New(cmdStr).DontLog().RunWithOutput() return strings.TrimSpace(message), err } func (self *CommitCommands) GetCommitMessage(commitSha string) (string, error) { - cmdStr := "git rev-list --format=%B --max-count=1 " + commitSha + cmdStr := NewGitCmd("rev-list"). + Arg("--format=%B", "--max-count=1", commitSha). + ToString() + messageWithHeader, err := self.cmd.New(cmdStr).DontLog().RunWithOutput() message := strings.Join(strings.SplitAfter(messageWithHeader, "\n")[1:], "") return strings.TrimSpace(message), err } func (self *CommitCommands) GetCommitDiff(commitSha string) (string, error) { - cmdStr := "git show --no-color " + commitSha + cmdStr := NewGitCmd("show").Arg("--no-color", commitSha).ToString() + diff, err := self.cmd.New(cmdStr).DontLog().RunWithOutput() return diff, err } @@ -118,7 +147,10 @@ type Author struct { } func (self *CommitCommands) GetCommitAuthor(commitSha string) (Author, error) { - cmdStr := "git show --no-patch --pretty=format:'%an%x00%ae' " + commitSha + cmdStr := NewGitCmd("show"). + Arg("--no-patch", "--pretty=format:'%an%x00%ae'", commitSha). + ToString() + output, err := self.cmd.New(cmdStr).DontLog().RunWithOutput() if err != nil { return Author{}, err @@ -138,15 +170,21 @@ func (self *CommitCommands) GetCommitMessageFirstLine(sha string) (string, error } func (self *CommitCommands) GetCommitMessagesFirstLine(shas []string) (string, error) { - return self.cmd.New( - fmt.Sprintf("git show --no-patch --pretty=format:%%s %s", strings.Join(shas, " ")), - ).DontLog().RunWithOutput() + cmdStr := NewGitCmd("show"). + Arg("--no-patch", "--pretty=format:%s"). + Arg(shas...). + ToString() + + return self.cmd.New(cmdStr).DontLog().RunWithOutput() } func (self *CommitCommands) GetCommitsOneline(shas []string) (string, error) { - return self.cmd.New( - fmt.Sprintf("git show --no-patch --oneline %s", strings.Join(shas, " ")), - ).DontLog().RunWithOutput() + cmdStr := NewGitCmd("show"). + Arg("--no-patch", "--oneline"). + Arg(shas...). + ToString() + + return self.cmd.New(cmdStr).DontLog().RunWithOutput() } // AmendHead amends HEAD with whatever is staged in your working tree @@ -155,42 +193,57 @@ func (self *CommitCommands) AmendHead() error { } func (self *CommitCommands) AmendHeadCmdObj() oscommands.ICmdObj { - return self.cmd.New("git commit --amend --no-edit --allow-empty") + cmdStr := NewGitCmd("commit"). + Arg("--amend", "--no-edit", "--allow-empty"). + ToString() + + return self.cmd.New(cmdStr) } func (self *CommitCommands) ShowCmdObj(sha string, filterPath string, ignoreWhitespace bool) oscommands.ICmdObj { contextSize := self.UserConfig.Git.DiffContextSize - filterPathArg := "" - if filterPath != "" { - filterPathArg = fmt.Sprintf(" -- %s", self.cmd.Quote(filterPath)) - } - ignoreWhitespaceArg := "" - if ignoreWhitespace { - ignoreWhitespaceArg = " --ignore-all-space" - } - cmdStr := fmt.Sprintf("git show --submodule --color=%s --unified=%d --stat -p %s%s%s", - self.UserConfig.Git.Paging.ColorArg, contextSize, sha, ignoreWhitespaceArg, filterPathArg) + cmdStr := NewGitCmd("show"). + Arg("--submodule"). + Arg("--color="+self.UserConfig.Git.Paging.ColorArg). + Arg(fmt.Sprintf("--unified=%d", contextSize)). + Arg("--stat"). + Arg("-p"). + Arg(sha). + ArgIf(ignoreWhitespace, "--ignore-all-space"). + ArgIf(filterPath != "", "--", self.cmd.Quote(filterPath)). + ToString() + return self.cmd.New(cmdStr).DontLog() } // Revert reverts the selected commit by sha func (self *CommitCommands) Revert(sha string) error { - return self.cmd.New(fmt.Sprintf("git revert %s", sha)).Run() + cmdStr := NewGitCmd("revert").Arg(sha).ToString() + + return self.cmd.New(cmdStr).Run() } func (self *CommitCommands) RevertMerge(sha string, parentNumber int) error { - return self.cmd.New(fmt.Sprintf("git revert %s -m %d", sha, parentNumber)).Run() + cmdStr := NewGitCmd("revert").Arg(sha, "-m", fmt.Sprintf("%d", parentNumber)). + ToString() + + return self.cmd.New(cmdStr).Run() } // CreateFixupCommit creates a commit that fixes up a previous commit func (self *CommitCommands) CreateFixupCommit(sha string) error { - return self.cmd.New(fmt.Sprintf("git commit --fixup=%s", sha)).Run() + cmdStr := NewGitCmd("commit").Arg("--fixup=" + sha).ToString() + + return self.cmd.New(cmdStr).Run() } // a value of 0 means the head commit, 1 is the parent commit, etc func (self *CommitCommands) GetCommitMessageFromHistory(value int) (string, error) { - hash, _ := self.cmd.New(fmt.Sprintf("git log -1 --skip=%d --pretty=%%H", value)).DontLog().RunWithOutput() + cmdStr := NewGitCmd("log").Arg("-1", fmt.Sprintf("--skip=%d", value), "--pretty=%H"). + ToString() + + hash, _ := self.cmd.New(cmdStr).DontLog().RunWithOutput() formattedHash := strings.TrimSpace(hash) if len(formattedHash) == 0 { return "", ErrInvalidCommitIndex diff --git a/pkg/commands/git_commands/commit_file_loader.go b/pkg/commands/git_commands/commit_file_loader.go index 0b606ae86..53ca046ba 100644 --- a/pkg/commands/git_commands/commit_file_loader.go +++ b/pkg/commands/git_commands/commit_file_loader.go @@ -1,7 +1,6 @@ package git_commands import ( - "fmt" "strings" "github.com/jesseduffield/generics/slices" @@ -25,12 +24,18 @@ func NewCommitFileLoader(common *common.Common, cmd oscommands.ICmdObjBuilder) * // GetFilesInDiff get the specified commit files func (self *CommitFileLoader) GetFilesInDiff(from string, to string, reverse bool) ([]*models.CommitFile, error) { - reverseFlag := "" - if reverse { - reverseFlag = " -R " - } + cmdStr := NewGitCmd("diff"). + Arg("--submodule"). + Arg("--no-ext-diff"). + Arg("--name-status"). + Arg("-z"). + Arg("--no-renames"). + ArgIf(reverse, "-R"). + Arg(from). + Arg(to). + ToString() - filenames, err := self.cmd.New(fmt.Sprintf("git diff --submodule --no-ext-diff --name-status -z --no-renames %s %s %s", reverseFlag, from, to)).DontLog().RunWithOutput() + filenames, err := self.cmd.New(cmdStr).DontLog().RunWithOutput() if err != nil { return nil, err } diff --git a/pkg/commands/git_commands/commit_loader.go b/pkg/commands/git_commands/commit_loader.go index e68f56ebb..00a468ab2 100644 --- a/pkg/commands/git_commands/commit_loader.go +++ b/pkg/commands/git_commands/commit_loader.go @@ -201,12 +201,11 @@ func (self *CommitLoader) getHydratedRebasingCommits(rebaseMode enums.RebaseMode // note that we're not filtering these as we do non-rebasing commits just because // I suspect that will cause some damage cmdObj := self.cmd.New( - fmt.Sprintf( - "git -c log.showSignature=false show %s --no-patch --oneline %s --abbrev=%d", - strings.Join(commitShas, " "), - prettyFormat, - 20, - ), + NewGitCmd("show"). + Config("log.showSignature=false"). + Arg("--no-patch", "--oneline", "--abbrev=20", prettyFormat). + Arg(commitShas...). + ToString(), ).DontLog() fullCommits := map[string]*models.Commit{} @@ -375,8 +374,11 @@ func (self *CommitLoader) getMergeBase(refName string) string { // We pass all configured main branches to the merge-base call; git will // return the base commit for the closest one. - output, err := self.cmd.New(fmt.Sprintf("git merge-base %s %s", - self.cmd.Quote(refName), *self.quotedMainBranches)).DontLog().RunWithOutput() + + output, err := self.cmd.New( + NewGitCmd("merge-base").Arg(self.cmd.Quote(refName), *self.quotedMainBranches). + ToString(), + ).DontLog().RunWithOutput() if err != nil { // If there's an error, it must be because one of the main branches that // used to exist when we called getExistingMainBranches() was deleted @@ -391,7 +393,9 @@ func (self *CommitLoader) getExistingMainBranches() string { lo.FilterMap(self.UserConfig.Git.MainBranches, func(branchName string, _ int) (string, bool) { quotedRef := self.cmd.Quote("refs/heads/" + branchName) - if err := self.cmd.New(fmt.Sprintf("git rev-parse --verify --quiet %s", quotedRef)).DontLog().Run(); err != nil { + if err := self.cmd.New( + NewGitCmd("rev-parse").Arg("--verify", "--quiet", quotedRef).ToString(), + ).DontLog().Run(); err != nil { return "", false } return quotedRef, true @@ -413,9 +417,10 @@ func ignoringWarnings(commandOutput string) string { func (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) { output, err := self.cmd. New( - fmt.Sprintf("git merge-base %s %s@{u}", - self.cmd.Quote(refName), - self.cmd.Quote(strings.TrimPrefix(refName, "refs/heads/"))), + NewGitCmd("merge-base"). + Arg(self.cmd.Quote(refName)). + Arg(self.cmd.Quote(strings.TrimPrefix(refName, "refs/heads/")) + "@{u}"). + ToString(), ). DontLog(). RunWithOutput() @@ -428,42 +433,23 @@ func (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) { // getLog gets the git log. func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj { - limitFlag := "" - if opts.Limit { - limitFlag = " -300" - } - - followFlag := "" - filterFlag := "" - if opts.FilterPath != "" { - followFlag = " --follow" - filterFlag = fmt.Sprintf(" %s", self.cmd.Quote(opts.FilterPath)) - } - config := self.UserConfig.Git.Log - orderFlag := "" - if config.Order != "default" { - orderFlag = " --" + config.Order - } - allFlag := "" - if opts.All { - allFlag = " --all" - } + cmdStr := NewGitCmd("log"). + Arg(self.cmd.Quote(opts.RefName)). + ArgIf(config.Order != "default", "--"+config.Order). + ArgIf(opts.All, "--all"). + Arg("--oneline"). + Arg(prettyFormat). + Arg("--abbrev=40"). + ArgIf(opts.Limit, "-300"). + ArgIf(opts.FilterPath != "", "--follow"). + Arg("--no-show-signature"). + Arg("--"). + ArgIf(opts.FilterPath != "", self.cmd.Quote(opts.FilterPath)). + ToString() - return self.cmd.New( - fmt.Sprintf( - "git log %s%s%s --oneline %s%s --abbrev=%d%s --no-show-signature --%s", - self.cmd.Quote(opts.RefName), - orderFlag, - allFlag, - prettyFormat, - limitFlag, - 40, - followFlag, - filterFlag, - ), - ).DontLog() + return self.cmd.New(cmdStr).DontLog() } const prettyFormat = `--pretty=format:"%H%x00%at%x00%aN%x00%ae%x00%d%x00%p%x00%s"` diff --git a/pkg/commands/git_commands/file_loader.go b/pkg/commands/git_commands/file_loader.go index 9f33b811a..dbd625683 100644 --- a/pkg/commands/git_commands/file_loader.go +++ b/pkg/commands/git_commands/file_loader.go @@ -42,7 +42,7 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File } untrackedFilesArg := fmt.Sprintf("--untracked-files=%s", untrackedFilesSetting) - statuses, err := self.GitStatus(GitStatusOptions{NoRenames: opts.NoRenames, UntrackedFilesArg: untrackedFilesArg}) + statuses, err := self.gitStatus(GitStatusOptions{NoRenames: opts.NoRenames, UntrackedFilesArg: untrackedFilesArg}) if err != nil { self.Log.Error(err) } @@ -81,13 +81,15 @@ type FileStatus struct { PreviousName string } -func (c *FileLoader) GitStatus(opts GitStatusOptions) ([]FileStatus, error) { - noRenamesFlag := "" - if opts.NoRenames { - noRenamesFlag = " --no-renames" - } +func (c *FileLoader) gitStatus(opts GitStatusOptions) ([]FileStatus, error) { + cmdStr := NewGitCmd("status"). + Arg(opts.UntrackedFilesArg). + Arg("--porcelain"). + Arg("-z"). + ArgIf(opts.NoRenames, "--no-renames"). + ToString() - statusLines, _, err := c.cmd.New(fmt.Sprintf("git status %s --porcelain -z%s", opts.UntrackedFilesArg, noRenamesFlag)).DontLog().RunWithOutputs() + statusLines, _, err := c.cmd.New(cmdStr).DontLog().RunWithOutputs() if err != nil { return []FileStatus{}, err } diff --git a/pkg/commands/git_commands/flow.go b/pkg/commands/git_commands/flow.go index fd97707ec..fab67d81b 100644 --- a/pkg/commands/git_commands/flow.go +++ b/pkg/commands/git_commands/flow.go @@ -34,6 +34,7 @@ func (self *FlowCommands) FinishCmdObj(branchName string) (oscommands.ICmdObj, e branchType := "" for _, line := range strings.Split(strings.TrimSpace(prefixes), "\n") { if strings.HasPrefix(line, "gitflow.prefix.") && strings.HasSuffix(line, prefix) { + regex := regexp.MustCompile("gitflow.prefix.([^ ]*) .*") matches := regex.FindAllStringSubmatch(line, 1) @@ -48,9 +49,13 @@ func (self *FlowCommands) FinishCmdObj(branchName string) (oscommands.ICmdObj, e return nil, errors.New(self.Tr.NotAGitFlowBranch) } - return self.cmd.New("git flow " + branchType + " finish " + suffix), nil + cmdStr := NewGitCmd("flow").Arg(branchType, "finish", suffix).ToString() + + return self.cmd.New(cmdStr), nil } func (self *FlowCommands) StartCmdObj(branchType string, name string) oscommands.ICmdObj { - return self.cmd.New("git flow " + branchType + " start " + name) + cmdStr := NewGitCmd("flow").Arg(branchType, "start", name).ToString() + + return self.cmd.New(cmdStr) } diff --git a/pkg/commands/git_commands/git_command_builder.go b/pkg/commands/git_commands/git_command_builder.go new file mode 100644 index 000000000..da0859c2c --- /dev/null +++ b/pkg/commands/git_commands/git_command_builder.go @@ -0,0 +1,54 @@ +package git_commands + +import "strings" + +// convenience struct for building git commands. Especially useful when +// including conditional args +type GitCommandBuilder struct { + // command string + args []string +} + +func NewGitCmd(command string) *GitCommandBuilder { + return &GitCommandBuilder{args: []string{command}} +} + +func (self *GitCommandBuilder) Arg(args ...string) *GitCommandBuilder { + self.args = append(self.args, args...) + + return self +} + +func (self *GitCommandBuilder) ArgIf(condition bool, ifTrue ...string) *GitCommandBuilder { + if condition { + self.Arg(ifTrue...) + } + + return self +} + +func (self *GitCommandBuilder) ArgIfElse(condition bool, ifTrue string, ifFalse string) *GitCommandBuilder { + if condition { + return self.Arg(ifTrue) + } else { + return self.Arg(ifFalse) + } +} + +func (self *GitCommandBuilder) Config(value string) *GitCommandBuilder { + // config settings come before the command + self.args = append([]string{"-c", value}, self.args...) + + return self +} + +func (self *GitCommandBuilder) RepoPath(value string) *GitCommandBuilder { + // repo path comes before the command + self.args = append([]string{"-C", value}, self.args...) + + return self +} + +func (self *GitCommandBuilder) ToString() string { + return "git " + strings.Join(self.args, " ") +} diff --git a/pkg/commands/git_commands/git_command_builder_test.go b/pkg/commands/git_commands/git_command_builder_test.go new file mode 100644 index 000000000..d3791388c --- /dev/null +++ b/pkg/commands/git_commands/git_command_builder_test.go @@ -0,0 +1,56 @@ +package git_commands + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGitCommandBuilder(t *testing.T) { + scenarios := []struct { + input string + expected string + }{ + { + input: NewGitCmd("push"). + Arg("--force-with-lease"). + Arg("--set-upstream"). + Arg("origin"). + Arg("master"). + ToString(), + expected: "git push --force-with-lease --set-upstream origin master", + }, + { + input: NewGitCmd("push").ArgIf(true, "--test").ToString(), + expected: "git push --test", + }, + { + input: NewGitCmd("push").ArgIf(false, "--test").ToString(), + expected: "git push", + }, + { + input: NewGitCmd("push").ArgIfElse(true, "-b", "-a").ToString(), + expected: "git push -b", + }, + { + input: NewGitCmd("push").ArgIfElse(false, "-a", "-b").ToString(), + expected: "git push -b", + }, + { + input: NewGitCmd("push").Arg("-a", "-b").ToString(), + expected: "git push -a -b", + }, + { + input: NewGitCmd("push").Config("user.name=foo").Config("user.email=bar").ToString(), + expected: "git -c user.email=bar -c user.name=foo push", + }, + { + input: NewGitCmd("push").RepoPath("a/b/c").ToString(), + expected: "git -C a/b/c push", + }, + } + + for _, s := range scenarios { + assert.Equal(t, s.input, s.expected) + } +} diff --git a/pkg/commands/git_commands/patch.go b/pkg/commands/git_commands/patch.go index 06e5e0f67..41de5a787 100644 --- a/pkg/commands/git_commands/patch.go +++ b/pkg/commands/git_commands/patch.go @@ -267,5 +267,7 @@ func (self *PatchCommands) PullPatchIntoNewCommit(commits []*models.Commit, comm // 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. func (self *PatchCommands) diffHeadAgainstCommit(commit *models.Commit) (string, error) { - return self.cmd.New(fmt.Sprintf("git diff HEAD..%s", commit.Sha)).RunWithOutput() + cmdStr := NewGitCmd("diff").Arg("HEAD.." + commit.Sha).ToString() + + return self.cmd.New(cmdStr).RunWithOutput() } diff --git a/pkg/commands/git_commands/rebase.go b/pkg/commands/git_commands/rebase.go index e7a2d766a..cbda4fb1f 100644 --- a/pkg/commands/git_commands/rebase.go +++ b/pkg/commands/git_commands/rebase.go @@ -176,23 +176,21 @@ type PrepareInteractiveRebaseCommandOpts struct { func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteractiveRebaseCommandOpts) oscommands.ICmdObj { ex := oscommands.GetLazygitPath() + cmdStr := NewGitCmd("rebase"). + Arg("--interactive"). + Arg("--autostash"). + Arg("--keep-empty"). + ArgIf(!self.version.IsOlderThan(2, 26, 0), "--empty=keep"). + Arg("--no-autosquash"). + ArgIf(!self.version.IsOlderThan(2, 22, 0), "--rebase-merges"). + Arg(opts.baseShaOrRoot). + ToString() + debug := "FALSE" if self.Debug { debug = "TRUE" } - emptyArg := " --empty=keep" - if self.version.IsOlderThan(2, 26, 0) { - emptyArg = "" - } - - rebaseMergesArg := " --rebase-merges" - if self.version.IsOlderThan(2, 22, 0) { - rebaseMergesArg = "" - } - - cmdStr := fmt.Sprintf("git rebase --interactive --autostash --keep-empty%s --no-autosquash%s %s", - emptyArg, rebaseMergesArg, opts.baseShaOrRoot) self.Log.WithField("command", cmdStr).Debug("RunCommand") cmdObj := self.cmd.New(cmdStr) @@ -228,7 +226,8 @@ func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) e } // Get the sha of the commit we just created - fixupSha, err := self.cmd.New("git rev-parse --verify HEAD").RunWithOutput() + cmdStr := NewGitCmd("rev-parse").Arg("--verify", "HEAD").ToString() + fixupSha, err := self.cmd.New(cmdStr).RunWithOutput() if err != nil { return err } @@ -265,14 +264,11 @@ func (self *RebaseCommands) SquashAllAboveFixupCommits(commit *models.Commit) er shaOrRoot = "--root" } - return self.runSkipEditorCommand( - self.cmd.New( - fmt.Sprintf( - "git rebase --interactive --rebase-merges --autostash --autosquash %s", - shaOrRoot, - ), - ), - ) + cmdStr := NewGitCmd("rebase"). + Arg("--interactive", "--rebase-merges", "--autostash", "--autosquash", shaOrRoot). + ToString() + + return self.runSkipEditorCommand(self.cmd.New(cmdStr)) } // BeginInteractiveRebaseForCommit starts an interactive rebase to edit the current @@ -308,7 +304,9 @@ func (self *RebaseCommands) RebaseBranch(branchName string) error { } func (self *RebaseCommands) GenericMergeOrRebaseActionCmdObj(commandType string, command string) oscommands.ICmdObj { - return self.cmd.New("git " + commandType + " --" + command) + cmdStr := NewGitCmd(commandType).Arg("--" + command).ToString() + + return self.cmd.New(cmdStr) } func (self *RebaseCommands) ContinueRebase() error { @@ -365,7 +363,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) - if err := self.cmd.New("git cat-file -e HEAD^:" + self.cmd.Quote(fileName)).Run(); err != nil { + cmdStr := NewGitCmd("cat-file").Arg("-e", "HEAD^:"+self.cmd.Quote(fileName)).ToString() + + if err := self.cmd.New(cmdStr).Run(); err != nil { if err := self.os.Remove(fileName); err != nil { return err } diff --git a/pkg/commands/git_commands/remote.go b/pkg/commands/git_commands/remote.go index 1245a8cf0..4be4350ef 100644 --- a/pkg/commands/git_commands/remote.go +++ b/pkg/commands/git_commands/remote.go @@ -15,44 +15,52 @@ func NewRemoteCommands(gitCommon *GitCommon) *RemoteCommands { } func (self *RemoteCommands) AddRemote(name string, url string) error { - return self.cmd. - New(fmt.Sprintf("git remote add %s %s", self.cmd.Quote(name), self.cmd.Quote(url))). - Run() + cmdStr := NewGitCmd("remote"). + Arg("add", self.cmd.Quote(name), self.cmd.Quote(url)). + ToString() + + return self.cmd.New(cmdStr).Run() } func (self *RemoteCommands) RemoveRemote(name string) error { - return self.cmd. - New(fmt.Sprintf("git remote remove %s", self.cmd.Quote(name))). - Run() + cmdStr := NewGitCmd("remote"). + Arg("remove", self.cmd.Quote(name)). + ToString() + + return self.cmd.New(cmdStr).Run() } func (self *RemoteCommands) RenameRemote(oldRemoteName string, newRemoteName string) error { - return self.cmd. - New(fmt.Sprintf("git remote rename %s %s", self.cmd.Quote(oldRemoteName), self.cmd.Quote(newRemoteName))). - Run() + cmdStr := NewGitCmd("remote"). + Arg("rename", self.cmd.Quote(oldRemoteName), self.cmd.Quote(newRemoteName)). + ToString() + + return self.cmd.New(cmdStr).Run() } func (self *RemoteCommands) UpdateRemoteUrl(remoteName string, updatedUrl string) error { - return self.cmd. - New(fmt.Sprintf("git remote set-url %s %s", self.cmd.Quote(remoteName), self.cmd.Quote(updatedUrl))). - Run() + cmdStr := NewGitCmd("remote"). + Arg("set-url", self.cmd.Quote(remoteName), self.cmd.Quote(updatedUrl)). + ToString() + + return self.cmd.New(cmdStr).Run() } func (self *RemoteCommands) DeleteRemoteBranch(remoteName string, branchName string) error { - command := fmt.Sprintf("git push %s --delete %s", self.cmd.Quote(remoteName), self.cmd.Quote(branchName)) - return self.cmd.New(command).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run() + cmdStr := NewGitCmd("push"). + Arg(self.cmd.Quote(remoteName), "--delete", self.cmd.Quote(branchName)). + ToString() + + return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run() } // CheckRemoteBranchExists Returns remote branch func (self *RemoteCommands) CheckRemoteBranchExists(branchName string) bool { - _, err := self.cmd. - New( - fmt.Sprintf("git show-ref --verify -- refs/remotes/origin/%s", - self.cmd.Quote(branchName), - ), - ). - DontLog(). - RunWithOutput() + cmdStr := NewGitCmd("show-ref"). + Arg("--verify", "--", fmt.Sprintf("refs/remotes/origin/%s", self.cmd.Quote(branchName))). + ToString() + + _, err := self.cmd.New(cmdStr).DontLog().RunWithOutput() return err == nil } diff --git a/pkg/commands/git_commands/remote_loader.go b/pkg/commands/git_commands/remote_loader.go index 1b0db49e0..d8fffff13 100644 --- a/pkg/commands/git_commands/remote_loader.go +++ b/pkg/commands/git_commands/remote_loader.go @@ -31,7 +31,8 @@ func NewRemoteLoader( } func (self *RemoteLoader) GetRemotes() ([]*models.Remote, error) { - remoteBranchesStr, err := self.cmd.New("git branch -r").DontLog().RunWithOutput() + cmdStr := NewGitCmd("branch").Arg("-r").ToString() + remoteBranchesStr, err := self.cmd.New(cmdStr).DontLog().RunWithOutput() if err != nil { return nil, err } diff --git a/pkg/commands/git_commands/stash.go b/pkg/commands/git_commands/stash.go index 54164e736..0033883a5 100644 --- a/pkg/commands/git_commands/stash.go +++ b/pkg/commands/git_commands/stash.go @@ -26,68 +26,94 @@ func NewStashCommands( } func (self *StashCommands) DropNewest() error { - return self.cmd.New("git stash drop").Run() + cmdStr := NewGitCmd("stash").Arg("drop").ToString() + + return self.cmd.New(cmdStr).Run() } func (self *StashCommands) Drop(index int) error { - return self.cmd.New(fmt.Sprintf("git stash drop stash@{%d}", index)).Run() + cmdStr := NewGitCmd("stash").Arg("drop", fmt.Sprintf("stash@{%d}", index)). + ToString() + + return self.cmd.New(cmdStr).Run() } func (self *StashCommands) Pop(index int) error { - return self.cmd.New(fmt.Sprintf("git stash pop stash@{%d}", index)).Run() + cmdStr := NewGitCmd("stash").Arg("pop", fmt.Sprintf("stash@{%d}", index)). + ToString() + + return self.cmd.New(cmdStr).Run() } func (self *StashCommands) Apply(index int) error { - return self.cmd.New(fmt.Sprintf("git stash apply stash@{%d}", index)).Run() + cmdStr := NewGitCmd("stash").Arg("apply", fmt.Sprintf("stash@{%d}", index)). + ToString() + + return self.cmd.New(cmdStr).Run() } // Save save stash func (self *StashCommands) Save(message string) error { - return self.cmd.New("git stash save " + self.cmd.Quote(message)).Run() + cmdStr := NewGitCmd("stash").Arg("save", self.cmd.Quote(message)). + ToString() + + return self.cmd.New(cmdStr).Run() } func (self *StashCommands) Store(sha string, message string) error { trimmedMessage := strings.Trim(message, " \t") - if len(trimmedMessage) > 0 { - return self.cmd.New(fmt.Sprintf("git stash store %s -m %s", self.cmd.Quote(sha), self.cmd.Quote(trimmedMessage))).Run() - } - return self.cmd.New(fmt.Sprintf("git stash store %s", self.cmd.Quote(sha))).Run() + + cmdStr := NewGitCmd("stash").Arg("store", self.cmd.Quote(sha)). + ArgIf(trimmedMessage != "", "-m", self.cmd.Quote(trimmedMessage)). + ToString() + + return self.cmd.New(cmdStr).Run() } func (self *StashCommands) Sha(index int) (string, error) { - sha, _, err := self.cmd.New(fmt.Sprintf("git rev-parse refs/stash@{%d}", index)).DontLog().RunWithOutputs() + cmdStr := NewGitCmd("rev-parse"). + Arg(fmt.Sprintf("refs/stash@{%d}", index)). + ToString() + + sha, _, err := self.cmd.New(cmdStr).DontLog().RunWithOutputs() return strings.Trim(sha, "\r\n"), err } func (self *StashCommands) ShowStashEntryCmdObj(index int, ignoreWhitespace bool) oscommands.ICmdObj { - ignoreWhitespaceFlag := "" - if ignoreWhitespace { - ignoreWhitespaceFlag = " --ignore-all-space" - } - - cmdStr := fmt.Sprintf( - "git stash show -p --stat --color=%s --unified=%d%s stash@{%d}", - self.UserConfig.Git.Paging.ColorArg, - self.UserConfig.Git.DiffContextSize, - ignoreWhitespaceFlag, - index, - ) + cmdStr := NewGitCmd("stash").Arg("show"). + Arg("-p"). + Arg("--stat"). + Arg(fmt.Sprintf("--color=%s", self.UserConfig.Git.Paging.ColorArg)). + Arg(fmt.Sprintf("--unified=%d", self.UserConfig.Git.DiffContextSize)). + ArgIf(ignoreWhitespace, "--ignore-all-space"). + Arg(fmt.Sprintf("stash@{%d}", index)). + ToString() return self.cmd.New(cmdStr).DontLog() } func (self *StashCommands) StashAndKeepIndex(message string) error { - return self.cmd.New(fmt.Sprintf("git stash save %s --keep-index", self.cmd.Quote(message))).Run() + cmdStr := NewGitCmd("stash").Arg("save", self.cmd.Quote(message), "--keep-index"). + ToString() + + return self.cmd.New(cmdStr).Run() } func (self *StashCommands) StashUnstagedChanges(message string) error { - if err := self.cmd.New("git commit --no-verify -m \"[lazygit] stashing unstaged changes\"").Run(); err != nil { + if err := self.cmd.New( + NewGitCmd("commit"). + Arg("--no-verify", "-m", self.cmd.Quote("[lazygit] stashing unstaged changes")). + ToString(), + ).Run(); err != nil { return err } if err := self.Save(message); err != nil { return err } - if err := self.cmd.New("git reset --soft HEAD^").Run(); err != nil { + + if err := self.cmd.New( + NewGitCmd("reset").Arg("--soft", "HEAD^").ToString(), + ).Run(); err != nil { return err } return nil @@ -97,7 +123,9 @@ func (self *StashCommands) StashUnstagedChanges(message string) error { // shoutouts to Joe on https://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible func (self *StashCommands) SaveStagedChanges(message string) error { // wrap in 'writing', which uses a mutex - if err := self.cmd.New("git stash --keep-index").Run(); err != nil { + if err := self.cmd.New( + NewGitCmd("stash").Arg("--keep-index").ToString(), + ).Run(); err != nil { return err } @@ -105,15 +133,22 @@ func (self *StashCommands) SaveStagedChanges(message string) error { return err } - if err := self.cmd.New("git stash apply stash@{1}").Run(); err != nil { + if err := self.cmd.New( + NewGitCmd("stash").Arg("apply", "stash@{1}").ToString(), + ).Run(); err != nil { return err } - if err := self.os.PipeCommands("git stash show -p", "git apply -R"); err != nil { + if err := self.os.PipeCommands( + NewGitCmd("stash").Arg("show", "-p").ToString(), + NewGitCmd("apply").Arg("-R").ToString(), + ); err != nil { return err } - if err := self.cmd.New("git stash drop stash@{1}").Run(); err != nil { + if err := self.cmd.New( + NewGitCmd("stash").Arg("drop", "stash@{1}").ToString(), + ).Run(); err != nil { return err } @@ -135,7 +170,10 @@ func (self *StashCommands) SaveStagedChanges(message string) error { } func (self *StashCommands) StashIncludeUntrackedChanges(message string) error { - return self.cmd.New(fmt.Sprintf("git stash save %s --include-untracked", self.cmd.Quote(message))).Run() + return self.cmd.New( + NewGitCmd("stash").Arg("save", self.cmd.Quote(message), "--include-untracked"). + ToString(), + ).Run() } func (self *StashCommands) Rename(index int, message string) error { diff --git a/pkg/commands/git_commands/stash_loader.go b/pkg/commands/git_commands/stash_loader.go index 0dea81c2c..580287256 100644 --- a/pkg/commands/git_commands/stash_loader.go +++ b/pkg/commands/git_commands/stash_loader.go @@ -32,7 +32,8 @@ func (self *StashLoader) GetStashEntries(filterPath string) []*models.StashEntry return self.getUnfilteredStashEntries() } - rawString, err := self.cmd.New("git stash list --name-only").DontLog().RunWithOutput() + cmdStr := NewGitCmd("stash").Arg("list", "--name-only").ToString() + rawString, err := self.cmd.New(cmdStr).DontLog().RunWithOutput() if err != nil { return self.getUnfilteredStashEntries() } @@ -65,7 +66,9 @@ outer: } func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry { - rawString, _ := self.cmd.New("git stash list -z --pretty='%gs'").DontLog().RunWithOutput() + cmdStr := NewGitCmd("stash").Arg("list", "-z", "--pretty='%gs'").ToString() + + rawString, _ := self.cmd.New(cmdStr).DontLog().RunWithOutput() return slices.MapWithIndex(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry { return self.stashEntryFromLine(line, index) }) diff --git a/pkg/commands/git_commands/status.go b/pkg/commands/git_commands/status.go index d660dd8b0..9a1c19b7e 100644 --- a/pkg/commands/git_commands/status.go +++ b/pkg/commands/git_commands/status.go @@ -56,7 +56,9 @@ func (self *StatusCommands) IsBareRepo() (bool, error) { } func IsBareRepo(osCommand *oscommands.OSCommand) (bool, error) { - res, err := osCommand.Cmd.New("git rev-parse --is-bare-repository").DontLog().RunWithOutput() + res, err := osCommand.Cmd.New( + NewGitCmd("rev-parse").Arg("--is-bare-repository").ToString(), + ).DontLog().RunWithOutput() if err != nil { return false, err } diff --git a/pkg/commands/git_commands/submodule.go b/pkg/commands/git_commands/submodule.go index 82744a338..a19e0ffe8 100644 --- a/pkg/commands/git_commands/submodule.go +++ b/pkg/commands/git_commands/submodule.go @@ -2,7 +2,6 @@ package git_commands import ( "bufio" - "fmt" "os" "path/filepath" "regexp" @@ -82,38 +81,60 @@ func (self *SubmoduleCommands) Stash(submodule *models.SubmoduleConfig) error { return nil } - return self.cmd.New("git -C " + self.cmd.Quote(submodule.Path) + " stash --include-untracked").Run() + cmdStr := NewGitCmd("stash"). + RepoPath(self.cmd.Quote(submodule.Path)). + Arg("--include-untracked"). + ToString() + + return self.cmd.New(cmdStr).Run() } func (self *SubmoduleCommands) Reset(submodule *models.SubmoduleConfig) error { - return self.cmd.New("git submodule update --init --force -- " + self.cmd.Quote(submodule.Path)).Run() + cmdStr := NewGitCmd("submodule"). + Arg("update", "--init", "--force", "--", self.cmd.Quote(submodule.Path)). + ToString() + + return self.cmd.New(cmdStr).Run() } func (self *SubmoduleCommands) UpdateAll() error { // not doing an --init here because the user probably doesn't want that - return self.cmd.New("git submodule update --force").Run() + cmdStr := NewGitCmd("submodule").Arg("update", "--force").ToString() + + return self.cmd.New(cmdStr).Run() } func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error { // based on https://gist.github.com/myusuf3/7f645819ded92bda6677 - if err := self.cmd.New("git submodule deinit --force -- " + self.cmd.Quote(submodule.Path)).Run(); err != nil { - if strings.Contains(err.Error(), "did not match any file(s) known to git") { - if err := self.cmd.New("git config --file .gitmodules --remove-section submodule." + self.cmd.Quote(submodule.Name)).Run(); err != nil { - return err - } + if err := self.cmd.New( + NewGitCmd("submodule"). + Arg("deinit", "--force", "--", self.cmd.Quote(submodule.Path)).ToString(), + ).Run(); err != nil { + if !strings.Contains(err.Error(), "did not match any file(s) known to git") { + return err + } - if err := self.cmd.New("git config --remove-section submodule." + self.cmd.Quote(submodule.Name)).Run(); err != nil { - return err - } + if err := self.cmd.New( + NewGitCmd("config"). + Arg("--file", ".gitmodules", "--remove-section", "submodule."+self.cmd.Quote(submodule.Path)). + ToString(), + ).Run(); err != nil { + return err + } - // if there's an error here about it not existing then we'll just continue to do `git rm` - } else { + if err := self.cmd.New( + NewGitCmd("config"). + Arg("--remove-section", "submodule."+self.cmd.Quote(submodule.Path)). + ToString(), + ).Run(); err != nil { return err } } - if err := self.cmd.New("git rm --force -r " + submodule.Path).Run(); err != nil { + if err := self.cmd.New( + NewGitCmd("rm").Arg("--force", "-r", submodule.Path).ToString(), + ).Run(); err != nil { // if the directory isn't there then that's fine self.Log.Error(err) } @@ -122,24 +143,35 @@ func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error { } func (self *SubmoduleCommands) Add(name string, path string, url string) error { - return self.cmd. - New( - fmt.Sprintf( - "git submodule add --force --name %s -- %s %s ", - self.cmd.Quote(name), - self.cmd.Quote(url), - self.cmd.Quote(path), - )). - Run() + cmdStr := NewGitCmd("submodule"). + Arg("add"). + Arg("--force"). + Arg("--name"). + Arg(self.cmd.Quote(name)). + Arg("--"). + Arg(self.cmd.Quote(url)). + Arg(self.cmd.Quote(path)). + ToString() + + return self.cmd.New(cmdStr).Run() } func (self *SubmoduleCommands) UpdateUrl(name string, path string, newUrl string) error { + setUrlCmdStr := NewGitCmd("config"). + Arg( + "--file", ".gitmodules", "submodule."+self.cmd.Quote(name)+".url", self.cmd.Quote(newUrl), + ). + ToString() + // the set-url command is only for later git versions so we're doing it manually here - if err := self.cmd.New("git config --file .gitmodules submodule." + self.cmd.Quote(name) + ".url " + self.cmd.Quote(newUrl)).Run(); err != nil { + if err := self.cmd.New(setUrlCmdStr).Run(); err != nil { return err } - if err := self.cmd.New("git submodule sync -- " + self.cmd.Quote(path)).Run(); err != nil { + syncCmdStr := NewGitCmd("submodule").Arg("sync", "--", self.cmd.Quote(path)). + ToString() + + if err := self.cmd.New(syncCmdStr).Run(); err != nil { return err } @@ -147,27 +179,45 @@ func (self *SubmoduleCommands) UpdateUrl(name string, path string, newUrl string } func (self *SubmoduleCommands) Init(path string) error { - return self.cmd.New("git submodule init -- " + self.cmd.Quote(path)).Run() + cmdStr := NewGitCmd("submodule").Arg("init", "--", self.cmd.Quote(path)). + ToString() + + return self.cmd.New(cmdStr).Run() } func (self *SubmoduleCommands) Update(path string) error { - return self.cmd.New("git submodule update --init -- " + self.cmd.Quote(path)).Run() + cmdStr := NewGitCmd("submodule").Arg("update", "--init", "--", self.cmd.Quote(path)). + ToString() + + return self.cmd.New(cmdStr).Run() } func (self *SubmoduleCommands) BulkInitCmdObj() oscommands.ICmdObj { - return self.cmd.New("git submodule init") + cmdStr := NewGitCmd("submodule").Arg("init"). + ToString() + + return self.cmd.New(cmdStr) } func (self *SubmoduleCommands) BulkUpdateCmdObj() oscommands.ICmdObj { - return self.cmd.New("git submodule update") + cmdStr := NewGitCmd("submodule").Arg("update"). + ToString() + + return self.cmd.New(cmdStr) } func (self *SubmoduleCommands) ForceBulkUpdateCmdObj() oscommands.ICmdObj { - return self.cmd.New("git submodule update --force") + cmdStr := NewGitCmd("submodule").Arg("update", "--force"). + ToString() + + return self.cmd.New(cmdStr) } func (self *SubmoduleCommands) BulkDeinitCmdObj() oscommands.ICmdObj { - return self.cmd.New("git submodule deinit --all --force") + cmdStr := NewGitCmd("submodule").Arg("deinit", "--all", "--force"). + ToString() + + return self.cmd.New(cmdStr) } func (self *SubmoduleCommands) ResetSubmodules(submodules []*models.SubmoduleConfig) error { diff --git a/pkg/commands/git_commands/sync.go b/pkg/commands/git_commands/sync.go index fb1aa9648..967fd8d9e 100644 --- a/pkg/commands/git_commands/sync.go +++ b/pkg/commands/git_commands/sync.go @@ -1,8 +1,6 @@ package git_commands import ( - "fmt" - "github.com/go-errors/errors" "github.com/jesseduffield/lazygit/pkg/commands/oscommands" ) @@ -26,26 +24,16 @@ type PushOpts struct { } func (self *SyncCommands) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error) { - cmdStr := "git push" - - if opts.Force { - cmdStr += " --force-with-lease" + if opts.UpstreamBranch != "" && opts.UpstreamRemote == "" { + return nil, errors.New(self.Tr.MustSpecifyOriginError) } - if opts.SetUpstream { - cmdStr += " --set-upstream" - } - - if opts.UpstreamRemote != "" { - cmdStr += " " + self.cmd.Quote(opts.UpstreamRemote) - } - - if opts.UpstreamBranch != "" { - if opts.UpstreamRemote == "" { - return nil, errors.New(self.Tr.MustSpecifyOriginError) - } - cmdStr += " " + self.cmd.Quote(opts.UpstreamBranch) - } + cmdStr := NewGitCmd("push"). + ArgIf(opts.Force, "--force-with-lease"). + ArgIf(opts.SetUpstream, "--set-upstream"). + ArgIf(opts.UpstreamRemote != "", self.cmd.Quote(opts.UpstreamRemote)). + ArgIf(opts.UpstreamBranch != "", self.cmd.Quote(opts.UpstreamBranch)). + ToString() cmdObj := self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex) return cmdObj, nil @@ -68,14 +56,10 @@ type FetchOptions struct { // Fetch fetch git repo func (self *SyncCommands) Fetch(opts FetchOptions) error { - cmdStr := "git fetch" - - if opts.RemoteName != "" { - cmdStr = fmt.Sprintf("%s %s", cmdStr, self.cmd.Quote(opts.RemoteName)) - } - if opts.BranchName != "" { - cmdStr = fmt.Sprintf("%s %s", cmdStr, self.cmd.Quote(opts.BranchName)) - } + cmdStr := NewGitCmd("fetch"). + ArgIf(opts.RemoteName != "", self.cmd.Quote(opts.RemoteName)). + ArgIf(opts.BranchName != "", self.cmd.Quote(opts.BranchName)). + ToString() cmdObj := self.cmd.New(cmdStr) if opts.Background { @@ -93,18 +77,12 @@ type PullOptions struct { } func (self *SyncCommands) Pull(opts PullOptions) error { - cmdStr := "git pull --no-edit" - - if opts.FastForwardOnly { - cmdStr += " --ff-only" - } - - if opts.RemoteName != "" { - cmdStr = fmt.Sprintf("%s %s", cmdStr, self.cmd.Quote(opts.RemoteName)) - } - if opts.BranchName != "" { - cmdStr = fmt.Sprintf("%s %s", cmdStr, self.cmd.Quote(opts.BranchName)) - } + cmdStr := NewGitCmd("pull"). + Arg("--no-edit"). + ArgIf(opts.FastForwardOnly, "--ff-only"). + ArgIf(opts.RemoteName != "", self.cmd.Quote(opts.RemoteName)). + ArgIf(opts.BranchName != "", self.cmd.Quote(opts.BranchName)). + ToString() // setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user // has 'pull.rebase = interactive' configured. @@ -112,11 +90,18 @@ func (self *SyncCommands) Pull(opts PullOptions) error { } func (self *SyncCommands) FastForward(branchName string, remoteName string, remoteBranchName string) error { - cmdStr := fmt.Sprintf("git fetch %s %s:%s", self.cmd.Quote(remoteName), self.cmd.Quote(remoteBranchName), self.cmd.Quote(branchName)) + cmdStr := NewGitCmd("fetch"). + Arg(self.cmd.Quote(remoteName)). + Arg(self.cmd.Quote(remoteBranchName) + ":" + self.cmd.Quote(branchName)). + ToString() + return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run() } func (self *SyncCommands) FetchRemote(remoteName string) error { - cmdStr := fmt.Sprintf("git fetch %s", self.cmd.Quote(remoteName)) + cmdStr := NewGitCmd("fetch"). + Arg(self.cmd.Quote(remoteName)). + ToString() + return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run() } diff --git a/pkg/commands/git_commands/tag.go b/pkg/commands/git_commands/tag.go index b4c04c438..ae6b816f5 100644 --- a/pkg/commands/git_commands/tag.go +++ b/pkg/commands/git_commands/tag.go @@ -1,9 +1,5 @@ package git_commands -import ( - "fmt" -) - type TagCommands struct { *GitCommon } @@ -15,25 +11,32 @@ func NewTagCommands(gitCommon *GitCommon) *TagCommands { } func (self *TagCommands) CreateLightweight(tagName string, ref string) error { - if len(ref) > 0 { - return self.cmd.New(fmt.Sprintf("git tag -- %s %s", self.cmd.Quote(tagName), self.cmd.Quote(ref))).Run() - } else { - return self.cmd.New(fmt.Sprintf("git tag -- %s", self.cmd.Quote(tagName))).Run() - } + cmdStr := NewGitCmd("tag").Arg("--", self.cmd.Quote(tagName)). + ArgIf(len(ref) > 0, self.cmd.Quote(ref)). + ToString() + + return self.cmd.New(cmdStr).Run() } func (self *TagCommands) CreateAnnotated(tagName, ref, msg string) error { - if len(ref) > 0 { - return self.cmd.New(fmt.Sprintf("git tag %s %s -m %s", self.cmd.Quote(tagName), self.cmd.Quote(ref), self.cmd.Quote(msg))).Run() - } else { - return self.cmd.New(fmt.Sprintf("git tag %s -m %s", self.cmd.Quote(tagName), self.cmd.Quote(msg))).Run() - } + cmdStr := NewGitCmd("tag").Arg(self.cmd.Quote(tagName)). + ArgIf(len(ref) > 0, self.cmd.Quote(ref)). + Arg("-m", self.cmd.Quote(msg)). + ToString() + + return self.cmd.New(cmdStr).Run() } func (self *TagCommands) Delete(tagName string) error { - return self.cmd.New(fmt.Sprintf("git tag -d %s", self.cmd.Quote(tagName))).Run() + cmdStr := NewGitCmd("tag").Arg("-d", self.cmd.Quote(tagName)). + ToString() + + return self.cmd.New(cmdStr).Run() } func (self *TagCommands) Push(remoteName string, tagName string) error { - return self.cmd.New(fmt.Sprintf("git push %s tag %s", self.cmd.Quote(remoteName), self.cmd.Quote(tagName))).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run() + cmdStr := NewGitCmd("push").Arg(self.cmd.Quote(remoteName), "tag", self.cmd.Quote(tagName)). + ToString() + + return self.cmd.New(cmdStr).PromptOnCredentialRequest().WithMutex(self.syncMutex).Run() } diff --git a/pkg/commands/git_commands/tag_loader.go b/pkg/commands/git_commands/tag_loader.go index 2dc5a4f41..67d26f80b 100644 --- a/pkg/commands/git_commands/tag_loader.go +++ b/pkg/commands/git_commands/tag_loader.go @@ -28,7 +28,8 @@ func NewTagLoader( func (self *TagLoader) GetTags() ([]*models.Tag, error) { // get remote branches, sorted by creation date (descending) // see: https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---sortltkeygt - tagsOutput, err := self.cmd.New(`git tag --list -n --sort=-creatordate`).DontLog().RunWithOutput() + cmdStr := NewGitCmd("tag").Arg("--list", "-n", "--sort=-creatordate").ToString() + tagsOutput, err := self.cmd.New(cmdStr).DontLog().RunWithOutput() if err != nil { return nil, err } diff --git a/pkg/commands/git_commands/version.go b/pkg/commands/git_commands/version.go index 1b1351df4..dcf624679 100644 --- a/pkg/commands/git_commands/version.go +++ b/pkg/commands/git_commands/version.go @@ -15,7 +15,7 @@ type GitVersion struct { } func GetGitVersion(osCommand *oscommands.OSCommand) (*GitVersion, error) { - versionStr, _, err := osCommand.Cmd.New("git --version").RunWithOutputs() + versionStr, _, err := osCommand.Cmd.New(NewGitCmd("--version").ToString()).RunWithOutputs() if err != nil { return nil, err } diff --git a/pkg/commands/git_commands/working_tree.go b/pkg/commands/git_commands/working_tree.go index b7ebe0d30..edfc3de94 100644 --- a/pkg/commands/git_commands/working_tree.go +++ b/pkg/commands/git_commands/working_tree.go @@ -4,7 +4,6 @@ import ( "fmt" "os" "path/filepath" - "strings" "time" "github.com/go-errors/errors" @@ -33,7 +32,7 @@ func NewWorkingTreeCommands( } func (self *WorkingTreeCommands) OpenMergeToolCmdObj() oscommands.ICmdObj { - return self.cmd.New("git mergetool") + return self.cmd.New(NewGitCmd("mergetool").ToString()) } func (self *WorkingTreeCommands) OpenMergeTool() error { @@ -49,30 +48,37 @@ func (self *WorkingTreeCommands) StageFiles(paths []string) error { quotedPaths := slices.Map(paths, func(path string) string { return self.cmd.Quote(path) }) - return self.cmd.New(fmt.Sprintf("git add -- %s", strings.Join(quotedPaths, " "))).Run() + + cmdStr := NewGitCmd("add").Arg("--").Arg(quotedPaths...).ToString() + + return self.cmd.New(cmdStr).Run() } // StageAll stages all files func (self *WorkingTreeCommands) StageAll() error { - return self.cmd.New("git add -A").Run() + cmdStr := NewGitCmd("add").Arg("-A").ToString() + + return self.cmd.New(cmdStr).Run() } // UnstageAll unstages all files func (self *WorkingTreeCommands) UnstageAll() error { - return self.cmd.New("git reset").Run() + return self.cmd.New(NewGitCmd("reset").ToString()).Run() } // UnStageFile unstages a file // we accept an array of filenames for the cases where a file has been renamed i.e. // we accept the current name and the previous name func (self *WorkingTreeCommands) UnStageFile(fileNames []string, reset bool) error { - command := "git rm --cached --force -- %s" - if reset { - command = "git reset HEAD -- %s" - } - for _, name := range fileNames { - err := self.cmd.New(fmt.Sprintf(command, self.cmd.Quote(name))).Run() + var cmdStr string + if reset { + cmdStr = NewGitCmd("reset").Arg("HEAD", "--", self.cmd.Quote(name)).ToString() + } else { + cmdStr = NewGitCmd("rm").Arg("--cached", "--force", "--", self.cmd.Quote(name)).ToString() + } + + err := self.cmd.New(cmdStr).Run() if err != nil { return err } @@ -137,22 +143,31 @@ func (self *WorkingTreeCommands) DiscardAllFileChanges(file *models.File) error quotedFileName := self.cmd.Quote(file.Name) if file.ShortStatus == "AA" { - if err := self.cmd.New("git checkout --ours -- " + quotedFileName).Run(); err != nil { + if err := self.cmd.New( + NewGitCmd("checkout").Arg("--ours", "--", quotedFileName).ToString(), + ).Run(); err != nil { return err } - if err := self.cmd.New("git add -- " + quotedFileName).Run(); err != nil { + + if err := self.cmd.New( + NewGitCmd("add").Arg("--", quotedFileName).ToString(), + ).Run(); err != nil { return err } return nil } if file.ShortStatus == "DU" { - return self.cmd.New("git rm -- " + quotedFileName).Run() + return self.cmd.New( + NewGitCmd("rm").Arg("rm", "--", quotedFileName).ToString(), + ).Run() } // if the file isn't tracked, we assume you want to delete it if file.HasStagedChanges || file.HasMergeConflicts { - if err := self.cmd.New("git reset -- " + quotedFileName).Run(); err != nil { + if err := self.cmd.New( + NewGitCmd("reset").Arg("--", quotedFileName).ToString(), + ).Run(); err != nil { return err } } @@ -184,7 +199,8 @@ func (self *WorkingTreeCommands) DiscardUnstagedDirChanges(node IFileNode) error } quotedPath := self.cmd.Quote(node.GetPath()) - if err := self.cmd.New("git checkout -- " + quotedPath).Run(); err != nil { + cmdStr := NewGitCmd("checkout").Arg("--", quotedPath).ToString() + if err := self.cmd.New(cmdStr).Run(); err != nil { return err } @@ -209,7 +225,8 @@ func (self *WorkingTreeCommands) RemoveUntrackedDirFiles(node IFileNode) error { // DiscardUnstagedFileChanges directly func (self *WorkingTreeCommands) DiscardUnstagedFileChanges(file *models.File) error { quotedFileName := self.cmd.Quote(file.Name) - return self.cmd.New("git checkout -- " + quotedFileName).Run() + cmdStr := NewGitCmd("checkout").Arg("--", quotedFileName).ToString() + return self.cmd.New(cmdStr).Run() } // Ignore adds a file to the gitignore for the repo @@ -230,30 +247,28 @@ func (self *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool, } func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool, ignoreWhitespace bool) oscommands.ICmdObj { - cachedArg := "" - trackedArg := "--" colorArg := self.UserConfig.Git.Paging.ColorArg - quotedPath := self.cmd.Quote(node.GetPath()) - quotedPrevPath := "" - ignoreWhitespaceArg := "" - contextSize := self.UserConfig.Git.DiffContextSize - if cached { - cachedArg = " --cached" - } - if !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile() { - trackedArg = "--no-index -- /dev/null" - } if plain { colorArg = "never" } - if ignoreWhitespace { - ignoreWhitespaceArg = " --ignore-all-space" - } - if prevPath := node.GetPreviousPath(); prevPath != "" { - quotedPrevPath = " " + self.cmd.Quote(prevPath) - } - cmdStr := fmt.Sprintf("git diff --submodule --no-ext-diff --unified=%d --color=%s%s%s %s %s%s", contextSize, colorArg, ignoreWhitespaceArg, cachedArg, trackedArg, quotedPath, quotedPrevPath) + contextSize := self.UserConfig.Git.DiffContextSize + prevPath := node.GetPreviousPath() + noIndex := !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile() + + cmdStr := NewGitCmd("diff"). + Arg("--submodule"). + Arg("--no-ext-diff"). + Arg(fmt.Sprintf("--unified=%d", contextSize)). + Arg(fmt.Sprintf("--color=%s", colorArg)). + ArgIf(ignoreWhitespace, "--ignore-all-space"). + ArgIf(cached, "--cached"). + ArgIf(noIndex, "--no-index"). + Arg("--"). + ArgIf(noIndex, "/dev/null"). + Arg(self.cmd.Quote(node.GetPath())). + ArgIf(prevPath != "", self.cmd.Quote(prevPath)). + ToString() return self.cmd.New(cmdStr).DontLog() } @@ -296,49 +311,59 @@ func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bo func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool, ignoreWhitespace bool, ) oscommands.ICmdObj { - colorArg := self.UserConfig.Git.Paging.ColorArg contextSize := self.UserConfig.Git.DiffContextSize + + colorArg := self.UserConfig.Git.Paging.ColorArg if plain { colorArg = "never" } - reverseFlag := "" - if reverse { - reverseFlag = " -R" - } + cmdStr := NewGitCmd("diff"). + Arg("--submodule"). + Arg("--no-ext-diff"). + Arg(fmt.Sprintf("--unified=%d", contextSize)). + Arg("--no-renames"). + Arg(fmt.Sprintf("--color=%s", colorArg)). + Arg(from). + Arg(to). + ArgIf(reverse, "-R"). + ArgIf(ignoreWhitespace, "--ignore-all-space"). + Arg("--"). + Arg(self.cmd.Quote(fileName)). + ToString() - ignoreWhitespaceFlag := "" - if ignoreWhitespace { - ignoreWhitespaceFlag = " --ignore-all-space" - } - - return self.cmd. - New( - fmt.Sprintf( - "git diff --submodule --no-ext-diff --unified=%d --no-renames --color=%s%s%s%s%s -- %s", - contextSize, colorArg, pad(from), pad(to), reverseFlag, ignoreWhitespaceFlag, self.cmd.Quote(fileName)), - ). - DontLog() + return self.cmd.New(cmdStr).DontLog() } // CheckoutFile checks out the file for the given commit func (self *WorkingTreeCommands) CheckoutFile(commitSha, fileName string) error { - return self.cmd.New(fmt.Sprintf("git checkout %s -- %s", commitSha, self.cmd.Quote(fileName))).Run() + cmdStr := NewGitCmd("checkout").Arg(commitSha, "--", self.cmd.Quote(fileName)). + ToString() + + return self.cmd.New(cmdStr).Run() } -// DiscardAnyUnstagedFileChanges discards any unstages file changes via `git checkout -- .` +// DiscardAnyUnstagedFileChanges discards any unstaged file changes via `git checkout -- .` func (self *WorkingTreeCommands) DiscardAnyUnstagedFileChanges() error { - return self.cmd.New("git checkout -- .").Run() + cmdStr := NewGitCmd("checkout").Arg("--", "."). + ToString() + + return self.cmd.New(cmdStr).Run() } // RemoveTrackedFiles will delete the given file(s) even if they are currently tracked func (self *WorkingTreeCommands) RemoveTrackedFiles(name string) error { - return self.cmd.New("git rm -r --cached -- " + self.cmd.Quote(name)).Run() + cmdStr := NewGitCmd("rm").Arg("-r", "--cached", "--", self.cmd.Quote(name)). + ToString() + + return self.cmd.New(cmdStr).Run() } // RemoveUntrackedFiles runs `git clean -fd` func (self *WorkingTreeCommands) RemoveUntrackedFiles() error { - return self.cmd.New("git clean -fd").Run() + cmdStr := NewGitCmd("clean").Arg("-fd").ToString() + + return self.cmd.New(cmdStr).Run() } // ResetAndClean removes all unstaged changes and removes all untracked files @@ -363,23 +388,23 @@ func (self *WorkingTreeCommands) ResetAndClean() error { // ResetHardHead runs `git reset --hard` func (self *WorkingTreeCommands) ResetHard(ref string) error { - return self.cmd.New("git reset --hard " + self.cmd.Quote(ref)).Run() + cmdStr := NewGitCmd("reset").Arg("--hard", self.cmd.Quote(ref)). + ToString() + + return self.cmd.New(cmdStr).Run() } // ResetSoft runs `git reset --soft HEAD` func (self *WorkingTreeCommands) ResetSoft(ref string) error { - return self.cmd.New("git reset --soft " + self.cmd.Quote(ref)).Run() + cmdStr := NewGitCmd("reset").Arg("--soft", self.cmd.Quote(ref)). + ToString() + + return self.cmd.New(cmdStr).Run() } func (self *WorkingTreeCommands) ResetMixed(ref string) error { - return self.cmd.New("git reset --mixed " + self.cmd.Quote(ref)).Run() -} + cmdStr := NewGitCmd("reset").Arg("--mixed", self.cmd.Quote(ref)). + ToString() -// so that we don't have unnecessary space in our commands we use this helper function to prepend spaces to args so that in the format string we can go '%s%s%s' and if any args are missing we won't have gaps. -func pad(str string) string { - if str == "" { - return "" - } - - return " " + str + return self.cmd.New(cmdStr).Run() }