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

View File

@ -151,7 +151,7 @@ func NewApp(config config.AppConfigurer, filterPath string) (*App, error) {
}
func (app *App) validateGitVersion() error {
output, err := app.OSCommand.RunWithOutput(app.OSCommand.NewCmdObj("git --version"))
output, err := app.OSCommand.Cmd.New("git --version").RunWithOutput()
// if we get an error anywhere here we'll show the same status
minVersionError := errors.New(app.Tr.MinGitVersionError)
if err != nil {
@ -236,7 +236,7 @@ func (app *App) setupRepo() (bool, error) {
os.Exit(1)
}
if err := app.OSCommand.Run(app.OSCommand.NewCmdObj("git init")); err != nil {
if err := app.OSCommand.Cmd.New("git init").Run(); err != nil {
return false, err
}
}

View File

@ -11,19 +11,19 @@ import (
// NewBranch create new branch
func (c *GitCommand) NewBranch(name string, base string) error {
return c.Run(c.NewCmdObj(fmt.Sprintf("git checkout -b %s %s", c.OSCommand.Quote(name), c.OSCommand.Quote(base))))
return c.Cmd.New(fmt.Sprintf("git checkout -b %s %s", c.OSCommand.Quote(name), c.OSCommand.Quote(base))).Run()
}
// CurrentBranchName get the current branch name and displayname.
// the first returned string is the name and the second is the displayname
// e.g. name is 123asdf and displayname is '(HEAD detached at 123asdf)'
func (c *GitCommand) CurrentBranchName() (string, string, error) {
branchName, err := c.RunWithOutput(c.NewCmdObj("git symbolic-ref --short HEAD"))
branchName, err := c.Cmd.New("git symbolic-ref --short HEAD").RunWithOutput()
if err == nil && branchName != "HEAD\n" {
trimmedBranchName := strings.TrimSpace(branchName)
return trimmedBranchName, trimmedBranchName, nil
}
output, err := c.RunWithOutput(c.NewCmdObj("git branch --contains"))
output, err := c.Cmd.New("git branch --contains").RunWithOutput()
if err != nil {
return "", "", err
}
@ -47,7 +47,7 @@ func (c *GitCommand) DeleteBranch(branch string, force bool) error {
command = "git branch -D"
}
return c.OSCommand.Run(c.OSCommand.NewCmdObj(fmt.Sprintf("%s %s", command, c.OSCommand.Quote(branch))))
return c.Cmd.New(fmt.Sprintf("%s %s", command, c.OSCommand.Quote(branch))).Run()
}
// Checkout checks out a branch (or commit), with --force if you set the force arg to true
@ -62,24 +62,23 @@ func (c *GitCommand) Checkout(branch string, options CheckoutOptions) error {
forceArg = " --force"
}
cmdObj := c.NewCmdObj(fmt.Sprintf("git checkout%s %s", forceArg, c.OSCommand.Quote(branch))).
return c.Cmd.New(fmt.Sprintf("git checkout%s %s", forceArg, c.OSCommand.Quote(branch))).
// prevents git from prompting us for input which would freeze the program
// TODO: see if this is actually needed here
AddEnvVars("GIT_TERMINAL_PROMPT=0").
AddEnvVars(options.EnvVars...)
return c.OSCommand.Run(cmdObj)
AddEnvVars(options.EnvVars...).
Run()
}
// GetBranchGraph gets the color-formatted graph of the log for the given branch
// Currently it limits the result to 100 commits, but when we get async stuff
// working we can do lazy loading
func (c *GitCommand) GetBranchGraph(branchName string) (string, error) {
return c.OSCommand.RunWithOutput(c.GetBranchGraphCmdObj(branchName))
return c.GetBranchGraphCmdObj(branchName).RunWithOutput()
}
func (c *GitCommand) GetUpstreamForBranch(branchName string) (string, error) {
output, err := c.RunWithOutput(c.NewCmdObj(fmt.Sprintf("git rev-parse --abbrev-ref --symbolic-full-name %s@{u}", c.OSCommand.Quote(branchName))))
output, err := c.Cmd.New(fmt.Sprintf("git rev-parse --abbrev-ref --symbolic-full-name %s@{u}", c.OSCommand.Quote(branchName))).RunWithOutput()
return strings.TrimSpace(output), err
}
@ -88,15 +87,15 @@ func (c *GitCommand) GetBranchGraphCmdObj(branchName string) oscommands.ICmdObj
templateValues := map[string]string{
"branchName": c.OSCommand.Quote(branchName),
}
return c.NewCmdObj(utils.ResolvePlaceholderString(branchLogCmdTemplate, templateValues))
return c.Cmd.New(utils.ResolvePlaceholderString(branchLogCmdTemplate, templateValues))
}
func (c *GitCommand) SetUpstreamBranch(upstream string) error {
return c.Run(c.NewCmdObj("git branch -u " + c.OSCommand.Quote(upstream)))
return c.Cmd.New("git branch -u " + c.OSCommand.Quote(upstream)).Run()
}
func (c *GitCommand) SetBranchUpstream(remoteName string, remoteBranchName string, branchName string) error {
return c.Run(c.NewCmdObj(fmt.Sprintf("git branch --set-upstream-to=%s/%s %s", c.OSCommand.Quote(remoteName), c.OSCommand.Quote(remoteBranchName), c.OSCommand.Quote(branchName))))
return c.Cmd.New(fmt.Sprintf("git branch --set-upstream-to=%s/%s %s", c.OSCommand.Quote(remoteName), c.OSCommand.Quote(remoteBranchName), c.OSCommand.Quote(branchName))).Run()
}
func (c *GitCommand) GetCurrentBranchUpstreamDifferenceCount() (string, string) {
@ -111,11 +110,11 @@ func (c *GitCommand) GetBranchUpstreamDifferenceCount(branchName string) (string
// current branch
func (c *GitCommand) GetCommitDifferences(from, to string) (string, string) {
command := "git rev-list %s..%s --count"
pushableCount, err := c.RunWithOutput(c.NewCmdObj(fmt.Sprintf(command, to, from)))
pushableCount, err := c.Cmd.New(fmt.Sprintf(command, to, from)).RunWithOutput()
if err != nil {
return "?", "?"
}
pullableCount, err := c.RunWithOutput(c.NewCmdObj(fmt.Sprintf(command, from, to)))
pullableCount, err := c.Cmd.New(fmt.Sprintf(command, from, to)).RunWithOutput()
if err != nil {
return "?", "?"
}
@ -135,37 +134,37 @@ func (c *GitCommand) Merge(branchName string, opts MergeOpts) error {
command = fmt.Sprintf("%s --ff-only", command)
}
return c.OSCommand.Run(c.OSCommand.NewCmdObj(command))
return c.OSCommand.Cmd.New(command).Run()
}
// AbortMerge abort merge
func (c *GitCommand) AbortMerge() error {
return c.Run(c.NewCmdObj("git merge --abort"))
return c.Cmd.New("git merge --abort").Run()
}
func (c *GitCommand) IsHeadDetached() bool {
err := c.Run(c.NewCmdObj("git symbolic-ref -q HEAD"))
err := c.Cmd.New("git symbolic-ref -q HEAD").Run()
return err != nil
}
// ResetHardHead runs `git reset --hard`
func (c *GitCommand) ResetHard(ref string) error {
return c.Run(c.NewCmdObj("git reset --hard " + c.OSCommand.Quote(ref)))
return c.Cmd.New("git reset --hard " + c.OSCommand.Quote(ref)).Run()
}
// ResetSoft runs `git reset --soft HEAD`
func (c *GitCommand) ResetSoft(ref string) error {
return c.Run(c.NewCmdObj("git reset --soft " + c.OSCommand.Quote(ref)))
return c.Cmd.New("git reset --soft " + c.OSCommand.Quote(ref)).Run()
}
func (c *GitCommand) ResetMixed(ref string) error {
return c.Run(c.NewCmdObj("git reset --mixed " + c.OSCommand.Quote(ref)))
return c.Cmd.New("git reset --mixed " + c.OSCommand.Quote(ref)).Run()
}
func (c *GitCommand) RenameBranch(oldName string, newName string) error {
return c.Run(c.NewCmdObj(fmt.Sprintf("git branch --move %s %s", c.OSCommand.Quote(oldName), c.OSCommand.Quote(newName))))
return c.Cmd.New(fmt.Sprintf("git branch --move %s %s", c.OSCommand.Quote(oldName), c.OSCommand.Quote(newName))).Run()
}
func (c *GitCommand) GetRawBranches() (string, error) {
return c.RunWithOutput(c.NewCmdObj(`git for-each-ref --sort=-committerdate --format="%(HEAD)|%(refname:short)|%(upstream:short)|%(upstream:track)" refs/heads`))
return c.Cmd.New(`git for-each-ref --sort=-committerdate --format="%(HEAD)|%(refname:short)|%(upstream:short)|%(upstream:track)" refs/heads`).RunWithOutput()
}

View File

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

View File

@ -10,18 +10,17 @@ import (
// RenameCommit renames the topmost commit with the given name
func (c *GitCommand) RenameCommit(name string) error {
return c.Run(c.NewCmdObj("git commit --allow-empty --amend --only -m " + c.OSCommand.Quote(name)))
return c.Cmd.New("git commit --allow-empty --amend --only -m " + c.OSCommand.Quote(name)).Run()
}
// ResetToCommit reset to commit
func (c *GitCommand) ResetToCommit(sha string, strength string, envVars []string) error {
cmdObj := c.NewCmdObj(fmt.Sprintf("git reset --%s %s", strength, sha)).
return c.Cmd.New(fmt.Sprintf("git reset --%s %s", strength, sha)).
// prevents git from prompting us for input which would freeze the program
// TODO: see if this is actually needed here
AddEnvVars("GIT_TERMINAL_PROMPT=0").
AddEnvVars(envVars...)
return c.OSCommand.Run(cmdObj)
AddEnvVars(envVars...).
Run()
}
func (c *GitCommand) CommitCmdObj(message string, flags string) oscommands.ICmdObj {
@ -36,33 +35,33 @@ func (c *GitCommand) CommitCmdObj(message string, flags string) oscommands.ICmdO
flagsStr = fmt.Sprintf(" %s", flags)
}
return c.NewCmdObj(fmt.Sprintf("git commit%s%s", flagsStr, lineArgs))
return c.Cmd.New(fmt.Sprintf("git commit%s%s", flagsStr, lineArgs))
}
// Get the subject of the HEAD commit
func (c *GitCommand) GetHeadCommitMessage() (string, error) {
message, err := c.RunWithOutput(c.NewCmdObj("git log -1 --pretty=%s"))
message, err := c.Cmd.New("git log -1 --pretty=%s").RunWithOutput()
return strings.TrimSpace(message), err
}
func (c *GitCommand) GetCommitMessage(commitSha string) (string, error) {
cmdStr := "git rev-list --format=%B --max-count=1 " + commitSha
messageWithHeader, err := c.RunWithOutput(c.NewCmdObj(cmdStr))
messageWithHeader, err := c.Cmd.New(cmdStr).RunWithOutput()
message := strings.Join(strings.SplitAfter(messageWithHeader, "\n")[1:], "\n")
return strings.TrimSpace(message), err
}
func (c *GitCommand) GetCommitMessageFirstLine(sha string) (string, error) {
return c.RunWithOutput(c.NewCmdObj(fmt.Sprintf("git show --no-patch --pretty=format:%%s %s", sha)))
return c.Cmd.New(fmt.Sprintf("git show --no-patch --pretty=format:%%s %s", sha)).RunWithOutput()
}
// AmendHead amends HEAD with whatever is staged in your working tree
func (c *GitCommand) AmendHead() error {
return c.OSCommand.Run(c.AmendHeadCmdObj())
return c.AmendHeadCmdObj().Run()
}
func (c *GitCommand) AmendHeadCmdObj() oscommands.ICmdObj {
return c.NewCmdObj("git commit --amend --no-edit --allow-empty")
return c.Cmd.New("git commit --amend --no-edit --allow-empty")
}
func (c *GitCommand) ShowCmdObj(sha string, filterPath string) oscommands.ICmdObj {
@ -73,16 +72,16 @@ func (c *GitCommand) ShowCmdObj(sha string, filterPath string) oscommands.ICmdOb
}
cmdStr := fmt.Sprintf("git show --submodule --color=%s --unified=%d --no-renames --stat -p %s %s", c.colorArg(), contextSize, sha, filterPathArg)
return c.NewCmdObj(cmdStr)
return c.Cmd.New(cmdStr)
}
// Revert reverts the selected commit by sha
func (c *GitCommand) Revert(sha string) error {
return c.Run(c.NewCmdObj(fmt.Sprintf("git revert %s", sha)))
return c.Cmd.New(fmt.Sprintf("git revert %s", sha)).Run()
}
func (c *GitCommand) RevertMerge(sha string, parentNumber int) error {
return c.Run(c.NewCmdObj(fmt.Sprintf("git revert %s -m %d", sha, parentNumber)))
return c.Cmd.New(fmt.Sprintf("git revert %s -m %d", sha, parentNumber)).Run()
}
// CherryPickCommits begins an interactive rebase with the given shas being cherry picked onto HEAD
@ -92,15 +91,15 @@ func (c *GitCommand) CherryPickCommits(commits []*models.Commit) error {
todo = "pick " + commit.Sha + " " + commit.Name + "\n" + todo
}
cmd, err := c.PrepareInteractiveRebaseCommand("HEAD", todo, false)
cmdObj, err := c.PrepareInteractiveRebaseCommand("HEAD", todo, false)
if err != nil {
return err
}
return c.OSCommand.Run(cmd)
return cmdObj.Run()
}
// CreateFixupCommit creates a commit that fixes up a previous commit
func (c *GitCommand) CreateFixupCommit(sha string) error {
return c.Run(c.NewCmdObj(fmt.Sprintf("git commit --fixup=%s", sha)))
return c.Cmd.New(fmt.Sprintf("git commit --fixup=%s", sha)).Run()
}

View File

@ -25,26 +25,26 @@ func (c *GitCommand) CatFile(fileName string) (string, error) {
}
func (c *GitCommand) OpenMergeToolCmdObj() oscommands.ICmdObj {
return c.NewCmdObj("git mergetool")
return c.Cmd.New("git mergetool")
}
func (c *GitCommand) OpenMergeTool() error {
return c.Run(c.OpenMergeToolCmdObj())
return c.OpenMergeToolCmdObj().Run()
}
// StageFile stages a file
func (c *GitCommand) StageFile(fileName string) error {
return c.Run(c.NewCmdObj("git add -- " + c.OSCommand.Quote(fileName)))
return c.Cmd.New("git add -- " + c.OSCommand.Quote(fileName)).Run()
}
// StageAll stages all files
func (c *GitCommand) StageAll() error {
return c.Run(c.NewCmdObj("git add -A"))
return c.Cmd.New("git add -A").Run()
}
// UnstageAll unstages all files
func (c *GitCommand) UnstageAll() error {
return c.Run(c.NewCmdObj("git reset"))
return c.Cmd.New("git reset").Run()
}
// UnStageFile unstages a file
@ -57,8 +57,8 @@ func (c *GitCommand) UnStageFile(fileNames []string, reset bool) error {
}
for _, name := range fileNames {
cmdObj := c.NewCmdObj(fmt.Sprintf(command, c.OSCommand.Quote(name)))
if err := c.Run(cmdObj); err != nil {
err := c.Cmd.New(fmt.Sprintf(command, c.OSCommand.Quote(name))).Run()
if err != nil {
return err
}
}
@ -122,22 +122,22 @@ func (c *GitCommand) DiscardAllFileChanges(file *models.File) error {
quotedFileName := c.OSCommand.Quote(file.Name)
if file.ShortStatus == "AA" {
if err := c.Run(c.NewCmdObj("git checkout --ours -- " + quotedFileName)); err != nil {
if err := c.Cmd.New("git checkout --ours -- " + quotedFileName).Run(); err != nil {
return err
}
if err := c.Run(c.NewCmdObj("git add -- " + quotedFileName)); err != nil {
if err := c.Cmd.New("git add -- " + quotedFileName).Run(); err != nil {
return err
}
return nil
}
if file.ShortStatus == "DU" {
return c.Run(c.NewCmdObj("git rm -- " + quotedFileName))
return c.Cmd.New("git rm -- " + quotedFileName).Run()
}
// if the file isn't tracked, we assume you want to delete it
if file.HasStagedChanges || file.HasMergeConflicts {
if err := c.Run(c.NewCmdObj("git reset -- " + quotedFileName)); err != nil {
if err := c.Cmd.New("git reset -- " + quotedFileName).Run(); err != nil {
return err
}
}
@ -163,7 +163,7 @@ func (c *GitCommand) DiscardUnstagedDirChanges(node *filetree.FileNode) error {
}
quotedPath := c.OSCommand.Quote(node.GetPath())
if err := c.Run(c.NewCmdObj("git checkout -- " + quotedPath)); err != nil {
if err := c.Cmd.New("git checkout -- " + quotedPath).Run(); err != nil {
return err
}
@ -188,7 +188,7 @@ func (c *GitCommand) RemoveUntrackedDirFiles(node *filetree.FileNode) error {
// DiscardUnstagedFileChanges directly
func (c *GitCommand) DiscardUnstagedFileChanges(file *models.File) error {
quotedFileName := c.OSCommand.Quote(file.Name)
return c.Run(c.NewCmdObj("git checkout -- " + quotedFileName))
return c.Cmd.New("git checkout -- " + quotedFileName).Run()
}
// Ignore adds a file to the gitignore for the repo
@ -199,7 +199,7 @@ func (c *GitCommand) Ignore(filename string) error {
// WorktreeFileDiff returns the diff of a file
func (c *GitCommand) WorktreeFileDiff(file *models.File, plain bool, cached bool, ignoreWhitespace bool) string {
// for now we assume an error means the file was deleted
s, _ := c.OSCommand.RunWithOutput(c.WorktreeFileDiffCmdObj(file, plain, cached, ignoreWhitespace))
s, _ := c.WorktreeFileDiffCmdObj(file, plain, cached, ignoreWhitespace).RunWithOutput()
return s
}
@ -225,7 +225,7 @@ func (c *GitCommand) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cache
cmdStr := fmt.Sprintf("git diff --submodule --no-ext-diff --unified=%d --color=%s %s %s %s %s", contextSize, colorArg, ignoreWhitespaceArg, cachedArg, trackedArg, quotedPath)
return c.NewCmdObj(cmdStr)
return c.Cmd.New(cmdStr)
}
func (c *GitCommand) ApplyPatch(patch string, flags ...string) error {
@ -240,14 +240,13 @@ func (c *GitCommand) ApplyPatch(patch string, flags ...string) error {
flagStr += " --" + flag
}
return c.Run(c.NewCmdObj(fmt.Sprintf("git apply %s %s", flagStr, c.OSCommand.Quote(filepath))))
return c.Cmd.New(fmt.Sprintf("git apply %s %s", flagStr, c.OSCommand.Quote(filepath))).Run()
}
// ShowFileDiff get the diff of specified from and to. Typically this will be used for a single commit so it'll be 123abc^..123abc
// but when we're in diff mode it could be any 'from' to any 'to'. The reverse flag is also here thanks to diff mode.
func (c *GitCommand) ShowFileDiff(from string, to string, reverse bool, fileName string, plain bool) (string, error) {
cmdObj := c.ShowFileDiffCmdObj(from, to, reverse, fileName, plain)
return c.RunWithOutput(cmdObj)
return c.ShowFileDiffCmdObj(from, to, reverse, fileName, plain).RunWithOutput()
}
func (c *GitCommand) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool) oscommands.ICmdObj {
@ -262,12 +261,12 @@ func (c *GitCommand) ShowFileDiffCmdObj(from string, to string, reverse bool, fi
reverseFlag = " -R "
}
return c.NewCmdObj(fmt.Sprintf("git diff --submodule --no-ext-diff --unified=%d --no-renames --color=%s %s %s %s -- %s", contextSize, colorArg, from, to, reverseFlag, c.OSCommand.Quote(fileName)))
return c.Cmd.New(fmt.Sprintf("git diff --submodule --no-ext-diff --unified=%d --no-renames --color=%s %s %s %s -- %s", contextSize, colorArg, from, to, reverseFlag, c.OSCommand.Quote(fileName)))
}
// CheckoutFile checks out the file for the given commit
func (c *GitCommand) CheckoutFile(commitSha, fileName string) error {
return c.Run(c.NewCmdObj(fmt.Sprintf("git checkout %s -- %s", commitSha, c.OSCommand.Quote(fileName))))
return c.Cmd.New(fmt.Sprintf("git checkout %s -- %s", commitSha, c.OSCommand.Quote(fileName))).Run()
}
// DiscardOldFileChanges discards changes to a file from an old commit
@ -277,7 +276,7 @@ func (c *GitCommand) DiscardOldFileChanges(commits []*models.Commit, commitIndex
}
// check if file exists in previous commit (this command returns an error if the file doesn't exist)
if err := c.Run(c.NewCmdObj("git cat-file -e HEAD^:" + c.OSCommand.Quote(fileName))); err != nil {
if err := c.Cmd.New("git cat-file -e HEAD^:" + c.OSCommand.Quote(fileName)).Run(); err != nil {
if err := c.OSCommand.Remove(fileName); err != nil {
return err
}
@ -300,17 +299,17 @@ func (c *GitCommand) DiscardOldFileChanges(commits []*models.Commit, commitIndex
// DiscardAnyUnstagedFileChanges discards any unstages file changes via `git checkout -- .`
func (c *GitCommand) DiscardAnyUnstagedFileChanges() error {
return c.Run(c.NewCmdObj("git checkout -- ."))
return c.Cmd.New("git checkout -- .").Run()
}
// RemoveTrackedFiles will delete the given file(s) even if they are currently tracked
func (c *GitCommand) RemoveTrackedFiles(name string) error {
return c.Run(c.NewCmdObj("git rm -r --cached -- " + c.OSCommand.Quote(name)))
return c.Cmd.New("git rm -r --cached -- " + c.OSCommand.Quote(name)).Run()
}
// RemoveUntrackedFiles runs `git clean -fd`
func (c *GitCommand) RemoveUntrackedFiles() error {
return c.Run(c.NewCmdObj("git clean -fd"))
return c.Cmd.New("git clean -fd").Run()
}
// ResetAndClean removes all unstaged changes and removes all untracked files
@ -350,7 +349,7 @@ func (c *GitCommand) EditFileCmdStr(filename string, lineNumber int) (string, er
editor = c.OSCommand.Getenv("EDITOR")
}
if editor == "" {
if err := c.OSCommand.Run(c.NewCmdObj("which vi")); err == nil {
if err := c.OSCommand.Cmd.New("which vi").Run(); err == nil {
editor = "vi"
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,9 +11,8 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/sirupsen/logrus"
)
// context:
@ -29,24 +28,28 @@ const SEPARATION_CHAR = "|"
// CommitListBuilder returns a list of Branch objects for the current repo
type CommitListBuilder struct {
Log *logrus.Entry
GitCommand *GitCommand
OSCommand *oscommands.OSCommand
Tr *i18n.TranslationSet
*common.Common
cmd oscommands.ICmdObjBuilder
getCurrentBranchName func() (string, string, error)
getRebaseMode func() (string, error)
readFile func(filename string) ([]byte, error)
dotGitDir string
}
// NewCommitListBuilder builds a new commit list builder
func NewCommitListBuilder(
log *logrus.Entry,
cmn *common.Common,
gitCommand *GitCommand,
osCommand *oscommands.OSCommand,
tr *i18n.TranslationSet,
) *CommitListBuilder {
return &CommitListBuilder{
Log: log,
GitCommand: gitCommand,
OSCommand: osCommand,
Tr: tr,
Common: cmn,
cmd: gitCommand.Cmd,
getCurrentBranchName: gitCommand.CurrentBranchName,
getRebaseMode: gitCommand.RebaseMode,
dotGitDir: gitCommand.DotGitDir,
readFile: ioutil.ReadFile,
}
}
@ -106,7 +109,7 @@ func (c *CommitListBuilder) MergeRebasingCommits(commits []*models.Commit) ([]*m
}
}
rebaseMode, err := c.GitCommand.RebaseMode()
rebaseMode, err := c.getRebaseMode()
if err != nil {
return nil, err
}
@ -131,7 +134,7 @@ func (c *CommitListBuilder) MergeRebasingCommits(commits []*models.Commit) ([]*m
func (c *CommitListBuilder) GetCommits(opts GetCommitsOptions) ([]*models.Commit, error) {
commits := []*models.Commit{}
var rebasingCommits []*models.Commit
rebaseMode, err := c.GitCommand.RebaseMode()
rebaseMode, err := c.getRebaseMode()
if err != nil {
return nil, err
}
@ -152,9 +155,7 @@ func (c *CommitListBuilder) GetCommits(opts GetCommitsOptions) ([]*models.Commit
passedFirstPushedCommit = true
}
cmdObj := c.getLogCmd(opts)
err = c.OSCommand.RunLineOutputCmd(cmdObj, func(line string) (bool, error) {
err = c.getLogCmd(opts).RunLineOutputCmd(func(line string) (bool, error) {
if canExtractCommit(line) {
commit := c.extractCommitFromLine(line)
if commit.Sha == firstPushedCommit {
@ -200,7 +201,7 @@ func (c *CommitListBuilder) getHydratedRebasingCommits(rebaseMode string) ([]*mo
// note that we're not filtering these as we do non-rebasing commits just because
// I suspect that will cause some damage
cmdObj := c.OSCommand.NewCmdObj(
cmdObj := c.cmd.New(
fmt.Sprintf(
"git show %s --no-patch --oneline %s --abbrev=%d",
strings.Join(commitShas, " "),
@ -211,7 +212,7 @@ func (c *CommitListBuilder) getHydratedRebasingCommits(rebaseMode string) ([]*mo
hydratedCommits := make([]*models.Commit, 0, len(commits))
i := 0
err = c.OSCommand.RunLineOutputCmd(cmdObj, func(line string) (bool, error) {
err = cmdObj.RunLineOutputCmd(func(line string) (bool, error) {
if canExtractCommit(line) {
commit := c.extractCommitFromLine(line)
matchingCommit := commits[i]
@ -242,7 +243,7 @@ func (c *CommitListBuilder) getRebasingCommits(rebaseMode string) ([]*models.Com
func (c *CommitListBuilder) getNormalRebasingCommits() ([]*models.Commit, error) {
rewrittenCount := 0
bytesContent, err := ioutil.ReadFile(filepath.Join(c.GitCommand.DotGitDir, "rebase-apply/rewritten"))
bytesContent, err := c.readFile(filepath.Join(c.dotGitDir, "rebase-apply/rewritten"))
if err == nil {
content := string(bytesContent)
rewrittenCount = len(strings.Split(content, "\n"))
@ -250,7 +251,7 @@ func (c *CommitListBuilder) getNormalRebasingCommits() ([]*models.Commit, error)
// we know we're rebasing, so lets get all the files whose names have numbers
commits := []*models.Commit{}
err = filepath.Walk(filepath.Join(c.GitCommand.DotGitDir, "rebase-apply"), func(path string, f os.FileInfo, err error) error {
err = filepath.Walk(filepath.Join(c.dotGitDir, "rebase-apply"), func(path string, f os.FileInfo, err error) error {
if rewrittenCount > 0 {
rewrittenCount--
return nil
@ -262,7 +263,7 @@ func (c *CommitListBuilder) getNormalRebasingCommits() ([]*models.Commit, error)
if !re.MatchString(f.Name()) {
return nil
}
bytesContent, err := ioutil.ReadFile(path)
bytesContent, err := c.readFile(path)
if err != nil {
return err
}
@ -294,7 +295,7 @@ func (c *CommitListBuilder) getNormalRebasingCommits() ([]*models.Commit, error)
// and extracts out the sha and names of commits that we still have to go
// in the rebase:
func (c *CommitListBuilder) getInteractiveRebasingCommits() ([]*models.Commit, error) {
bytesContent, err := ioutil.ReadFile(filepath.Join(c.GitCommand.DotGitDir, "rebase-merge/git-rebase-todo"))
bytesContent, err := c.readFile(filepath.Join(c.dotGitDir, "rebase-merge/git-rebase-todo"))
if err != nil {
c.Log.Error(fmt.Sprintf("error occurred reading git-rebase-todo: %s", err.Error()))
// we assume an error means the file doesn't exist so we just return
@ -362,7 +363,7 @@ func (c *CommitListBuilder) setCommitMergedStatuses(refName string, commits []*m
}
func (c *CommitListBuilder) getMergeBase(refName string) (string, error) {
currentBranch, _, err := c.GitCommand.CurrentBranchName()
currentBranch, _, err := c.getCurrentBranchName()
if err != nil {
return "", err
}
@ -373,7 +374,7 @@ func (c *CommitListBuilder) getMergeBase(refName string) (string, error) {
}
// swallowing error because it's not a big deal; probably because there are no commits yet
output, _ := c.OSCommand.RunWithOutput(c.OSCommand.NewCmdObj(fmt.Sprintf("git merge-base %s %s", c.OSCommand.Quote(refName), c.OSCommand.Quote(baseBranch))))
output, _ := c.cmd.New(fmt.Sprintf("git merge-base %s %s", c.cmd.Quote(refName), c.cmd.Quote(baseBranch))).RunWithOutput()
return ignoringWarnings(output), nil
}
@ -390,7 +391,7 @@ func ignoringWarnings(commandOutput string) string {
// getFirstPushedCommit returns the first commit SHA which has been pushed to the ref's upstream.
// all commits above this are deemed unpushed and marked as such.
func (c *CommitListBuilder) getFirstPushedCommit(refName string) (string, error) {
output, err := c.OSCommand.RunWithOutput(c.OSCommand.NewCmdObj(fmt.Sprintf("git merge-base %s %s@{u}", c.OSCommand.Quote(refName), c.OSCommand.Quote(refName))))
output, err := c.cmd.New(fmt.Sprintf("git merge-base %s %s@{u}", c.cmd.Quote(refName), c.cmd.Quote(refName))).RunWithOutput()
if err != nil {
return "", err
}
@ -407,10 +408,10 @@ func (c *CommitListBuilder) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj
filterFlag := ""
if opts.FilterPath != "" {
filterFlag = fmt.Sprintf(" --follow -- %s", c.OSCommand.Quote(opts.FilterPath))
filterFlag = fmt.Sprintf(" --follow -- %s", c.cmd.Quote(opts.FilterPath))
}
config := c.GitCommand.UserConfig.Git.Log
config := c.UserConfig.Git.Log
orderFlag := "--" + config.Order
allFlag := ""
@ -418,10 +419,10 @@ func (c *CommitListBuilder) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj
allFlag = " --all"
}
return c.OSCommand.NewCmdObj(
return c.cmd.New(
fmt.Sprintf(
"git log %s %s %s --oneline %s %s --abbrev=%d %s",
c.OSCommand.Quote(opts.RefName),
c.cmd.Quote(opts.RefName),
orderFlag,
allFlag,
prettyFormat,

View File

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

View File

@ -18,9 +18,9 @@ func (c *GitCommand) GetReflogCommits(lastReflogCommit *models.Commit, filterPat
filterPathArg = fmt.Sprintf(" --follow -- %s", c.OSCommand.Quote(filterPath))
}
cmdObj := c.OSCommand.NewCmdObj(fmt.Sprintf(`git log -g --abbrev=20 --format="%%h %%ct %%gs" %s`, filterPathArg))
cmdObj := c.OSCommand.Cmd.New(fmt.Sprintf(`git log -g --abbrev=20 --format="%%h %%ct %%gs" %s`, filterPathArg))
onlyObtainedNewReflogCommits := false
err := c.OSCommand.RunLineOutputCmd(cmdObj, func(line string) (bool, error) {
err := cmdObj.RunLineOutputCmd(func(line string) (bool, error) {
fields := strings.SplitN(line, " ", 3)
if len(fields) <= 2 {
return false, nil

View File

@ -10,7 +10,7 @@ import (
)
func (c *GitCommand) GetRemotes() ([]*models.Remote, error) {
remoteBranchesStr, err := c.RunWithOutput(c.NewCmdObj("git branch -r"))
remoteBranchesStr, err := c.Cmd.New("git branch -r").RunWithOutput()
if err != nil {
return nil, err
}

View File

@ -10,7 +10,7 @@ import (
)
func (c *GitCommand) getUnfilteredStashEntries() []*models.StashEntry {
rawString, _ := c.RunWithOutput(c.NewCmdObj("git stash list --pretty='%gs'"))
rawString, _ := c.Cmd.New("git stash list --pretty='%gs'").RunWithOutput()
stashEntries := []*models.StashEntry{}
for i, line := range utils.SplitLines(rawString) {
stashEntries = append(stashEntries, stashEntryFromLine(line, i))
@ -24,7 +24,7 @@ func (c *GitCommand) GetStashEntries(filterPath string) []*models.StashEntry {
return c.getUnfilteredStashEntries()
}
rawString, err := c.RunWithOutput(c.NewCmdObj("git stash list --name-only"))
rawString, err := c.Cmd.New("git stash list --name-only").RunWithOutput()
if err != nil {
return c.getUnfilteredStashEntries()
}

View File

@ -10,7 +10,7 @@ import (
func (c *GitCommand) GetTags() ([]*models.Tag, error) {
// get remote branches, sorted by creation date (descending)
// see: https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---sortltkeygt
remoteBranchesStr, err := c.OSCommand.RunWithOutput(c.NewCmdObj(`git tag --list --sort=-creatordate`))
remoteBranchesStr, err := c.Cmd.New(`git tag --list --sort=-creatordate`).RunWithOutput()
if err != nil {
return nil, err
}

View File

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

View File

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

View File

@ -0,0 +1,79 @@
package oscommands
import (
"bufio"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/sirupsen/logrus"
)
type ICmdObjRunner interface {
Run(cmdObj ICmdObj) error
RunWithOutput(cmdObj ICmdObj) (string, error)
RunLineOutputCmd(cmdObj ICmdObj, onLine func(line string) (bool, error)) error
}
type RunExpectation func(ICmdObj) (string, error)
type RealRunner struct {
log *logrus.Entry
logCmdObj func(ICmdObj)
}
func (self *RealRunner) Run(cmdObj ICmdObj) error {
_, err := self.RunWithOutput(cmdObj)
return err
}
func (self *RealRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
self.logCmdObj(cmdObj)
output, err := sanitisedCommandOutput(cmdObj.GetCmd().CombinedOutput())
if err != nil {
self.log.WithField("command", cmdObj.ToString()).Error(output)
}
return output, err
}
func (self *RealRunner) RunLineOutputCmd(cmdObj ICmdObj, onLine func(line string) (bool, error)) error {
cmd := cmdObj.GetCmd()
stdoutPipe, err := cmd.StdoutPipe()
if err != nil {
return err
}
scanner := bufio.NewScanner(stdoutPipe)
scanner.Split(bufio.ScanLines)
if err := cmd.Start(); err != nil {
return err
}
for scanner.Scan() {
line := scanner.Text()
stop, err := onLine(line)
if err != nil {
return err
}
if stop {
_ = cmd.Process.Kill()
break
}
}
_ = cmd.Wait()
return nil
}
func sanitisedCommandOutput(output []byte, err error) (string, error) {
outputString := string(output)
if err != nil {
// errors like 'exit status 1' are not very useful so we'll create an error
// from the combined output
if outputString == "" {
return "", utils.WrapError(err)
}
return outputString, errors.New(outputString)
}
return outputString, nil
}

View File

@ -1,7 +1,6 @@
package oscommands
import (
"bufio"
"fmt"
"io/ioutil"
"os"
@ -16,7 +15,6 @@ import (
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/secureexec"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/mgutz/str"
)
// Platform stores the os state
@ -55,7 +53,7 @@ type OSCommand struct {
removeFile func(string) error
IRunner
Cmd *CmdObjBuilder
}
// TODO: make these fields private
@ -90,15 +88,20 @@ func NewCmdLogEntry(cmdStr string, span string, commandLine bool) CmdLogEntry {
// NewOSCommand os command runner
func NewOSCommand(common *common.Common) *OSCommand {
command := secureexec.Command
platform := getPlatform()
c := &OSCommand{
Common: common,
Platform: getPlatform(),
Command: secureexec.Command,
Platform: platform,
Command: command,
Getenv: os.Getenv,
removeFile: os.RemoveAll,
}
c.IRunner = &RealRunner{c: c}
runner := &RealRunner{log: common.Log, logCmdObj: c.LogCmdObj}
c.Cmd = &CmdObjBuilder{runner: runner, command: command, logCmdObj: c.LogCmdObj, platform: platform}
return c
}
@ -162,8 +165,7 @@ func (c *OSCommand) OpenFile(filename string) error {
"filename": c.Quote(filename),
}
command := utils.ResolvePlaceholderString(commandTemplate, templateValues)
err := c.Run(c.NewShellCmdObjFromString(command))
return err
return c.Cmd.NewShell(command).Run()
}
// OpenLink opens a file with the given
@ -175,14 +177,17 @@ func (c *OSCommand) OpenLink(link string) error {
}
command := utils.ResolvePlaceholderString(commandTemplate, templateValues)
err := c.Run(c.NewShellCmdObjFromString(command))
return err
return c.Cmd.NewShell(command).Run()
}
// Quote wraps a message in platform-specific quotation marks
func (c *OSCommand) Quote(message string) string {
return c.Cmd.Quote(message)
}
func (self *CmdObjBuilder) Quote(message string) string {
var quote string
if c.Platform.OS == "windows" {
if self.platform.OS == "windows" {
quote = `\"`
message = strings.NewReplacer(
`"`, `"'"'"`,
@ -289,7 +294,7 @@ func (c *OSCommand) PipeCommands(commandStrings ...string) error {
logCmdStr += " | "
}
logCmdStr += str
cmds[i] = c.NewCmdObj(str).GetCmd()
cmds[i] = c.Cmd.New(str).GetCmd()
}
c.LogCommand(logCmdStr, true)
@ -365,127 +370,6 @@ func (c *OSCommand) RemoveFile(path string) error {
return c.removeFile(path)
}
// builders
func (c *OSCommand) NewCmdObj(cmdStr string) ICmdObj {
args := str.ToArgv(cmdStr)
cmd := c.Command(args[0], args[1:]...)
cmd.Env = os.Environ()
return &CmdObj{
cmdStr: cmdStr,
cmd: cmd,
}
}
func (c *OSCommand) NewCmdObjFromArgs(args []string) ICmdObj {
cmd := c.Command(args[0], args[1:]...)
cmd.Env = os.Environ()
return &CmdObj{
cmdStr: strings.Join(args, " "),
cmd: cmd,
}
}
// NewShellCmdObjFromString takes a string like `git commit` and returns an executable shell command for it
func (c *OSCommand) NewShellCmdObjFromString(commandStr string) ICmdObj {
quotedCommand := ""
// Windows does not seem to like quotes around the command
if c.Platform.OS == "windows" {
quotedCommand = strings.NewReplacer(
"^", "^^",
"&", "^&",
"|", "^|",
"<", "^<",
">", "^>",
"%", "^%",
).Replace(commandStr)
} else {
quotedCommand = c.Quote(commandStr)
}
shellCommand := fmt.Sprintf("%s %s %s", c.Platform.Shell, c.Platform.ShellArg, quotedCommand)
return c.NewCmdObj(shellCommand)
}
// TODO: pick one of NewShellCmdObjFromString2 and ShellCommandFromString to use. I'm not sure
// which one actually is better, but I suspect it's NewShellCmdObjFromString2
func (c *OSCommand) NewShellCmdObjFromString2(command string) ICmdObj {
return c.NewCmdObjFromArgs([]string{c.Platform.Shell, c.Platform.ShellArg, command})
}
// runners
type IRunner interface {
Run(cmdObj ICmdObj) error
RunWithOutput(cmdObj ICmdObj) (string, error)
RunLineOutputCmd(cmdObj ICmdObj, onLine func(line string) (bool, error)) error
}
type RunExpectation func(ICmdObj) (string, error)
type RealRunner struct {
c *OSCommand
}
func (self *RealRunner) Run(cmdObj ICmdObj) error {
_, err := self.RunWithOutput(cmdObj)
return err
}
func (self *RealRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
self.c.LogCmdObj(cmdObj)
output, err := sanitisedCommandOutput(cmdObj.GetCmd().CombinedOutput())
if err != nil {
self.c.Log.WithField("command", cmdObj.ToString()).Error(output)
}
return output, err
}
func (self *RealRunner) RunLineOutputCmd(cmdObj ICmdObj, onLine func(line string) (bool, error)) error {
cmd := cmdObj.GetCmd()
stdoutPipe, err := cmd.StdoutPipe()
if err != nil {
return err
}
scanner := bufio.NewScanner(stdoutPipe)
scanner.Split(bufio.ScanLines)
if err := cmd.Start(); err != nil {
return err
}
for scanner.Scan() {
line := scanner.Text()
stop, err := onLine(line)
if err != nil {
return err
}
if stop {
_ = cmd.Process.Kill()
break
}
}
_ = cmd.Wait()
return nil
}
func sanitisedCommandOutput(output []byte, err error) (string, error) {
outputString := string(output)
if err != nil {
// errors like 'exit status 1' are not very useful so we'll create an error
// from the combined output
if outputString == "" {
return "", utils.WrapError(err)
}
return outputString, errors.New(outputString)
}
return outputString, nil
}
func GetTempDir() string {
return filepath.Join(os.TempDir(), "lazygit")
}

View File

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

View File

@ -85,12 +85,12 @@ func (c *GitCommand) MovePatchToSelectedCommit(commits []*models.Commit, sourceC
todo = a + " " + commit.Sha + " " + commit.Name + "\n" + todo
}
cmd, err := c.PrepareInteractiveRebaseCommand(commits[baseIndex].Sha, todo, true)
cmdObj, err := c.PrepareInteractiveRebaseCommand(commits[baseIndex].Sha, todo, true)
if err != nil {
return err
}
if err := c.OSCommand.Run(cmd); err != nil {
if err := cmdObj.Run(); err != nil {
return err
}
@ -217,7 +217,7 @@ func (c *GitCommand) PullPatchIntoNewCommit(commits []*models.Commit, commitIdx
head_message, _ := c.GetHeadCommitMessage()
new_message := fmt.Sprintf("Split from \"%s\"", head_message)
err := c.OSCommand.Run(c.CommitCmdObj(new_message, ""))
err := c.CommitCmdObj(new_message, "").Run()
if err != nil {
return err
}

View File

@ -33,12 +33,12 @@ func (c *GitCommand) MoveCommitDown(commits []*models.Commit, index int) error {
todo = "pick " + commit.Sha + " " + commit.Name + "\n" + todo
}
cmd, err := c.PrepareInteractiveRebaseCommand(commits[index+2].Sha, todo, true)
cmdObj, err := c.PrepareInteractiveRebaseCommand(commits[index+2].Sha, todo, true)
if err != nil {
return err
}
return c.OSCommand.Run(cmd)
return cmdObj.Run()
}
func (c *GitCommand) InteractiveRebase(commits []*models.Commit, index int, action string) error {
@ -47,12 +47,12 @@ func (c *GitCommand) InteractiveRebase(commits []*models.Commit, index int, acti
return err
}
cmd, err := c.PrepareInteractiveRebaseCommand(sha, todo, true)
cmdObj, err := c.PrepareInteractiveRebaseCommand(sha, todo, true)
if err != nil {
return err
}
return c.OSCommand.Run(cmd)
return cmdObj.Run()
}
// PrepareInteractiveRebaseCommand returns the cmd for an interactive rebase
@ -69,7 +69,7 @@ func (c *GitCommand) PrepareInteractiveRebaseCommand(baseSha string, todo string
cmdStr := fmt.Sprintf("git rebase --interactive --autostash --keep-empty %s", baseSha)
c.Log.WithField("command", cmdStr).Info("RunCommand")
cmdObj := c.NewCmdObj(cmdStr)
cmdObj := c.Cmd.New(cmdStr)
gitSequenceEditor := ex
if todo == "" {
@ -217,26 +217,22 @@ func (c *GitCommand) BeginInteractiveRebaseForCommit(commits []*models.Commit, c
return err
}
cmd, err := c.PrepareInteractiveRebaseCommand(sha, todo, true)
cmdObj, err := c.PrepareInteractiveRebaseCommand(sha, todo, true)
if err != nil {
return err
}
if err := c.OSCommand.Run(cmd); err != nil {
return err
}
return nil
return cmdObj.Run()
}
// RebaseBranch interactive rebases onto a branch
func (c *GitCommand) RebaseBranch(branchName string) error {
cmd, err := c.PrepareInteractiveRebaseCommand(branchName, "", false)
cmdObj, err := c.PrepareInteractiveRebaseCommand(branchName, "", false)
if err != nil {
return err
}
return c.OSCommand.Run(cmd)
return cmdObj.Run()
}
// GenericMerge takes a commandType of "merge" or "rebase" and a command of "abort", "skip" or "continue"
@ -271,13 +267,14 @@ func (c *GitCommand) GenericMergeOrRebaseAction(commandType string, command stri
}
func (c *GitCommand) runSkipEditorCommand(command string) error {
cmdObj := c.OSCommand.NewCmdObj(command)
cmdObj := c.OSCommand.Cmd.New(command)
lazyGitPath := c.OSCommand.GetLazygitPath()
cmdObj.AddEnvVars(
"LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY",
"GIT_EDITOR="+lazyGitPath,
"EDITOR="+lazyGitPath,
"VISUAL="+lazyGitPath,
)
return c.OSCommand.Run(cmdObj)
return cmdObj.
AddEnvVars(
"LAZYGIT_CLIENT_COMMAND=EXIT_IMMEDIATELY",
"GIT_EDITOR="+lazyGitPath,
"EDITOR="+lazyGitPath,
"VISUAL="+lazyGitPath,
).
Run()
}

View File

@ -7,24 +7,24 @@ import (
)
func (c *GitCommand) AddRemote(name string, url string) error {
return c.Run(c.NewCmdObj(fmt.Sprintf("git remote add %s %s", c.OSCommand.Quote(name), c.OSCommand.Quote(url))))
return c.Cmd.New(fmt.Sprintf("git remote add %s %s", c.OSCommand.Quote(name), c.OSCommand.Quote(url))).Run()
}
func (c *GitCommand) RemoveRemote(name string) error {
return c.Run(c.NewCmdObj(fmt.Sprintf("git remote remove %s", c.OSCommand.Quote(name))))
return c.Cmd.New(fmt.Sprintf("git remote remove %s", c.OSCommand.Quote(name))).Run()
}
func (c *GitCommand) RenameRemote(oldRemoteName string, newRemoteName string) error {
return c.Run(c.NewCmdObj(fmt.Sprintf("git remote rename %s %s", c.OSCommand.Quote(oldRemoteName), c.OSCommand.Quote(newRemoteName))))
return c.Cmd.New(fmt.Sprintf("git remote rename %s %s", c.OSCommand.Quote(oldRemoteName), c.OSCommand.Quote(newRemoteName))).Run()
}
func (c *GitCommand) UpdateRemoteUrl(remoteName string, updatedUrl string) error {
return c.Run(c.NewCmdObj(fmt.Sprintf("git remote set-url %s %s", c.OSCommand.Quote(remoteName), c.OSCommand.Quote(updatedUrl))))
return c.Cmd.New(fmt.Sprintf("git remote set-url %s %s", c.OSCommand.Quote(remoteName), c.OSCommand.Quote(updatedUrl))).Run()
}
func (c *GitCommand) DeleteRemoteBranch(remoteName string, branchName string, promptUserForCredential func(string) string) error {
command := fmt.Sprintf("git push %s --delete %s", c.OSCommand.Quote(remoteName), c.OSCommand.Quote(branchName))
cmdObj := c.NewCmdObj(command)
cmdObj := c.Cmd.New(command)
return c.DetectUnamePass(cmdObj, promptUserForCredential)
}
@ -34,10 +34,10 @@ func (c *GitCommand) DetectUnamePass(cmdObj oscommands.ICmdObj, promptUserForCre
// CheckRemoteBranchExists Returns remote branch
func (c *GitCommand) CheckRemoteBranchExists(branchName string) bool {
_, err := c.RunWithOutput(c.NewCmdObj(
_, err := c.Cmd.New(
fmt.Sprintf("git show-ref --verify -- refs/remotes/origin/%s",
c.OSCommand.Quote(branchName),
)))
)).RunWithOutput()
return err == nil
}

View File

@ -4,13 +4,13 @@ import "fmt"
// StashDo modify stash
func (c *GitCommand) StashDo(index int, method string) error {
return c.Run(c.NewCmdObj(fmt.Sprintf("git stash %s stash@{%d}", method, index)))
return c.Cmd.New(fmt.Sprintf("git stash %s stash@{%d}", method, index)).Run()
}
// StashSave save stash
// TODO: before calling this, check if there is anything to save
func (c *GitCommand) StashSave(message string) error {
return c.Run(c.NewCmdObj("git stash save " + c.OSCommand.Quote(message)))
return c.Cmd.New("git stash save " + c.OSCommand.Quote(message)).Run()
}
// GetStashEntryDiff stash diff
@ -22,7 +22,7 @@ func (c *GitCommand) ShowStashEntryCmdStr(index int) string {
// shoutouts to Joe on https://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible
func (c *GitCommand) StashSaveStagedChanges(message string) error {
// wrap in 'writing', which uses a mutex
if err := c.Run(c.NewCmdObj("git stash --keep-index")); err != nil {
if err := c.Cmd.New("git stash --keep-index").Run(); err != nil {
return err
}
@ -30,7 +30,7 @@ func (c *GitCommand) StashSaveStagedChanges(message string) error {
return err
}
if err := c.Run(c.NewCmdObj("git stash apply stash@{1}")); err != nil {
if err := c.Cmd.New("git stash apply stash@{1}").Run(); err != nil {
return err
}
@ -38,7 +38,7 @@ func (c *GitCommand) StashSaveStagedChanges(message string) error {
return err
}
if err := c.Run(c.NewCmdObj("git stash drop stash@{1}")); err != nil {
if err := c.Cmd.New("git stash drop stash@{1}").Run(); err != nil {
return err
}

View File

@ -71,28 +71,28 @@ func (c *GitCommand) SubmoduleStash(submodule *models.SubmoduleConfig) error {
return nil
}
return c.Run(c.NewCmdObj("git -C " + c.OSCommand.Quote(submodule.Path) + " stash --include-untracked"))
return c.Cmd.New("git -C " + c.OSCommand.Quote(submodule.Path) + " stash --include-untracked").Run()
}
func (c *GitCommand) SubmoduleReset(submodule *models.SubmoduleConfig) error {
return c.Run(c.NewCmdObj("git submodule update --init --force -- " + c.OSCommand.Quote(submodule.Path)))
return c.Cmd.New("git submodule update --init --force -- " + c.OSCommand.Quote(submodule.Path)).Run()
}
func (c *GitCommand) SubmoduleUpdateAll() error {
// not doing an --init here because the user probably doesn't want that
return c.Run(c.NewCmdObj("git submodule update --force"))
return c.Cmd.New("git submodule update --force").Run()
}
func (c *GitCommand) SubmoduleDelete(submodule *models.SubmoduleConfig) error {
// based on https://gist.github.com/myusuf3/7f645819ded92bda6677
if err := c.Run(c.NewCmdObj("git submodule deinit --force -- " + c.OSCommand.Quote(submodule.Path))); err != nil {
if err := c.Cmd.New("git submodule deinit --force -- " + c.OSCommand.Quote(submodule.Path)).Run(); err != nil {
if strings.Contains(err.Error(), "did not match any file(s) known to git") {
if err := c.Run(c.NewCmdObj("git config --file .gitmodules --remove-section submodule." + c.OSCommand.Quote(submodule.Name))); err != nil {
if err := c.Cmd.New("git config --file .gitmodules --remove-section submodule." + c.OSCommand.Quote(submodule.Name)).Run(); err != nil {
return err
}
if err := c.Run(c.NewCmdObj("git config --remove-section submodule." + c.OSCommand.Quote(submodule.Name))); err != nil {
if err := c.Cmd.New("git config --remove-section submodule." + c.OSCommand.Quote(submodule.Name)).Run(); err != nil {
return err
}
@ -102,7 +102,7 @@ func (c *GitCommand) SubmoduleDelete(submodule *models.SubmoduleConfig) error {
}
}
if err := c.Run(c.NewCmdObj("git rm --force -r " + submodule.Path)); err != nil {
if err := c.Cmd.New("git rm --force -r " + submodule.Path).Run(); err != nil {
// if the directory isn't there then that's fine
c.Log.Error(err)
}
@ -111,23 +111,24 @@ func (c *GitCommand) SubmoduleDelete(submodule *models.SubmoduleConfig) error {
}
func (c *GitCommand) SubmoduleAdd(name string, path string, url string) error {
return c.OSCommand.Run(
c.OSCommand.NewCmdObj(
return c.Cmd.
New(
fmt.Sprintf(
"git submodule add --force --name %s -- %s %s ",
c.OSCommand.Quote(name),
c.OSCommand.Quote(url),
c.OSCommand.Quote(path),
)))
)).
Run()
}
func (c *GitCommand) SubmoduleUpdateUrl(name string, path string, newUrl string) error {
// the set-url command is only for later git versions so we're doing it manually here
if err := c.Run(c.NewCmdObj("git config --file .gitmodules submodule." + c.OSCommand.Quote(name) + ".url " + c.OSCommand.Quote(newUrl))); err != nil {
if err := c.Cmd.New("git config --file .gitmodules submodule." + c.OSCommand.Quote(name) + ".url " + c.OSCommand.Quote(newUrl)).Run(); err != nil {
return err
}
if err := c.Run(c.NewCmdObj("git submodule sync -- " + c.OSCommand.Quote(path))); err != nil {
if err := c.Cmd.New("git submodule sync -- " + c.OSCommand.Quote(path)).Run(); err != nil {
return err
}
@ -135,27 +136,27 @@ func (c *GitCommand) SubmoduleUpdateUrl(name string, path string, newUrl string)
}
func (c *GitCommand) SubmoduleInit(path string) error {
return c.Run(c.NewCmdObj("git submodule init -- " + c.OSCommand.Quote(path)))
return c.Cmd.New("git submodule init -- " + c.OSCommand.Quote(path)).Run()
}
func (c *GitCommand) SubmoduleUpdate(path string) error {
return c.Run(c.NewCmdObj("git submodule update --init -- " + c.OSCommand.Quote(path)))
return c.Cmd.New("git submodule update --init -- " + c.OSCommand.Quote(path)).Run()
}
func (c *GitCommand) SubmoduleBulkInitCmdObj() oscommands.ICmdObj {
return c.NewCmdObj("git submodule init")
return c.Cmd.New("git submodule init")
}
func (c *GitCommand) SubmoduleBulkUpdateCmdObj() oscommands.ICmdObj {
return c.NewCmdObj("git submodule update")
return c.Cmd.New("git submodule update")
}
func (c *GitCommand) SubmoduleForceBulkUpdateCmdObj() oscommands.ICmdObj {
return c.NewCmdObj("git submodule update --force")
return c.Cmd.New("git submodule update --force")
}
func (c *GitCommand) SubmoduleBulkDeinitCmdObj() oscommands.ICmdObj {
return c.NewCmdObj("git submodule deinit --all --force")
return c.Cmd.New("git submodule deinit --all --force")
}
func (c *GitCommand) ResetSubmodules(submodules []*models.SubmoduleConfig) error {

View File

@ -37,7 +37,7 @@ func (c *GitCommand) Push(opts PushOpts) error {
cmdStr += " " + c.OSCommand.Quote(opts.UpstreamBranch)
}
cmdObj := c.NewCmdObj(cmdStr)
cmdObj := c.Cmd.New(cmdStr)
return c.DetectUnamePass(cmdObj, opts.PromptUserForCredential)
}
@ -58,7 +58,7 @@ func (c *GitCommand) Fetch(opts FetchOptions) error {
cmdStr = fmt.Sprintf("%s %s", cmdStr, c.OSCommand.Quote(opts.BranchName))
}
cmdObj := c.NewCmdObj(cmdStr)
cmdObj := c.Cmd.New(cmdStr)
return c.DetectUnamePass(cmdObj, func(question string) string {
if opts.PromptUserForCredential != nil {
return opts.PromptUserForCredential(question)
@ -94,18 +94,18 @@ func (c *GitCommand) Pull(opts PullOptions) error {
// setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user
// has 'pull.rebase = interactive' configured.
cmdObj := c.NewCmdObj(cmdStr).AddEnvVars("GIT_SEQUENCE_EDITOR=:")
cmdObj := c.Cmd.New(cmdStr).AddEnvVars("GIT_SEQUENCE_EDITOR=:")
return c.DetectUnamePass(cmdObj, opts.PromptUserForCredential)
}
func (c *GitCommand) FastForward(branchName string, remoteName string, remoteBranchName string, promptUserForCredential func(string) string) error {
cmdStr := fmt.Sprintf("git fetch %s %s:%s", c.OSCommand.Quote(remoteName), c.OSCommand.Quote(remoteBranchName), c.OSCommand.Quote(branchName))
cmdObj := c.NewCmdObj(cmdStr)
cmdObj := c.Cmd.New(cmdStr)
return c.DetectUnamePass(cmdObj, promptUserForCredential)
}
func (c *GitCommand) FetchRemote(remoteName string, promptUserForCredential func(string) string) error {
cmdStr := fmt.Sprintf("git fetch %s", c.OSCommand.Quote(remoteName))
cmdObj := c.NewCmdObj(cmdStr)
cmdObj := c.Cmd.New(cmdStr)
return c.DetectUnamePass(cmdObj, promptUserForCredential)
}

View File

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

View File

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

View File

@ -203,7 +203,7 @@ func (gui *Gui) menuPromptFromCommand(prompt config.CustomCommandPrompt, promptR
}
// Run and save output
message, err := gui.GitCommand.RunWithOutput(gui.GitCommand.NewCmdObj(cmdStr))
message, err := gui.GitCommand.Cmd.New(cmdStr).RunWithOutput()
if err != nil {
return gui.surfaceError(err)
}
@ -244,7 +244,7 @@ func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand
}
if customCommand.Subprocess {
return gui.runSubprocessWithSuspenseAndRefresh(gui.OSCommand.NewShellCmdObjFromString2(cmdStr))
return gui.runSubprocessWithSuspenseAndRefresh(gui.OSCommand.Cmd.NewShell(cmdStr))
}
loadingText := customCommand.LoadingText
@ -252,8 +252,8 @@ func (gui *Gui) handleCustomCommandKeybinding(customCommand config.CustomCommand
loadingText = gui.Tr.LcRunningCustomCommandStatus
}
return gui.WithWaitingStatus(loadingText, func() error {
cmdObj := gui.OSCommand.NewShellCmdObjFromString(cmdStr)
if err := gui.OSCommand.WithSpan(gui.Tr.Spans.CustomCommand).Run(cmdObj); err != nil {
err := gui.OSCommand.WithSpan(gui.Tr.Spans.CustomCommand).Cmd.NewShell(cmdStr).Run()
if err != nil {
return gui.surfaceError(err)
}
return gui.refreshSidePanels(refreshOptions{})

View File

@ -13,7 +13,7 @@ func (gui *Gui) exitDiffMode() error {
}
func (gui *Gui) renderDiff() error {
cmdObj := gui.OSCommand.NewCmdObj(
cmdObj := gui.OSCommand.Cmd.New(
fmt.Sprintf("git diff --submodule --no-ext-diff --color %s", gui.diffStr()),
)
task := NewRunPtyTask(cmdObj.GetCmd())

View File

@ -465,7 +465,7 @@ func (gui *Gui) handleCommitEditorPress() error {
cmdStr := "git " + strings.Join(args, " ")
return gui.runSubprocessWithSuspenseAndRefresh(
gui.GitCommand.WithSpan(gui.Tr.Spans.Commit).NewCmdObjWithLog(cmdStr),
gui.GitCommand.WithSpan(gui.Tr.Spans.Commit).Cmd.New(cmdStr).Log(),
)
}
@ -511,7 +511,7 @@ func (gui *Gui) editFileAtLine(filename string, lineNumber int) error {
}
return gui.runSubprocessWithSuspenseAndRefresh(
gui.OSCommand.WithSpan(gui.Tr.Spans.EditFile).NewShellCmdObjFromString(cmdStr),
gui.OSCommand.WithSpan(gui.Tr.Spans.EditFile).Cmd.NewShell(cmdStr),
)
}
@ -923,7 +923,7 @@ func (gui *Gui) handleCustomCommand() error {
gui.OnRunCommand(oscommands.NewCmdLogEntry(command, gui.Tr.Spans.CustomCommand, true))
return gui.runSubprocessWithSuspenseAndRefresh(
gui.OSCommand.NewShellCmdObjFromString2(command),
gui.OSCommand.Cmd.NewShell(command),
)
},
})

View File

@ -32,7 +32,7 @@ func (gui *Gui) gitFlowFinishBranch(gitFlowConfig string, branchName string) err
}
return gui.runSubprocessWithSuspenseAndRefresh(
gui.GitCommand.WithSpan(gui.Tr.Spans.GitFlowFinish).NewCmdObjWithLog("git flow " + branchType + " finish " + suffix),
gui.GitCommand.WithSpan(gui.Tr.Spans.GitFlowFinish).Cmd.New("git flow " + branchType + " finish " + suffix).Log(),
)
}
@ -43,7 +43,7 @@ func (gui *Gui) handleCreateGitFlowMenu() error {
}
// get config
gitFlowConfig, err := gui.GitCommand.RunWithOutput(gui.GitCommand.NewCmdObj("git config --local --get-regexp gitflow"))
gitFlowConfig, err := gui.GitCommand.Cmd.New("git config --local --get-regexp gitflow").RunWithOutput()
if err != nil {
return gui.createErrorPanel("You need to install git-flow and enable it in this repo to use git-flow features")
}
@ -56,7 +56,7 @@ func (gui *Gui) handleCreateGitFlowMenu() error {
title: title,
handleConfirm: func(name string) error {
return gui.runSubprocessWithSuspenseAndRefresh(
gui.GitCommand.WithSpan(gui.Tr.Spans.GitFlowStart).NewCmdObjWithLog("git flow " + branchType + " start " + name),
gui.GitCommand.WithSpan(gui.Tr.Spans.GitFlowStart).Cmd.New("git flow " + branchType + " start " + name).Log(),
)
},
})

View File

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

View File

@ -58,7 +58,7 @@ func (gui *Gui) genericMergeCommand(command string) error {
// it's impossible for a rebase to require a commit so we'll use a subprocess only if it's a merge
if status == commands.REBASE_MODE_MERGING && command != REBASE_OPTION_ABORT && gui.UserConfig.Git.Merging.ManualCommit {
sub := gitCommand.NewCmdObj("git " + commandType + " --" + command)
sub := gitCommand.Cmd.New("git " + commandType + " --" + command)
if sub != nil {
return gui.runSubprocessWithSuspenseAndRefresh(sub)
}

View File

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

View File

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

View File

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

View File

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

View File

@ -45,7 +45,7 @@ func RunTests(
testDir := filepath.Join(rootDir, "test", "integration")
osCommand := oscommands.NewDummyOSCommand()
err = osCommand.Run(osCommand.NewCmdObj("go build -o " + tempLazygitPath()))
err = osCommand.Cmd.New("go build -o " + tempLazygitPath()).Run()
if err != nil {
return err
}
@ -319,7 +319,7 @@ func generateSnapshot(dir string) (string, error) {
for _, cmdStr := range cmdStrs {
// ignoring error for now. If there's an error it could be that there are no results
output, _ := osCommand.RunWithOutput(osCommand.NewCmdObj(cmdStr))
output, _ := osCommand.Cmd.New(cmdStr).RunWithOutput()
snapshot += output + "\n"
}
@ -428,7 +428,7 @@ func getLazygitCommand(testPath string, rootDir string, record bool, speed float
cmdStr := fmt.Sprintf("%s -debug --use-config-dir=%s --path=%s %s", tempLazygitPath(), configDir, actualDir, extraCmdArgs)
cmdObj := osCommand.NewCmdObj(cmdStr)
cmdObj := osCommand.Cmd.New(cmdStr)
cmdObj.AddEnvVars(fmt.Sprintf("SPEED=%f", speed))
if record {

View File

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