1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-05-15 22:26:40 +02:00

start breaking up git struct

This commit is contained in:
Jesse Duffield 2022-01-02 10:34:33 +11:00
parent 4a1d23dc27
commit f503ff1ecb
76 changed files with 2234 additions and 1758 deletions

View File

@ -32,7 +32,6 @@ type App struct {
closers []io.Closer
Config config.AppConfigurer
OSCommand *oscommands.OSCommand
GitCommand *commands.GitCommand
Gui *gui.Gui
Updater *updates.Updater // may only need this on the Gui
ClientContext string
@ -122,7 +121,7 @@ func NewApp(config config.AppConfigurer, filterPath string) (*App, error) {
return app, nil
}
app.OSCommand = oscommands.NewOSCommand(app.Common, oscommands.GetPlatform())
app.OSCommand = oscommands.NewOSCommand(app.Common, oscommands.GetPlatform(), oscommands.NewNullGuiIO(log))
app.Updater, err = updates.NewUpdater(app.Common, config, app.OSCommand)
if err != nil {
@ -134,16 +133,9 @@ func NewApp(config config.AppConfigurer, filterPath string) (*App, error) {
return app, err
}
app.GitCommand, err = commands.NewGitCommand(
app.Common,
app.OSCommand,
git_config.NewStdCachedGitConfig(app.Log),
)
if err != nil {
return app, err
}
gitConfig := git_config.NewStdCachedGitConfig(app.Log)
app.Gui, err = gui.NewGui(app.Common, app.GitCommand, app.OSCommand, config, app.Updater, filterPath, showRecentRepos)
app.Gui, err = gui.NewGui(app.Common, config, gitConfig, app.Updater, filterPath, showRecentRepos)
if err != nil {
return app, err
}

View File

@ -6,24 +6,47 @@ import (
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils"
)
// NewBranch create new branch
func (c *GitCommand) NewBranch(name string, base string) error {
return c.Cmd.New(fmt.Sprintf("git checkout -b %s %s", c.OSCommand.Quote(name), c.OSCommand.Quote(base))).Run()
// this takes something like:
// * (HEAD detached at 264fc6f5)
// remotes
// and returns '264fc6f5' as the second match
const CurrentBranchNameRegex = `(?m)^\*.*?([^ ]*?)\)?$`
type BranchCommands struct {
*common.Common
cmd oscommands.ICmdObjBuilder
}
func NewBranchCommands(
common *common.Common,
cmd oscommands.ICmdObjBuilder,
) *BranchCommands {
return &BranchCommands{
Common: common,
cmd: cmd,
}
}
// 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()
}
// 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.Cmd.New("git symbolic-ref --short HEAD").DontLog().RunWithOutput()
func (self *BranchCommands) CurrentBranchName() (string, string, error) {
branchName, err := self.cmd.New("git symbolic-ref --short HEAD").DontLog().RunWithOutput()
if err == nil && branchName != "HEAD\n" {
trimmedBranchName := strings.TrimSpace(branchName)
return trimmedBranchName, trimmedBranchName, nil
}
output, err := c.Cmd.New("git branch --contains").DontLog().RunWithOutput()
output, err := self.cmd.New("git branch --contains").DontLog().RunWithOutput()
if err != nil {
return "", "", err
}
@ -39,15 +62,15 @@ func (c *GitCommand) CurrentBranchName() (string, string, error) {
return "HEAD", "HEAD", nil
}
// DeleteBranch delete branch
func (c *GitCommand) DeleteBranch(branch string, force bool) error {
// Delete delete branch
func (self *BranchCommands) Delete(branch string, force bool) error {
command := "git branch -d"
if force {
command = "git branch -D"
}
return c.Cmd.New(fmt.Sprintf("%s %s", command, c.OSCommand.Quote(branch))).Run()
return self.cmd.New(fmt.Sprintf("%s %s", command, self.cmd.Quote(branch))).Run()
}
// Checkout checks out a branch (or commit), with --force if you set the force arg to true
@ -56,13 +79,13 @@ type CheckoutOptions struct {
EnvVars []string
}
func (c *GitCommand) Checkout(branch string, options CheckoutOptions) error {
func (self *BranchCommands) Checkout(branch string, options CheckoutOptions) error {
forceArg := ""
if options.Force {
forceArg = " --force"
}
return c.Cmd.New(fmt.Sprintf("git checkout%s %s", forceArg, c.OSCommand.Quote(branch))).
return self.cmd.New(fmt.Sprintf("git checkout%s %s", forceArg, self.cmd.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").
@ -70,104 +93,84 @@ func (c *GitCommand) Checkout(branch string, options CheckoutOptions) error {
Run()
}
// GetBranchGraph gets the color-formatted graph of the log for the given branch
// GetGraph 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.GetBranchGraphCmdObj(branchName).DontLog().RunWithOutput()
func (self *BranchCommands) GetGraph(branchName string) (string, error) {
return self.GetGraphCmdObj(branchName).DontLog().RunWithOutput()
}
func (c *GitCommand) GetUpstreamForBranch(branchName string) (string, error) {
output, err := c.Cmd.New(fmt.Sprintf("git rev-parse --abbrev-ref --symbolic-full-name %s@{u}", c.OSCommand.Quote(branchName))).DontLog().RunWithOutput()
func (self *BranchCommands) GetGraphCmdObj(branchName string) oscommands.ICmdObj {
branchLogCmdTemplate := self.UserConfig.Git.BranchLogCmd
templateValues := map[string]string{
"branchName": self.cmd.Quote(branchName),
}
return self.cmd.New(utils.ResolvePlaceholderString(branchLogCmdTemplate, templateValues)).DontLog()
}
func (self *BranchCommands) SetCurrentBranchUpstream(upstream string) error {
return self.cmd.New("git branch --set-upstream-to=" + self.cmd.Quote(upstream)).Run()
}
func (self *BranchCommands) GetUpstream(branchName string) (string, error) {
output, err := self.cmd.New(fmt.Sprintf("git rev-parse --abbrev-ref --symbolic-full-name %s@{u}", self.cmd.Quote(branchName))).DontLog().RunWithOutput()
return strings.TrimSpace(output), err
}
func (c *GitCommand) GetBranchGraphCmdObj(branchName string) oscommands.ICmdObj {
branchLogCmdTemplate := c.UserConfig.Git.BranchLogCmd
templateValues := map[string]string{
"branchName": c.OSCommand.Quote(branchName),
}
return c.Cmd.New(utils.ResolvePlaceholderString(branchLogCmdTemplate, templateValues)).DontLog()
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()
}
func (c *GitCommand) SetUpstreamBranch(upstream string) error {
return c.Cmd.New("git branch -u " + c.OSCommand.Quote(upstream)).Run()
func (self *BranchCommands) GetCurrentBranchUpstreamDifferenceCount() (string, string) {
return self.GetCommitDifferences("HEAD", "HEAD@{u}")
}
func (c *GitCommand) SetBranchUpstream(remoteName string, remoteBranchName string, branchName string) error {
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) {
return c.GetCommitDifferences("HEAD", "HEAD@{u}")
}
func (c *GitCommand) GetBranchUpstreamDifferenceCount(branchName string) (string, string) {
return c.GetCommitDifferences(branchName, branchName+"@{u}")
func (self *BranchCommands) GetUpstreamDifferenceCount(branchName string) (string, string) {
return self.GetCommitDifferences(branchName, branchName+"@{u}")
}
// GetCommitDifferences checks how many pushables/pullables there are for the
// current branch
func (c *GitCommand) GetCommitDifferences(from, to string) (string, string) {
func (self *BranchCommands) GetCommitDifferences(from, to string) (string, string) {
command := "git rev-list %s..%s --count"
pushableCount, err := c.Cmd.New(fmt.Sprintf(command, to, from)).DontLog().RunWithOutput()
pushableCount, err := self.cmd.New(fmt.Sprintf(command, to, from)).DontLog().RunWithOutput()
if err != nil {
return "?", "?"
}
pullableCount, err := c.Cmd.New(fmt.Sprintf(command, from, to)).DontLog().RunWithOutput()
pullableCount, err := self.cmd.New(fmt.Sprintf(command, from, to)).DontLog().RunWithOutput()
if err != nil {
return "?", "?"
}
return strings.TrimSpace(pushableCount), strings.TrimSpace(pullableCount)
}
func (self *BranchCommands) IsHeadDetached() bool {
err := self.cmd.New("git symbolic-ref -q HEAD").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()
}
func (self *BranchCommands) GetRawBranches() (string, error) {
return self.cmd.New(`git for-each-ref --sort=-committerdate --format="%(HEAD)|%(refname:short)|%(upstream:short)|%(upstream:track)" refs/heads`).DontLog().RunWithOutput()
}
type MergeOpts struct {
FastForwardOnly bool
}
// Merge merge
func (c *GitCommand) Merge(branchName string, opts MergeOpts) error {
func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error {
mergeArg := ""
if c.UserConfig.Git.Merging.Args != "" {
mergeArg = " " + c.UserConfig.Git.Merging.Args
if self.UserConfig.Git.Merging.Args != "" {
mergeArg = " " + self.UserConfig.Git.Merging.Args
}
command := fmt.Sprintf("git merge --no-edit%s %s", mergeArg, c.OSCommand.Quote(branchName))
command := fmt.Sprintf("git merge --no-edit%s %s", mergeArg, self.cmd.Quote(branchName))
if opts.FastForwardOnly {
command = fmt.Sprintf("%s --ff-only", command)
}
return c.OSCommand.Cmd.New(command).Run()
}
// AbortMerge abort merge
func (c *GitCommand) AbortMerge() error {
return c.Cmd.New("git merge --abort").Run()
}
func (c *GitCommand) IsHeadDetached() bool {
err := c.Cmd.New("git symbolic-ref -q HEAD").DontLog().Run()
return err != nil
}
// ResetHardHead runs `git reset --hard`
func (c *GitCommand) ResetHard(ref string) error {
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.Cmd.New("git reset --soft " + c.OSCommand.Quote(ref)).Run()
}
func (c *GitCommand) ResetMixed(ref string) error {
return c.Cmd.New("git reset --mixed " + c.OSCommand.Quote(ref)).Run()
}
func (c *GitCommand) RenameBranch(oldName string, newName string) error {
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.Cmd.New(`git for-each-ref --sort=-committerdate --format="%(HEAD)|%(refname:short)|%(upstream:short)|%(upstream:track)" refs/heads`).DontLog().RunWithOutput()
return self.cmd.New(command).Run()
}

View File

@ -42,7 +42,7 @@ func TestGitCommandGetCommitDifferences(t *testing.T) {
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
pushables, pullables := gitCmd.GetCommitDifferences("HEAD", "@{u}")
pushables, pullables := gitCmd.Branch.GetCommitDifferences("HEAD", "@{u}")
assert.EqualValues(t, s.expectedPushables, pushables)
assert.EqualValues(t, s.expectedPullables, pullables)
s.runner.CheckForMissingCalls()
@ -55,7 +55,7 @@ func TestGitCommandNewBranch(t *testing.T) {
Expect(`git checkout -b "test" "master"`, "", nil)
gitCmd := NewDummyGitCommandWithRunner(runner)
assert.NoError(t, gitCmd.NewBranch("test", "master"))
assert.NoError(t, gitCmd.Branch.New("test", "master"))
runner.CheckForMissingCalls()
}
@ -90,7 +90,7 @@ func TestGitCommandDeleteBranch(t *testing.T) {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
s.test(gitCmd.DeleteBranch("test", s.force))
s.test(gitCmd.Branch.Delete("test", s.force))
s.runner.CheckForMissingCalls()
})
}
@ -101,7 +101,7 @@ func TestGitCommandMerge(t *testing.T) {
Expect(`git merge --no-edit "test"`, "", nil)
gitCmd := NewDummyGitCommandWithRunner(runner)
assert.NoError(t, gitCmd.Merge("test", MergeOpts{}))
assert.NoError(t, gitCmd.Branch.Merge("test", MergeOpts{}))
runner.CheckForMissingCalls()
}
@ -135,7 +135,7 @@ func TestGitCommandCheckout(t *testing.T) {
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
s.test(gitCmd.Checkout("test", CheckoutOptions{Force: s.force}))
s.test(gitCmd.Branch.Checkout("test", CheckoutOptions{Force: s.force}))
s.runner.CheckForMissingCalls()
})
}
@ -146,7 +146,7 @@ func TestGitCommandGetBranchGraph(t *testing.T) {
"log", "--graph", "--color=always", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium", "test", "--",
}, "", nil)
gitCmd := NewDummyGitCommandWithRunner(runner)
_, err := gitCmd.GetBranchGraph("test")
_, err := gitCmd.Branch.GetGraph("test")
assert.NoError(t, err)
}
@ -215,36 +215,8 @@ func TestGitCommandCurrentBranchName(t *testing.T) {
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
s.test(gitCmd.CurrentBranchName())
s.test(gitCmd.Branch.CurrentBranchName())
s.runner.CheckForMissingCalls()
})
}
}
func TestGitCommandResetHard(t *testing.T) {
type scenario struct {
testName string
ref string
runner *oscommands.FakeCmdObjRunner
test func(error)
}
scenarios := []scenario{
{
"valid case",
"HEAD",
oscommands.NewFakeRunner(t).
Expect(`git reset --hard "HEAD"`, "", nil),
func(err error) {
assert.NoError(t, err)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
s.test(gitCmd.ResetHard(s.ref))
})
}
}

View File

@ -4,18 +4,34 @@ import (
"fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
)
// RenameCommit renames the topmost commit with the given name
func (c *GitCommand) RenameCommit(name string) error {
return c.Cmd.New("git commit --allow-empty --amend --only -m " + c.OSCommand.Quote(name)).Run()
type CommitCommands struct {
*common.Common
cmd oscommands.ICmdObjBuilder
}
func NewCommitCommands(
common *common.Common,
cmd oscommands.ICmdObjBuilder,
) *CommitCommands {
return &CommitCommands{
Common: common,
cmd: cmd,
}
}
// RewordLastCommit renames the topmost commit with the given name
func (self *CommitCommands) RewordLastCommit(name string) error {
return self.cmd.New("git commit --allow-empty --amend --only -m " + self.cmd.Quote(name)).Run()
}
// ResetToCommit reset to commit
func (c *GitCommand) ResetToCommit(sha string, strength string, envVars []string) error {
return c.Cmd.New(fmt.Sprintf("git reset --%s %s", strength, sha)).
func (self *CommitCommands) ResetToCommit(sha string, strength string, envVars []string) error {
return self.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").
@ -23,11 +39,11 @@ func (c *GitCommand) ResetToCommit(sha string, strength string, envVars []string
Run()
}
func (c *GitCommand) CommitCmdObj(message string, flags string) oscommands.ICmdObj {
func (self *CommitCommands) CommitCmdObj(message string, flags string) oscommands.ICmdObj {
splitMessage := strings.Split(message, "\n")
lineArgs := ""
for _, line := range splitMessage {
lineArgs += fmt.Sprintf(" -m %s", c.OSCommand.Quote(line))
lineArgs += fmt.Sprintf(" -m %s", self.cmd.Quote(line))
}
flagsStr := ""
@ -35,71 +51,56 @@ func (c *GitCommand) CommitCmdObj(message string, flags string) oscommands.ICmdO
flagsStr = fmt.Sprintf(" %s", flags)
}
return c.Cmd.New(fmt.Sprintf("git commit%s%s", flagsStr, lineArgs))
return self.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.Cmd.New("git log -1 --pretty=%s").DontLog().RunWithOutput()
func (self *CommitCommands) GetHeadCommitMessage() (string, error) {
message, err := self.cmd.New("git log -1 --pretty=%s").DontLog().RunWithOutput()
return strings.TrimSpace(message), err
}
func (c *GitCommand) GetCommitMessage(commitSha string) (string, error) {
func (self *CommitCommands) GetCommitMessage(commitSha string) (string, error) {
cmdStr := "git rev-list --format=%B --max-count=1 " + commitSha
messageWithHeader, err := c.Cmd.New(cmdStr).DontLog().RunWithOutput()
messageWithHeader, err := self.cmd.New(cmdStr).DontLog().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.Cmd.New(fmt.Sprintf("git show --no-patch --pretty=format:%%s %s", sha)).DontLog().RunWithOutput()
func (self *CommitCommands) GetCommitMessageFirstLine(sha string) (string, error) {
return self.cmd.New(fmt.Sprintf("git show --no-patch --pretty=format:%%s %s", sha)).DontLog().RunWithOutput()
}
// AmendHead amends HEAD with whatever is staged in your working tree
func (c *GitCommand) AmendHead() error {
return c.AmendHeadCmdObj().Run()
func (self *CommitCommands) AmendHead() error {
return self.AmendHeadCmdObj().Run()
}
func (c *GitCommand) AmendHeadCmdObj() oscommands.ICmdObj {
return c.Cmd.New("git commit --amend --no-edit --allow-empty")
func (self *CommitCommands) AmendHeadCmdObj() oscommands.ICmdObj {
return self.cmd.New("git commit --amend --no-edit --allow-empty")
}
func (c *GitCommand) ShowCmdObj(sha string, filterPath string) oscommands.ICmdObj {
contextSize := c.UserConfig.Git.DiffContextSize
func (self *CommitCommands) ShowCmdObj(sha string, filterPath string) oscommands.ICmdObj {
contextSize := self.UserConfig.Git.DiffContextSize
filterPathArg := ""
if filterPath != "" {
filterPathArg = fmt.Sprintf(" -- %s", c.OSCommand.Quote(filterPath))
filterPathArg = fmt.Sprintf(" -- %s", self.cmd.Quote(filterPath))
}
cmdStr := fmt.Sprintf("git show --submodule --color=%s --unified=%d --no-renames --stat -p %s %s", c.colorArg(), contextSize, sha, filterPathArg)
return c.Cmd.New(cmdStr).DontLog()
cmdStr := fmt.Sprintf("git show --submodule --color=%s --unified=%d --no-renames --stat -p %s %s", self.UserConfig.Git.Paging.ColorArg, contextSize, sha, filterPathArg)
return self.cmd.New(cmdStr).DontLog()
}
// Revert reverts the selected commit by sha
func (c *GitCommand) Revert(sha string) error {
return c.Cmd.New(fmt.Sprintf("git revert %s", sha)).Run()
func (self *CommitCommands) Revert(sha string) error {
return self.cmd.New(fmt.Sprintf("git revert %s", sha)).Run()
}
func (c *GitCommand) RevertMerge(sha string, parentNumber int) error {
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
func (c *GitCommand) CherryPickCommits(commits []*models.Commit) error {
todo := ""
for _, commit := range commits {
todo = "pick " + commit.Sha + " " + commit.Name + "\n" + todo
}
cmdObj, err := c.PrepareInteractiveRebaseCommand("HEAD", todo, false)
if err != nil {
return err
}
return cmdObj.Run()
func (self *CommitCommands) RevertMerge(sha string, parentNumber int) error {
return self.cmd.New(fmt.Sprintf("git revert %s -m %d", sha, parentNumber)).Run()
}
// CreateFixupCommit creates a commit that fixes up a previous commit
func (c *GitCommand) CreateFixupCommit(sha string) error {
return c.Cmd.New(fmt.Sprintf("git commit --fixup=%s", sha)).Run()
func (self *CommitCommands) CreateFixupCommit(sha string) error {
return self.cmd.New(fmt.Sprintf("git commit --fixup=%s", sha)).Run()
}

View File

@ -7,12 +7,12 @@ import (
"github.com/stretchr/testify/assert"
)
func TestGitCommandRenameCommit(t *testing.T) {
func TestGitCommandRewordCommit(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"commit", "--allow-empty", "--amend", "--only", "-m", "test"}, "", nil)
gitCmd := NewDummyGitCommandWithRunner(runner)
assert.NoError(t, gitCmd.RenameCommit("test"))
assert.NoError(t, gitCmd.Commit.RewordLastCommit("test"))
runner.CheckForMissingCalls()
}
@ -21,7 +21,7 @@ func TestGitCommandResetToCommit(t *testing.T) {
ExpectGitArgs([]string{"reset", "--hard", "78976bc"}, "", nil)
gitCmd := NewDummyGitCommandWithRunner(runner)
assert.NoError(t, gitCmd.ResetToCommit("78976bc", "hard", []string{}))
assert.NoError(t, gitCmd.Commit.ResetToCommit("78976bc", "hard", []string{}))
runner.CheckForMissingCalls()
}
@ -57,7 +57,7 @@ func TestGitCommandCommitObj(t *testing.T) {
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommand()
cmdStr := gitCmd.CommitCmdObj(s.message, s.flags).ToString()
cmdStr := gitCmd.Commit.CommitCmdObj(s.message, s.flags).ToString()
assert.Equal(t, s.expected, cmdStr)
})
}
@ -86,7 +86,7 @@ func TestGitCommandCreateFixupCommit(t *testing.T) {
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
s.test(gitCmd.CreateFixupCommit(s.sha))
s.test(gitCmd.Commit.CreateFixupCommit(s.sha))
s.runner.CheckForMissingCalls()
})
}
@ -125,7 +125,7 @@ func TestGitCommandShowCmdObj(t *testing.T) {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommand()
gitCmd.UserConfig.Git.DiffContextSize = s.contextSize
cmdStr := gitCmd.ShowCmdObj("1234567890", s.filterPath).ToString()
cmdStr := gitCmd.Commit.ShowCmdObj("1234567890", s.filterPath).ToString()
assert.Equal(t, s.expected, cmdStr)
})
}

View File

@ -5,24 +5,42 @@ import (
"strconv"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils"
)
func (c *GitCommand) ConfiguredPager() string {
type ConfigCommands struct {
*common.Common
gitConfig git_config.IGitConfig
}
func NewConfigCommands(
common *common.Common,
gitConfig git_config.IGitConfig,
) *ConfigCommands {
return &ConfigCommands{
Common: common,
gitConfig: gitConfig,
}
}
func (self *ConfigCommands) ConfiguredPager() string {
if os.Getenv("GIT_PAGER") != "" {
return os.Getenv("GIT_PAGER")
}
if os.Getenv("PAGER") != "" {
return os.Getenv("PAGER")
}
output := c.GitConfig.Get("core.pager")
output := self.gitConfig.Get("core.pager")
return strings.Split(output, "\n")[0]
}
func (c *GitCommand) GetPager(width int) string {
useConfig := c.UserConfig.Git.Paging.UseConfig
func (self *ConfigCommands) GetPager(width int) string {
useConfig := self.UserConfig.Git.Paging.UseConfig
if useConfig {
pager := c.ConfiguredPager()
pager := self.ConfiguredPager()
return strings.Split(pager, "| less")[0]
}
@ -30,21 +48,35 @@ func (c *GitCommand) GetPager(width int) string {
"columnWidth": strconv.Itoa(width/2 - 6),
}
pagerTemplate := c.UserConfig.Git.Paging.Pager
pagerTemplate := self.UserConfig.Git.Paging.Pager
return utils.ResolvePlaceholderString(pagerTemplate, templateValues)
}
func (c *GitCommand) colorArg() string {
return c.UserConfig.Git.Paging.ColorArg
}
// UsingGpg tells us whether the user has gpg enabled so that we can know
// whether we need to run a subprocess to allow them to enter their password
func (c *GitCommand) UsingGpg() bool {
overrideGpg := c.UserConfig.Git.OverrideGpg
func (self *ConfigCommands) UsingGpg() bool {
overrideGpg := self.UserConfig.Git.OverrideGpg
if overrideGpg {
return false
}
return c.GitConfig.GetBool("commit.gpgsign")
return self.gitConfig.GetBool("commit.gpgsign")
}
func (self *ConfigCommands) GetCoreEditor() string {
return self.gitConfig.Get("core.editor")
}
// GetRemoteURL returns current repo remote url
func (self *ConfigCommands) GetRemoteURL() string {
return self.gitConfig.Get("remote.origin.url")
}
func (self *ConfigCommands) GetShowUntrackedFiles() string {
return self.gitConfig.Get("status.showUntrackedFiles")
}
// this determines whether the user has configured to push to the remote branch of the same name as the current or not
func (self *ConfigCommands) GetPushToCurrent() bool {
return self.gitConfig.Get("push.default") == "current"
}

View File

@ -1,10 +1,6 @@
package commands
import (
"io"
"io/ioutil"
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@ -16,16 +12,13 @@ func NewDummyGitCommand() *GitCommand {
// NewDummyGitCommandWithOSCommand creates a new dummy GitCommand for testing
func NewDummyGitCommandWithOSCommand(osCommand *oscommands.OSCommand) *GitCommand {
runner := &oscommands.FakeCmdObjRunner{}
builder := oscommands.NewDummyCmdObjBuilder(runner)
return &GitCommand{
Common: utils.NewDummyCommon(),
Cmd: builder,
OSCommand: osCommand,
GitConfig: git_config.NewFakeGitConfig(map[string]string{}),
GetCmdWriter: func() io.Writer { return ioutil.Discard },
}
return NewGitCommandAux(
utils.NewDummyCommon(),
osCommand,
utils.NewDummyGitConfig(),
".git",
nil,
)
}
func NewDummyGitCommandWithRunner(runner oscommands.ICmdObjRunner) *GitCommand {

View File

@ -1,23 +1,43 @@
package commands
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"time"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils"
)
// CatFile obtains the content of a file
func (c *GitCommand) CatFile(fileName string) (string, error) {
type FileCommands struct {
*common.Common
cmd oscommands.ICmdObjBuilder
config *ConfigCommands
os FileOSCommand
}
type FileOSCommand interface {
Getenv(string) string
}
func NewFileCommands(
common *common.Common,
cmd oscommands.ICmdObjBuilder,
config *ConfigCommands,
osCommand FileOSCommand,
) *FileCommands {
return &FileCommands{
Common: common,
cmd: cmd,
config: config,
os: osCommand,
}
}
// Cat obtains the content of a file
func (self *FileCommands) Cat(fileName string) (string, error) {
buf, err := ioutil.ReadFile(fileName)
if err != nil {
return "", nil
@ -25,335 +45,24 @@ func (c *GitCommand) CatFile(fileName string) (string, error) {
return string(buf), nil
}
func (c *GitCommand) OpenMergeToolCmdObj() oscommands.ICmdObj {
return c.Cmd.New("git mergetool")
}
func (c *GitCommand) OpenMergeTool() error {
return c.OpenMergeToolCmdObj().Run()
}
// StageFile stages a file
func (c *GitCommand) StageFile(fileName string) error {
return c.Cmd.New("git add -- " + c.OSCommand.Quote(fileName)).Run()
}
// StageAll stages all files
func (c *GitCommand) StageAll() error {
return c.Cmd.New("git add -A").Run()
}
// UnstageAll unstages all files
func (c *GitCommand) UnstageAll() error {
return c.Cmd.New("git reset").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 (c *GitCommand) UnStageFile(fileNames []string, reset bool) error {
command := "git rm --cached --force -- %s"
if reset {
command = "git reset HEAD -- %s"
}
for _, name := range fileNames {
err := c.Cmd.New(fmt.Sprintf(command, c.OSCommand.Quote(name))).Run()
if err != nil {
return err
}
}
return nil
}
func (c *GitCommand) BeforeAndAfterFileForRename(file *models.File) (*models.File, *models.File, error) {
if !file.IsRename() {
return nil, nil, errors.New("Expected renamed file")
}
// we've got a file that represents a rename from one file to another. Here we will refetch
// all files, passing the --no-renames flag and then recursively call the function
// again for the before file and after file.
filesWithoutRenames := loaders.
NewFileLoader(c.Common, c.Cmd, c.GitConfig).
GetStatusFiles(loaders.GetStatusFileOptions{NoRenames: true})
var beforeFile *models.File
var afterFile *models.File
for _, f := range filesWithoutRenames {
if f.Name == file.PreviousName {
beforeFile = f
}
if f.Name == file.Name {
afterFile = f
}
}
if beforeFile == nil || afterFile == nil {
return nil, nil, errors.New("Could not find deleted file or new file for file rename")
}
if beforeFile.IsRename() || afterFile.IsRename() {
// probably won't happen but we want to ensure we don't get an infinite loop
return nil, nil, errors.New("Nested rename found")
}
return beforeFile, afterFile, nil
}
// DiscardAllFileChanges directly
func (c *GitCommand) DiscardAllFileChanges(file *models.File) error {
if file.IsRename() {
beforeFile, afterFile, err := c.BeforeAndAfterFileForRename(file)
if err != nil {
return err
}
if err := c.DiscardAllFileChanges(beforeFile); err != nil {
return err
}
if err := c.DiscardAllFileChanges(afterFile); err != nil {
return err
}
return nil
}
quotedFileName := c.OSCommand.Quote(file.Name)
if file.ShortStatus == "AA" {
if err := c.Cmd.New("git checkout --ours -- " + quotedFileName).Run(); err != nil {
return err
}
if err := c.Cmd.New("git add -- " + quotedFileName).Run(); err != nil {
return err
}
return nil
}
if file.ShortStatus == "DU" {
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.Cmd.New("git reset -- " + quotedFileName).Run(); err != nil {
return err
}
}
if file.ShortStatus == "DD" || file.ShortStatus == "AU" {
return nil
}
if file.Added {
return c.OSCommand.RemoveFile(file.Name)
}
return c.DiscardUnstagedFileChanges(file)
}
func (c *GitCommand) DiscardAllDirChanges(node *filetree.FileNode) error {
// this could be more efficient but we would need to handle all the edge cases
return node.ForEachFile(c.DiscardAllFileChanges)
}
func (c *GitCommand) DiscardUnstagedDirChanges(node *filetree.FileNode) error {
if err := c.RemoveUntrackedDirFiles(node); err != nil {
return err
}
quotedPath := c.OSCommand.Quote(node.GetPath())
if err := c.Cmd.New("git checkout -- " + quotedPath).Run(); err != nil {
return err
}
return nil
}
func (c *GitCommand) RemoveUntrackedDirFiles(node *filetree.FileNode) error {
untrackedFilePaths := node.GetPathsMatching(
func(n *filetree.FileNode) bool { return n.File != nil && !n.File.GetIsTracked() },
)
for _, path := range untrackedFilePaths {
err := os.Remove(path)
if err != nil {
return err
}
}
return nil
}
// DiscardUnstagedFileChanges directly
func (c *GitCommand) DiscardUnstagedFileChanges(file *models.File) error {
quotedFileName := c.OSCommand.Quote(file.Name)
return c.Cmd.New("git checkout -- " + quotedFileName).Run()
}
// Ignore adds a file to the gitignore for the repo
func (c *GitCommand) Ignore(filename string) error {
return c.OSCommand.AppendLineToFile(".gitignore", filename)
}
// 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.WorktreeFileDiffCmdObj(file, plain, cached, ignoreWhitespace).RunWithOutput()
return s
}
func (c *GitCommand) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool, ignoreWhitespace bool) oscommands.ICmdObj {
cachedArg := ""
trackedArg := "--"
colorArg := c.colorArg()
quotedPath := c.OSCommand.Quote(node.GetPath())
ignoreWhitespaceArg := ""
contextSize := c.UserConfig.Git.DiffContextSize
if cached {
cachedArg = "--cached"
}
if !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached {
trackedArg = "--no-index -- /dev/null"
}
if plain {
colorArg = "never"
}
if ignoreWhitespace {
ignoreWhitespaceArg = "--ignore-all-space"
}
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.Cmd.New(cmdStr).DontLog()
}
func (c *GitCommand) ApplyPatch(patch string, flags ...string) error {
filepath := filepath.Join(oscommands.GetTempDir(), utils.GetCurrentRepoName(), time.Now().Format("Jan _2 15.04.05.000000000")+".patch")
c.Log.Infof("saving temporary patch to %s", filepath)
if err := c.OSCommand.CreateFileWithContent(filepath, patch); err != nil {
return err
}
flagStr := ""
for _, flag := range flags {
flagStr += " --" + flag
}
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) {
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 {
colorArg := c.colorArg()
contextSize := c.UserConfig.Git.DiffContextSize
if plain {
colorArg = "never"
}
reverseFlag := ""
if reverse {
reverseFlag = " -R "
}
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))).DontLog()
}
// CheckoutFile checks out the file for the given commit
func (c *GitCommand) CheckoutFile(commitSha, fileName string) error {
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
func (c *GitCommand) DiscardOldFileChanges(commits []*models.Commit, commitIndex int, fileName string) error {
if err := c.BeginInteractiveRebaseForCommit(commits, commitIndex); err != nil {
return err
}
// check if file exists in previous commit (this command returns an error if the file doesn't exist)
if err := c.Cmd.New("git cat-file -e HEAD^:" + c.OSCommand.Quote(fileName)).DontLog().Run(); err != nil {
if err := c.OSCommand.Remove(fileName); err != nil {
return err
}
if err := c.StageFile(fileName); err != nil {
return err
}
} else if err := c.CheckoutFile("HEAD^", fileName); err != nil {
return err
}
// amend the commit
err := c.AmendHead()
if err != nil {
return err
}
// continue
return c.GenericMergeOrRebaseAction("rebase", "continue")
}
// DiscardAnyUnstagedFileChanges discards any unstages file changes via `git checkout -- .`
func (c *GitCommand) DiscardAnyUnstagedFileChanges() error {
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.Cmd.New("git rm -r --cached -- " + c.OSCommand.Quote(name)).Run()
}
// RemoveUntrackedFiles runs `git clean -fd`
func (c *GitCommand) RemoveUntrackedFiles() error {
return c.Cmd.New("git clean -fd").Run()
}
// ResetAndClean removes all unstaged changes and removes all untracked files
func (c *GitCommand) ResetAndClean() error {
submoduleConfigs, err := c.GetSubmoduleConfigs()
if err != nil {
return err
}
if len(submoduleConfigs) > 0 {
if err := c.ResetSubmodules(submoduleConfigs); err != nil {
return err
}
}
if err := c.ResetHard("HEAD"); err != nil {
return err
}
return c.RemoveUntrackedFiles()
}
func (c *GitCommand) EditFileCmdStr(filename string, lineNumber int) (string, error) {
func (c *FileCommands) GetEditCmdStr(filename string, lineNumber int) (string, error) {
editor := c.UserConfig.OS.EditCommand
if editor == "" {
editor = c.GitConfig.Get("core.editor")
editor = c.config.GetCoreEditor()
}
if editor == "" {
editor = c.OSCommand.Getenv("GIT_EDITOR")
editor = c.os.Getenv("GIT_EDITOR")
}
if editor == "" {
editor = c.OSCommand.Getenv("VISUAL")
editor = c.os.Getenv("VISUAL")
}
if editor == "" {
editor = c.OSCommand.Getenv("EDITOR")
editor = c.os.Getenv("EDITOR")
}
if editor == "" {
if err := c.OSCommand.Cmd.New("which vi").DontLog().Run(); err == nil {
if err := c.cmd.New("which vi").DontLog().Run(); err == nil {
editor = "vi"
}
}
@ -363,7 +72,7 @@ func (c *GitCommand) EditFileCmdStr(filename string, lineNumber int) (string, er
templateValues := map[string]string{
"editor": editor,
"filename": c.OSCommand.Quote(filename),
"filename": c.cmd.Quote(filename),
"line": strconv.Itoa(lineNumber),
}

View File

@ -1,602 +1,14 @@
package commands
import (
"fmt"
"io/ioutil"
"regexp"
"testing"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/stretchr/testify/assert"
)
func TestGitCommandStageFile(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"add", "--", "test.txt"}, "", nil)
gitCmd := NewDummyGitCommandWithRunner(runner)
assert.NoError(t, gitCmd.StageFile("test.txt"))
runner.CheckForMissingCalls()
}
func TestGitCommandUnstageFile(t *testing.T) {
type scenario struct {
testName string
reset bool
runner *oscommands.FakeCmdObjRunner
test func(error)
}
scenarios := []scenario{
{
testName: "Remove an untracked file from staging",
reset: false,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"rm", "--cached", "--force", "--", "test.txt"}, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
},
{
testName: "Remove a tracked file from staging",
reset: true,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"reset", "HEAD", "--", "test.txt"}, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
s.test(gitCmd.UnStageFile([]string{"test.txt"}, s.reset))
})
}
}
// these tests don't cover everything, in part because we already have an integration
// test which does cover everything. I don't want to unnecessarily assert on the 'how'
// when the 'what' is what matters
func TestGitCommandDiscardAllFileChanges(t *testing.T) {
type scenario struct {
testName string
file *models.File
removeFile func(string) error
runner *oscommands.FakeCmdObjRunner
expectedError string
}
scenarios := []scenario{
{
testName: "An error occurred when resetting",
file: &models.File{
Name: "test",
HasStagedChanges: true,
},
removeFile: func(string) error { return nil },
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"reset", "--", "test"}, "", errors.New("error")),
expectedError: "error",
},
{
testName: "An error occurred when removing file",
file: &models.File{
Name: "test",
Tracked: false,
Added: true,
},
removeFile: func(string) error {
return fmt.Errorf("an error occurred when removing file")
},
runner: oscommands.NewFakeRunner(t),
expectedError: "an error occurred when removing file",
},
{
testName: "An error occurred with checkout",
file: &models.File{
Name: "test",
Tracked: true,
HasStagedChanges: false,
},
removeFile: func(string) error { return nil },
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"checkout", "--", "test"}, "", errors.New("error")),
expectedError: "error",
},
{
testName: "Checkout only",
file: &models.File{
Name: "test",
Tracked: true,
HasStagedChanges: false,
},
removeFile: func(string) error { return nil },
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"checkout", "--", "test"}, "", nil),
expectedError: "",
},
{
testName: "Reset and checkout staged changes",
file: &models.File{
Name: "test",
Tracked: true,
HasStagedChanges: true,
},
removeFile: func(string) error { return nil },
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"reset", "--", "test"}, "", nil).
ExpectGitArgs([]string{"checkout", "--", "test"}, "", nil),
expectedError: "",
},
{
testName: "Reset and checkout merge conflicts",
file: &models.File{
Name: "test",
Tracked: true,
HasMergeConflicts: true,
},
removeFile: func(string) error { return nil },
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"reset", "--", "test"}, "", nil).
ExpectGitArgs([]string{"checkout", "--", "test"}, "", nil),
expectedError: "",
},
{
testName: "Reset and remove",
file: &models.File{
Name: "test",
Tracked: false,
Added: true,
HasStagedChanges: true,
},
removeFile: func(filename string) error {
assert.Equal(t, "test", filename)
return nil
},
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"reset", "--", "test"}, "", nil),
expectedError: "",
},
{
testName: "Remove only",
file: &models.File{
Name: "test",
Tracked: false,
Added: true,
HasStagedChanges: false,
},
removeFile: func(filename string) error {
assert.Equal(t, "test", filename)
return nil
},
runner: oscommands.NewFakeRunner(t),
expectedError: "",
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
gitCmd.OSCommand.SetRemoveFile(s.removeFile)
err := gitCmd.DiscardAllFileChanges(s.file)
if s.expectedError == "" {
assert.Nil(t, err)
} else {
assert.Equal(t, s.expectedError, err.Error())
}
s.runner.CheckForMissingCalls()
})
}
}
func TestGitCommandDiff(t *testing.T) {
type scenario struct {
testName string
file *models.File
plain bool
cached bool
ignoreWhitespace bool
contextSize int
runner *oscommands.FakeCmdObjRunner
}
const expectedResult = "pretend this is an actual git diff"
scenarios := []scenario{
{
testName: "Default case",
file: &models.File{
Name: "test.txt",
HasStagedChanges: false,
Tracked: true,
},
plain: false,
cached: false,
ignoreWhitespace: false,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "cached",
file: &models.File{
Name: "test.txt",
HasStagedChanges: false,
Tracked: true,
},
plain: false,
cached: true,
ignoreWhitespace: false,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--cached", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "plain",
file: &models.File{
Name: "test.txt",
HasStagedChanges: false,
Tracked: true,
},
plain: true,
cached: false,
ignoreWhitespace: false,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=never", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "File not tracked and file has no staged changes",
file: &models.File{
Name: "test.txt",
HasStagedChanges: false,
Tracked: false,
},
plain: false,
cached: false,
ignoreWhitespace: false,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--no-index", "--", "/dev/null", "test.txt"}, expectedResult, nil),
},
{
testName: "Default case (ignore whitespace)",
file: &models.File{
Name: "test.txt",
HasStagedChanges: false,
Tracked: true,
},
plain: false,
cached: false,
ignoreWhitespace: true,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "Show diff with custom context size",
file: &models.File{
Name: "test.txt",
HasStagedChanges: false,
Tracked: true,
},
plain: false,
cached: false,
ignoreWhitespace: false,
contextSize: 17,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=17", "--color=always", "--", "test.txt"}, expectedResult, nil),
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
gitCmd.UserConfig.Git.DiffContextSize = s.contextSize
result := gitCmd.WorktreeFileDiff(s.file, s.plain, s.cached, s.ignoreWhitespace)
assert.Equal(t, expectedResult, result)
s.runner.CheckForMissingCalls()
})
}
}
func TestGitCommandShowFileDiff(t *testing.T) {
type scenario struct {
testName string
from string
to string
reverse bool
plain bool
contextSize int
runner *oscommands.FakeCmdObjRunner
}
const expectedResult = "pretend this is an actual git diff"
scenarios := []scenario{
{
testName: "Default case",
from: "1234567890",
to: "0987654321",
reverse: false,
plain: false,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "Show diff with custom context size",
from: "1234567890",
to: "0987654321",
reverse: false,
plain: false,
contextSize: 123,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"diff", "--submodule", "--no-ext-diff", "--unified=123", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
gitCmd.UserConfig.Git.DiffContextSize = s.contextSize
result, err := gitCmd.ShowFileDiff(s.from, s.to, s.reverse, "test.txt", s.plain)
assert.NoError(t, err)
assert.Equal(t, expectedResult, result)
s.runner.CheckForMissingCalls()
})
}
}
func TestGitCommandCheckoutFile(t *testing.T) {
type scenario struct {
testName string
commitSha string
fileName string
runner *oscommands.FakeCmdObjRunner
test func(error)
}
scenarios := []scenario{
{
testName: "typical case",
commitSha: "11af912",
fileName: "test999.txt",
runner: oscommands.NewFakeRunner(t).
Expect(`git checkout 11af912 -- "test999.txt"`, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
},
{
testName: "returns error if there is one",
commitSha: "11af912",
fileName: "test999.txt",
runner: oscommands.NewFakeRunner(t).
Expect(`git checkout 11af912 -- "test999.txt"`, "", errors.New("error")),
test: func(err error) {
assert.Error(t, err)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
s.test(gitCmd.CheckoutFile(s.commitSha, s.fileName))
s.runner.CheckForMissingCalls()
})
}
}
func TestGitCommandApplyPatch(t *testing.T) {
type scenario struct {
testName string
runner *oscommands.FakeCmdObjRunner
test func(error)
}
expectFn := func(regexStr string, errToReturn error) func(cmdObj oscommands.ICmdObj) (string, error) {
return func(cmdObj oscommands.ICmdObj) (string, error) {
re := regexp.MustCompile(regexStr)
matches := re.FindStringSubmatch(cmdObj.ToString())
assert.Equal(t, 2, len(matches))
filename := matches[1]
content, err := ioutil.ReadFile(filename)
assert.NoError(t, err)
assert.Equal(t, "test", string(content))
return "", errToReturn
}
}
scenarios := []scenario{
{
testName: "valid case",
runner: oscommands.NewFakeRunner(t).
ExpectFunc(expectFn(`git apply --cached "(.*)"`, nil)),
test: func(err error) {
assert.NoError(t, err)
},
},
{
testName: "command returns error",
runner: oscommands.NewFakeRunner(t).
ExpectFunc(expectFn(`git apply --cached "(.*)"`, errors.New("error"))),
test: func(err error) {
assert.Error(t, err)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
s.test(gitCmd.ApplyPatch("test", "cached"))
s.runner.CheckForMissingCalls()
})
}
}
func TestGitCommandDiscardOldFileChanges(t *testing.T) {
type scenario struct {
testName string
gitConfigMockResponses map[string]string
commits []*models.Commit
commitIndex int
fileName string
runner *oscommands.FakeCmdObjRunner
test func(error)
}
scenarios := []scenario{
{
testName: "returns error when index outside of range of commits",
gitConfigMockResponses: nil,
commits: []*models.Commit{},
commitIndex: 0,
fileName: "test999.txt",
runner: oscommands.NewFakeRunner(t),
test: func(err error) {
assert.Error(t, err)
},
},
{
testName: "returns error when using gpg",
gitConfigMockResponses: map[string]string{"commit.gpgsign": "true"},
commits: []*models.Commit{{Name: "commit", Sha: "123456"}},
commitIndex: 0,
fileName: "test999.txt",
runner: oscommands.NewFakeRunner(t),
test: func(err error) {
assert.Error(t, err)
},
},
{
testName: "checks out file if it already existed",
gitConfigMockResponses: nil,
commits: []*models.Commit{
{Name: "commit", Sha: "123456"},
{Name: "commit2", Sha: "abcdef"},
},
commitIndex: 0,
fileName: "test999.txt",
runner: oscommands.NewFakeRunner(t).
Expect(`git rebase --interactive --autostash --keep-empty abcdef`, "", nil).
Expect(`git cat-file -e HEAD^:"test999.txt"`, "", nil).
Expect(`git checkout HEAD^ -- "test999.txt"`, "", nil).
Expect(`git commit --amend --no-edit --allow-empty`, "", nil).
Expect(`git rebase --continue`, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
},
// test for when the file was created within the commit requires a refactor to support proper mocks
// currently we'd need to mock out the os.Remove function and that's gonna introduce tech debt
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
gitCmd.GitConfig = git_config.NewFakeGitConfig(s.gitConfigMockResponses)
s.test(gitCmd.DiscardOldFileChanges(s.commits, s.commitIndex, s.fileName))
s.runner.CheckForMissingCalls()
})
}
}
func TestGitCommandDiscardUnstagedFileChanges(t *testing.T) {
type scenario struct {
testName string
file *models.File
runner *oscommands.FakeCmdObjRunner
test func(error)
}
scenarios := []scenario{
{
testName: "valid case",
file: &models.File{Name: "test.txt"},
runner: oscommands.NewFakeRunner(t).
Expect(`git checkout -- "test.txt"`, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
s.test(gitCmd.DiscardUnstagedFileChanges(s.file))
s.runner.CheckForMissingCalls()
})
}
}
func TestGitCommandDiscardAnyUnstagedFileChanges(t *testing.T) {
type scenario struct {
testName string
runner *oscommands.FakeCmdObjRunner
test func(error)
}
scenarios := []scenario{
{
testName: "valid case",
runner: oscommands.NewFakeRunner(t).
Expect(`git checkout -- .`, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
s.test(gitCmd.DiscardAnyUnstagedFileChanges())
s.runner.CheckForMissingCalls()
})
}
}
func TestGitCommandRemoveUntrackedFiles(t *testing.T) {
type scenario struct {
testName string
runner *oscommands.FakeCmdObjRunner
test func(error)
}
scenarios := []scenario{
{
testName: "valid case",
runner: oscommands.NewFakeRunner(t).
Expect(`git clean -fd`, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
s.test(gitCmd.RemoveUntrackedFiles())
s.runner.CheckForMissingCalls()
})
}
}
func TestEditFileCmdStr(t *testing.T) {
type scenario struct {
filename string
@ -736,9 +148,9 @@ func TestEditFileCmdStr(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
gitCmd.UserConfig.OS.EditCommand = s.configEditCommand
gitCmd.UserConfig.OS.EditCommandTemplate = s.configEditCommandTemplate
gitCmd.OSCommand.Getenv = s.getenv
gitCmd.GitConfig = git_config.NewFakeGitConfig(s.gitConfigMockResponses)
s.test(gitCmd.EditFileCmdStr(s.filename, 1))
gitCmd.OSCommand.GetenvFn = s.getenv
gitCmd.gitConfig = git_config.NewFakeGitConfig(s.gitConfigMockResponses)
s.test(gitCmd.File.GetEditCmdStr(s.filename, 1))
s.runner.CheckForMissingCalls()
}
}

View File

@ -1,7 +1,6 @@
package commands
import (
"io"
"io/ioutil"
"os"
"path/filepath"
@ -19,11 +18,31 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils"
)
// this takes something like:
// * (HEAD detached at 264fc6f5)
// remotes
// and returns '264fc6f5' as the second match
const CurrentBranchNameRegex = `(?m)^\*.*?([^ ]*?)\)?$`
// GitCommand is our main git interface
type GitCommand struct {
*common.Common
OSCommand *oscommands.OSCommand
Repo *gogit.Repository
Loaders Loaders
Cmd oscommands.ICmdObjBuilder
Submodule *SubmoduleCommands
Tag *TagCommands
WorkingTree *WorkingTreeCommands
File *FileCommands
Branch *BranchCommands
Commit *CommitCommands
Rebase *RebaseCommands
Stash *StashCommands
Status *StatusCommands
Config *ConfigCommands
Patch *PatchCommands
Remote *RemoteCommands
Sync *SyncCommands
}
type Loaders struct {
Commits *loaders.CommitLoader
@ -36,44 +55,17 @@ type Loaders struct {
Tags *loaders.TagLoader
}
// GitCommand is our main git interface
type GitCommand struct {
*common.Common
OSCommand *oscommands.OSCommand
Repo *gogit.Repository
DotGitDir string
onSuccessfulContinue func() error
PatchManager *patch.PatchManager
GitConfig git_config.IGitConfig
Loaders Loaders
// Push to current determines whether the user has configured to push to the remote branch of the same name as the current or not
PushToCurrent bool
// this is just a view that we write to when running certain commands.
// 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
func NewGitCommand(
cmn *common.Common,
osCommand *oscommands.OSCommand,
gitConfig git_config.IGitConfig,
) (*GitCommand, error) {
var repo *gogit.Repository
pushToCurrent := gitConfig.Get("push.default") == "current"
if err := navigateToRepoRootDirectory(os.Stat, os.Chdir); err != nil {
return nil, err
}
var err error
if repo, err = setupRepository(gogit.PlainOpen, cmn.Tr.GitconfigParseErr); err != nil {
repo, err := setupRepository(gogit.PlainOpen, cmn.Tr.GitconfigParseErr)
if err != nil {
return nil, err
}
@ -82,33 +74,81 @@ func NewGitCommand(
return nil, err
}
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd)
gitCommand := &GitCommand{
Common: cmn,
OSCommand: osCommand,
Repo: repo,
DotGitDir: dotGitDir,
PushToCurrent: pushToCurrent,
GitConfig: gitConfig,
GetCmdWriter: func() io.Writer { return ioutil.Discard },
Cmd: cmd,
return NewGitCommandAux(
cmn,
osCommand,
gitConfig,
dotGitDir,
repo,
), nil
}
gitCommand.Loaders = Loaders{
Commits: loaders.NewCommitLoader(cmn, gitCommand),
Branches: loaders.NewBranchLoader(cmn, gitCommand),
Files: loaders.NewFileLoader(cmn, cmd, gitConfig),
func NewGitCommandAux(
cmn *common.Common,
osCommand *oscommands.OSCommand,
gitConfig git_config.IGitConfig,
dotGitDir string,
repo *gogit.Repository,
) *GitCommand {
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd)
configCommands := NewConfigCommands(cmn, gitConfig)
statusCommands := NewStatusCommands(cmn, osCommand, repo, dotGitDir)
fileLoader := loaders.NewFileLoader(cmn, cmd, configCommands)
remoteCommands := NewRemoteCommands(cmn, cmd)
branchCommands := NewBranchCommands(cmn, cmd)
syncCommands := NewSyncCommands(cmn, cmd)
tagCommands := NewTagCommands(cmn, cmd)
commitCommands := NewCommitCommands(cmn, cmd)
fileCommands := NewFileCommands(cmn, cmd, configCommands, osCommand)
submoduleCommands := NewSubmoduleCommands(cmn, cmd, dotGitDir)
workingTreeCommands := NewWorkingTreeCommands(cmn, cmd, submoduleCommands, osCommand, fileLoader)
rebaseCommands := NewRebaseCommands(
cmn,
cmd,
osCommand,
commitCommands,
workingTreeCommands,
configCommands,
dotGitDir,
)
stashCommands := NewStashCommands(cmn, cmd, osCommand, fileLoader, workingTreeCommands)
// TODO: have patch manager take workingTreeCommands in its entirety
patchManager := patch.NewPatchManager(cmn.Log, workingTreeCommands.ApplyPatch, workingTreeCommands.ShowFileDiff)
patchCommands := NewPatchCommands(cmn, cmd, rebaseCommands, commitCommands, configCommands, statusCommands, patchManager)
return &GitCommand{
Common: cmn,
OSCommand: osCommand,
Repo: repo,
Cmd: cmd,
Submodule: submoduleCommands,
Tag: tagCommands,
WorkingTree: workingTreeCommands,
File: fileCommands,
Branch: branchCommands,
Commit: commitCommands,
Rebase: rebaseCommands,
Config: configCommands,
Stash: stashCommands,
Status: statusCommands,
Patch: patchCommands,
Remote: remoteCommands,
Sync: syncCommands,
Loaders: Loaders{
Commits: loaders.NewCommitLoader(cmn, cmd, dotGitDir, branchCommands.CurrentBranchName, statusCommands.RebaseMode),
Branches: loaders.NewBranchLoader(cmn, branchCommands.GetRawBranches, branchCommands.CurrentBranchName),
Files: fileLoader,
CommitFiles: loaders.NewCommitFileLoader(cmn, cmd),
Remotes: loaders.NewRemoteLoader(cmn, cmd, gitCommand.Repo.Remotes),
Remotes: loaders.NewRemoteLoader(cmn, cmd, repo.Remotes),
ReflogCommits: loaders.NewReflogCommitLoader(cmn, cmd),
Stash: loaders.NewStashLoader(cmn, cmd),
Tags: loaders.NewTagLoader(cmn, cmd),
},
}
gitCommand.PatchManager = patch.NewPatchManager(gitCommand.Log, gitCommand.ApplyPatch, gitCommand.ShowFileDiff)
return gitCommand, nil
}
func navigateToRepoRootDirectory(stat func(string) (os.FileInfo, error), chdir func(string) error) error {
@ -224,11 +264,3 @@ func findDotGitDir(stat func(string) (os.FileInfo, error), readFile func(filenam
func VerifyInGitRepo(osCommand *oscommands.OSCommand) error {
return osCommand.Cmd.New("git rev-parse --git-dir").DontLog().Run()
}
func (c *GitCommand) GetDotGitDir() string {
return c.DotGitDir
}
func (c *GitCommand) GetCmd() oscommands.ICmdObjBuilder {
return c.Cmd
}

View File

@ -27,19 +27,15 @@ type BranchLoader struct {
getCurrentBranchName func() (string, string, error)
}
type BranchLoaderGitCommand interface {
GetRawBranches() (string, error)
CurrentBranchName() (string, string, error)
}
func NewBranchLoader(
cmn *common.Common,
gitCommand BranchLoaderGitCommand,
getRawBranches func() (string, error),
getCurrentBranchName func() (string, string, error),
) *BranchLoader {
return &BranchLoader{
Common: cmn,
getRawBranches: gitCommand.GetRawBranches,
getCurrentBranchName: gitCommand.CurrentBranchName,
getRawBranches: getRawBranches,
getCurrentBranchName: getCurrentBranchName,
}
}

View File

@ -36,26 +36,22 @@ type CommitLoader struct {
dotGitDir string
}
type CommitLoaderGitCommand interface {
CurrentBranchName() (string, string, error)
RebaseMode() (enums.RebaseMode, error)
GetCmd() oscommands.ICmdObjBuilder
GetDotGitDir() string
}
// making our dependencies explicit for the sake of easier testing
func NewCommitLoader(
cmn *common.Common,
gitCommand CommitLoaderGitCommand,
cmd oscommands.ICmdObjBuilder,
dotGitDir string,
getCurrentBranchName func() (string, string, error),
getRebaseMode func() (enums.RebaseMode, error),
) *CommitLoader {
return &CommitLoader{
Common: cmn,
cmd: gitCommand.GetCmd(),
getCurrentBranchName: gitCommand.CurrentBranchName,
getRebaseMode: gitCommand.RebaseMode,
cmd: cmd,
getCurrentBranchName: getCurrentBranchName,
getRebaseMode: getRebaseMode,
readFile: ioutil.ReadFile,
walkFiles: filepath.Walk,
dotGitDir: gitCommand.GetDotGitDir(),
dotGitDir: dotGitDir,
}
}

View File

@ -11,19 +11,24 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils"
)
type FileLoaderConfig interface {
GetShowUntrackedFiles() string
}
type FileLoader struct {
*common.Common
cmd oscommands.ICmdObjBuilder
config FileLoaderConfig
gitConfig git_config.IGitConfig
getFileType func(string) string
}
func NewFileLoader(cmn *common.Common, cmd oscommands.ICmdObjBuilder, gitConfig git_config.IGitConfig) *FileLoader {
func NewFileLoader(cmn *common.Common, cmd oscommands.ICmdObjBuilder, config FileLoaderConfig) *FileLoader {
return &FileLoader{
Common: cmn,
cmd: cmd,
gitConfig: gitConfig,
getFileType: oscommands.FileType,
config: config,
}
}
@ -33,7 +38,7 @@ type GetStatusFileOptions struct {
func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File {
// check if config wants us ignoring untracked files
untrackedFilesSetting := self.gitConfig.Get("status.showUntrackedFiles")
untrackedFilesSetting := self.config.GetShowUntrackedFiles()
if untrackedFilesSetting == "" {
untrackedFilesSetting = "all"

View File

@ -34,6 +34,11 @@ type ICmdObj interface {
// This returns false if DontLog() was called
ShouldLog() bool
PromptOnCredentialRequest() ICmdObj
FailOnCredentialRequest() ICmdObj
GetCredentialStrategy() CredentialStrategy
}
type CmdObj struct {
@ -44,8 +49,28 @@ type CmdObj struct {
// if set to true, we don't want to log the command to the user.
dontLog bool
// if set to true, it means we might be asked to enter a username/password by this command.
credentialStrategy CredentialStrategy
}
type CredentialStrategy int
const (
// do not expect a credential request. If we end up getting one
// we'll be in trouble because the command will hang indefinitely
NONE CredentialStrategy = iota
// expect a credential request and if we get one, prompt the user to enter their username/password
PROMPT
// in this case we will check for a credential request (i.e. the command pauses to ask for
// username/password) and if we get one, we just submit a newline, forcing the
// command to fail. We use this e.g. for a background `git fetch` to prevent it
// from hanging indefinitely.
FAIL
)
var _ ICmdObj = &CmdObj{}
func (self *CmdObj) GetCmd() *exec.Cmd {
return self.cmd
}
@ -84,3 +109,19 @@ func (self *CmdObj) RunWithOutput() (string, error) {
func (self *CmdObj) RunAndProcessLines(onLine func(line string) (bool, error)) error {
return self.runner.RunAndProcessLines(self, onLine)
}
func (self *CmdObj) PromptOnCredentialRequest() ICmdObj {
self.credentialStrategy = PROMPT
return self
}
func (self *CmdObj) FailOnCredentialRequest() ICmdObj {
self.credentialStrategy = FAIL
return self
}
func (self *CmdObj) GetCredentialStrategy() CredentialStrategy {
return self.credentialStrategy
}

View File

@ -16,17 +16,45 @@ type ICmdObjRunner interface {
type cmdObjRunner struct {
log *logrus.Entry
logCmdObj func(ICmdObj)
guiIO *guiIO
}
var _ ICmdObjRunner = &cmdObjRunner{}
func (self *cmdObjRunner) runWithCredentialHandling(cmdObj ICmdObj) error {
switch cmdObj.GetCredentialStrategy() {
case PROMPT:
return self.RunCommandWithOutputLive(cmdObj, self.guiIO.promptForCredentialFn)
case FAIL:
return self.RunCommandWithOutputLive(cmdObj, func(s string) string { return "\n" })
}
// we should never land here
return errors.New("runWithCredentialHandling called but cmdObj does not have a a credential strategy")
}
func (self *cmdObjRunner) Run(cmdObj ICmdObj) error {
if cmdObj.GetCredentialStrategy() == NONE {
_, err := self.RunWithOutput(cmdObj)
return err
} else {
return self.runWithCredentialHandling(cmdObj)
}
}
func (self *cmdObjRunner) logCmdObj(cmdObj ICmdObj) {
self.guiIO.logCommandFn(cmdObj.ToString(), true)
}
func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
if cmdObj.GetCredentialStrategy() != NONE {
err := self.runWithCredentialHandling(cmdObj)
// for now we're not capturing output, just because it would take a little more
// effort and there's currently no use case for it. Some commands call RunWithOutput
// but ignore the output, hence why we've got this check here.
return "", err
}
if cmdObj.ShouldLog() {
self.logCmdObj(cmdObj)
}
@ -39,6 +67,10 @@ func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
}
func (self *cmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error {
if cmdObj.GetCredentialStrategy() != NONE {
return errors.New("cannot call RunAndProcessLines with credential strategy. If you're seeing this then a contributor to Lazygit has accidentally called this method! Please raise an issue")
}
if cmdObj.ShouldLog() {
self.logCmdObj(cmdObj)
}

View File

@ -6,7 +6,7 @@ import (
// NewDummyOSCommand creates a new dummy OSCommand for testing
func NewDummyOSCommand() *OSCommand {
osCmd := NewOSCommand(utils.NewDummyCommon(), dummyPlatform)
osCmd := NewOSCommand(utils.NewDummyCommon(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog()))
return osCmd
}
@ -27,7 +27,7 @@ var dummyPlatform = &Platform{
}
func NewDummyOSCommandWithRunner(runner *FakeCmdObjRunner) *OSCommand {
osCommand := NewOSCommand(utils.NewDummyCommon(), dummyPlatform)
osCommand := NewOSCommand(utils.NewDummyCommon(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog()))
osCommand.Cmd = NewDummyCmdObjBuilder(runner)
return osCommand

View File

@ -13,12 +13,12 @@ import (
"github.com/jesseduffield/lazygit/pkg/utils"
)
// DetectUnamePass detect a username / password / passphrase question in a command
// RunAndDetectCredentialRequest detect a username / password / passphrase question in a command
// promptUserForCredential is a function that gets executed when this function detect you need to fillin a password or passphrase
// The promptUserForCredential argument will be "username", "password" or "passphrase" and expects the user's password/passphrase or username back
func (c *OSCommand) DetectUnamePass(cmdObj ICmdObj, writer io.Writer, promptUserForCredential func(string) string) error {
func (self *cmdObjRunner) RunAndDetectCredentialRequest(cmdObj ICmdObj, promptUserForCredential func(string) string) error {
ttyText := ""
errMessage := c.RunCommandWithOutputLive(cmdObj, writer, func(word string) string {
err := self.RunCommandWithOutputLive(cmdObj, func(word string) string {
ttyText = ttyText + " " + word
prompts := map[string]string{
@ -37,13 +37,7 @@ func (c *OSCommand) DetectUnamePass(cmdObj ICmdObj, writer io.Writer, promptUser
return ""
})
return errMessage
}
// Due to a lack of pty support on windows we have RunCommandWithOutputLiveWrapper being defined
// separate for windows and other OS's
func (c *OSCommand) RunCommandWithOutputLive(cmdObj ICmdObj, writer io.Writer, handleOutput func(string) string) error {
return RunCommandWithOutputLiveWrapper(c, cmdObj, writer, handleOutput)
return err
}
type cmdHandler struct {
@ -56,23 +50,22 @@ type cmdHandler struct {
// Output is a function that executes by every word that gets read by bufio
// As return of output you need to give a string that will be written to stdin
// NOTE: If the return data is empty it won't write anything to stdin
func RunCommandWithOutputLiveAux(
c *OSCommand,
func (self *cmdObjRunner) RunCommandWithOutputLiveAux(
cmdObj ICmdObj,
writer io.Writer,
// handleOutput takes a word from stdout and returns a string to be written to stdin.
// See DetectUnamePass above for how this is used to check for a username/password request
// See RunAndDetectCredentialRequest above for how this is used to check for a username/password request
handleOutput func(string) string,
startCmd func(cmd *exec.Cmd) (*cmdHandler, error),
) error {
c.Log.WithField("command", cmdObj.ToString()).Info("RunCommand")
cmdWriter := self.guiIO.newCmdWriterFn()
self.log.WithField("command", cmdObj.ToString()).Info("RunCommand")
if cmdObj.ShouldLog() {
c.LogCommand(cmdObj.ToString(), true)
self.logCmdObj(cmdObj)
}
cmd := cmdObj.AddEnvVars("LANG=en_US.UTF-8", "LC_ALL=en_US.UTF-8").GetCmd()
var stderr bytes.Buffer
cmd.Stderr = io.MultiWriter(writer, &stderr)
cmd.Stderr = io.MultiWriter(cmdWriter, &stderr)
handler, err := startCmd(cmd)
if err != nil {
@ -81,11 +74,11 @@ func RunCommandWithOutputLiveAux(
defer func() {
if closeErr := handler.close(); closeErr != nil {
c.Log.Error(closeErr)
self.log.Error(closeErr)
}
}()
tr := io.TeeReader(handler.stdoutPipe, writer)
tr := io.TeeReader(handler.stdoutPipe, cmdWriter)
go utils.Safe(func() {
scanner := bufio.NewScanner(tr)

View File

@ -4,22 +4,19 @@
package oscommands
import (
"io"
"os/exec"
"github.com/creack/pty"
)
func RunCommandWithOutputLiveWrapper(
c *OSCommand,
// we define this separately for windows and non-windows given that windows does
// not have great PTY support and we need a PTY to handle a credential request
func (self *cmdObjRunner) RunCommandWithOutputLive(
cmdObj ICmdObj,
writer io.Writer,
output func(string) string,
) error {
return RunCommandWithOutputLiveAux(
c,
return self.RunCommandWithOutputLiveAux(
cmdObj,
writer,
output,
func(cmd *exec.Cmd) (*cmdHandler, error) {
ptmx, err := pty.Start(cmd)

View File

@ -26,18 +26,14 @@ func (b *Buffer) Write(p []byte) (n int, err error) {
return b.b.Write(p)
}
// RunCommandWithOutputLiveWrapper runs a command live but because of windows compatibility this command can't be ran there
// RunCommandWithOutputLive runs a command live but because of windows compatibility this command can't be ran there
// TODO: Remove this hack and replace it with a proper way to run commands live on windows. We still have an issue where if a password is requested, the request for a password is written straight to stdout because we can't control the stdout of a subprocess of a subprocess. Keep an eye on https://github.com/creack/pty/pull/109
func RunCommandWithOutputLiveWrapper(
c *OSCommand,
func (self *cmdObjRunner) RunCommandWithOutputLive(
cmdObj ICmdObj,
writer io.Writer,
output func(string) string,
) error {
return RunCommandWithOutputLiveAux(
c,
return self.RunCommandWithOutputLiveAux(
cmdObj,
writer,
output,
func(cmd *exec.Cmd) (*cmdHandler, error) {
stdoutReader, stdoutWriter := io.Pipe()

View File

@ -0,0 +1,49 @@
package oscommands
import (
"io"
"io/ioutil"
"github.com/sirupsen/logrus"
)
// this struct captures some IO stuff
type guiIO struct {
// this is for logging anything we want. It'll be written to a log file for the sake
// of debugging.
log *logrus.Entry
// this is for us to log the command we're about to run e.g. 'git push'. The GUI
// will write this to a log panel so that the user can see which commands are being
// run.
// The isCommandLineCommand arg is there so that we can style the log differently
// depending on whether we're directly outputting a command we're about to run that
// will be run on the command line, or if we're using something from Go's standard lib.
logCommandFn func(str string, isCommandLineCommand bool)
// this is for us to directly write the output of a command. We will do this for
// certain commands like 'git push'. The GUI will write this to a command output panel.
// We need a new cmd writer per command, hence it being a function.
newCmdWriterFn func() io.Writer
// this allows us to request info from the user like username/password, in the event
// that a command requests it.
// the 'credential' arg is something like 'username' or 'password'
promptForCredentialFn func(credential string) string
}
func NewGuiIO(log *logrus.Entry, logCommandFn func(string, bool), newCmdWriterFn func() io.Writer, promptForCredentialFn func(string) string) *guiIO {
return &guiIO{
log: log,
logCommandFn: logCommandFn,
newCmdWriterFn: newCmdWriterFn,
promptForCredentialFn: promptForCredentialFn,
}
}
func NewNullGuiIO(log *logrus.Entry) *guiIO {
return &guiIO{
log: log,
logCommandFn: func(string, bool) {},
newCmdWriterFn: func() io.Writer { return ioutil.Discard },
promptForCredentialFn: func(string) string { return "" },
}
}

View File

@ -20,7 +20,7 @@ import (
type OSCommand struct {
*common.Common
Platform *Platform
Getenv func(string) string
GetenvFn func(string) string
// callback to run before running a command, i.e. for the purposes of logging.
// the string argument is the command string e.g. 'git add .' and the bool is
@ -43,24 +43,20 @@ type Platform struct {
}
// NewOSCommand os command runner
func NewOSCommand(common *common.Common, platform *Platform) *OSCommand {
func NewOSCommand(common *common.Common, platform *Platform, guiIO *guiIO) *OSCommand {
c := &OSCommand{
Common: common,
Platform: platform,
Getenv: os.Getenv,
GetenvFn: os.Getenv,
removeFile: os.RemoveAll,
}
runner := &cmdObjRunner{log: common.Log, logCmdObj: c.LogCmdObj}
runner := &cmdObjRunner{log: common.Log, guiIO: guiIO}
c.Cmd = &CmdObjBuilder{runner: runner, platform: platform}
return c
}
func (c *OSCommand) LogCmdObj(cmdObj ICmdObj) {
c.LogCommand(cmdObj.ToString(), true)
}
func (c *OSCommand) LogCommand(cmdStr string, commandLine bool) {
c.Log.WithField("command", cmdStr).Info("RunCommand")
@ -270,6 +266,10 @@ func (c *OSCommand) RemoveFile(path string) error {
return c.removeFile(path)
}
func (c *OSCommand) Getenv(key string) string {
return c.GetenvFn(key)
}
func GetTempDir() string {
return filepath.Join(os.TempDir(), "lazygit")
}

View File

@ -5,64 +5,98 @@ import (
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/common"
)
type PatchCommands struct {
*common.Common
cmd oscommands.ICmdObjBuilder
rebase *RebaseCommands
commit *CommitCommands
config *ConfigCommands
stash *StashCommands
status *StatusCommands
PatchManager *patch.PatchManager
}
func NewPatchCommands(
common *common.Common,
cmd oscommands.ICmdObjBuilder,
rebaseCommands *RebaseCommands,
commitCommands *CommitCommands,
configCommands *ConfigCommands,
statusCommands *StatusCommands,
patchManager *patch.PatchManager,
) *PatchCommands {
return &PatchCommands{
Common: common,
cmd: cmd,
rebase: rebaseCommands,
commit: commitCommands,
config: configCommands,
status: statusCommands,
PatchManager: patchManager,
}
}
// DeletePatchesFromCommit applies a patch in reverse for a commit
func (c *GitCommand) DeletePatchesFromCommit(commits []*models.Commit, commitIndex int, p *patch.PatchManager) error {
if err := c.BeginInteractiveRebaseForCommit(commits, commitIndex); err != nil {
func (self *PatchCommands) DeletePatchesFromCommit(commits []*models.Commit, commitIndex int) error {
if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIndex); err != nil {
return err
}
// apply each patch in reverse
if err := p.ApplyPatches(true); err != nil {
if err := c.GenericMergeOrRebaseAction("rebase", "abort"); err != nil {
if err := self.PatchManager.ApplyPatches(true); err != nil {
if err := self.rebase.GenericMergeOrRebaseAction("rebase", "abort"); err != nil {
return err
}
return err
}
// time to amend the selected commit
if err := c.AmendHead(); err != nil {
if err := self.commit.AmendHead(); err != nil {
return err
}
c.onSuccessfulContinue = func() error {
c.PatchManager.Reset()
self.rebase.onSuccessfulContinue = func() error {
self.PatchManager.Reset()
return nil
}
// continue
return c.GenericMergeOrRebaseAction("rebase", "continue")
return self.rebase.GenericMergeOrRebaseAction("rebase", "continue")
}
func (c *GitCommand) MovePatchToSelectedCommit(commits []*models.Commit, sourceCommitIdx int, destinationCommitIdx int, p *patch.PatchManager) error {
func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, sourceCommitIdx int, destinationCommitIdx int) error {
if sourceCommitIdx < destinationCommitIdx {
if err := c.BeginInteractiveRebaseForCommit(commits, destinationCommitIdx); err != nil {
if err := self.rebase.BeginInteractiveRebaseForCommit(commits, destinationCommitIdx); err != nil {
return err
}
// apply each patch forward
if err := p.ApplyPatches(false); err != nil {
if err := c.GenericMergeOrRebaseAction("rebase", "abort"); err != nil {
if err := self.PatchManager.ApplyPatches(false); err != nil {
if err := self.rebase.GenericMergeOrRebaseAction("rebase", "abort"); err != nil {
return err
}
return err
}
// amend the destination commit
if err := c.AmendHead(); err != nil {
if err := self.commit.AmendHead(); err != nil {
return err
}
c.onSuccessfulContinue = func() error {
c.PatchManager.Reset()
self.rebase.onSuccessfulContinue = func() error {
self.PatchManager.Reset()
return nil
}
// continue
return c.GenericMergeOrRebaseAction("rebase", "continue")
return self.rebase.GenericMergeOrRebaseAction("rebase", "continue")
}
if len(commits)-1 < sourceCommitIdx {
@ -72,8 +106,8 @@ func (c *GitCommand) MovePatchToSelectedCommit(commits []*models.Commit, sourceC
// we can make this GPG thing possible it just means we need to do this in two parts:
// one where we handle the possibility of a credential request, and the other
// where we continue the rebase
if c.UsingGpg() {
return errors.New(c.Tr.DisabledForGPG)
if self.config.UsingGpg() {
return errors.New(self.Tr.DisabledForGPG)
}
baseIndex := sourceCommitIdx + 1
@ -86,7 +120,7 @@ func (c *GitCommand) MovePatchToSelectedCommit(commits []*models.Commit, sourceC
todo = a + " " + commit.Sha + " " + commit.Name + "\n" + todo
}
cmdObj, err := c.PrepareInteractiveRebaseCommand(commits[baseIndex].Sha, todo, true)
cmdObj, err := self.rebase.PrepareInteractiveRebaseCommand(commits[baseIndex].Sha, todo, true)
if err != nil {
return err
}
@ -96,62 +130,62 @@ func (c *GitCommand) MovePatchToSelectedCommit(commits []*models.Commit, sourceC
}
// apply each patch in reverse
if err := p.ApplyPatches(true); err != nil {
if err := c.GenericMergeOrRebaseAction("rebase", "abort"); err != nil {
if err := self.PatchManager.ApplyPatches(true); err != nil {
if err := self.rebase.GenericMergeOrRebaseAction("rebase", "abort"); err != nil {
return err
}
return err
}
// amend the source commit
if err := c.AmendHead(); err != nil {
if err := self.commit.AmendHead(); err != nil {
return err
}
if c.onSuccessfulContinue != nil {
if self.rebase.onSuccessfulContinue != nil {
return errors.New("You are midway through another rebase operation. Please abort to start again")
}
c.onSuccessfulContinue = func() error {
self.rebase.onSuccessfulContinue = func() error {
// now we should be up to the destination, so let's apply forward these patches to that.
// ideally we would ensure we're on the right commit but I'm not sure if that check is necessary
if err := p.ApplyPatches(false); err != nil {
if err := c.GenericMergeOrRebaseAction("rebase", "abort"); err != nil {
if err := self.PatchManager.ApplyPatches(false); err != nil {
if err := self.rebase.GenericMergeOrRebaseAction("rebase", "abort"); err != nil {
return err
}
return err
}
// amend the destination commit
if err := c.AmendHead(); err != nil {
if err := self.commit.AmendHead(); err != nil {
return err
}
c.onSuccessfulContinue = func() error {
c.PatchManager.Reset()
self.rebase.onSuccessfulContinue = func() error {
self.PatchManager.Reset()
return nil
}
return c.GenericMergeOrRebaseAction("rebase", "continue")
return self.rebase.GenericMergeOrRebaseAction("rebase", "continue")
}
return c.GenericMergeOrRebaseAction("rebase", "continue")
return self.rebase.GenericMergeOrRebaseAction("rebase", "continue")
}
func (c *GitCommand) MovePatchIntoIndex(commits []*models.Commit, commitIdx int, p *patch.PatchManager, stash bool) error {
func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitIdx int, stash bool) error {
if stash {
if err := c.StashSave(c.Tr.StashPrefix + commits[commitIdx].Sha); err != nil {
if err := self.stash.Save(self.Tr.StashPrefix + commits[commitIdx].Sha); err != nil {
return err
}
}
if err := c.BeginInteractiveRebaseForCommit(commits, commitIdx); err != nil {
if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIdx); err != nil {
return err
}
if err := p.ApplyPatches(true); err != nil {
if c.WorkingTreeState() == enums.REBASE_MODE_REBASING {
if err := c.GenericMergeOrRebaseAction("rebase", "abort"); err != nil {
if err := self.PatchManager.ApplyPatches(true); err != nil {
if self.status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
if err := self.rebase.GenericMergeOrRebaseAction("rebase", "abort"); err != nil {
return err
}
}
@ -159,19 +193,19 @@ func (c *GitCommand) MovePatchIntoIndex(commits []*models.Commit, commitIdx int,
}
// amend the commit
if err := c.AmendHead(); err != nil {
if err := self.commit.AmendHead(); err != nil {
return err
}
if c.onSuccessfulContinue != nil {
if self.rebase.onSuccessfulContinue != nil {
return errors.New("You are midway through another rebase operation. Please abort to start again")
}
c.onSuccessfulContinue = func() error {
self.rebase.onSuccessfulContinue = func() error {
// add patches to index
if err := p.ApplyPatches(false); err != nil {
if c.WorkingTreeState() == enums.REBASE_MODE_REBASING {
if err := c.GenericMergeOrRebaseAction("rebase", "abort"); err != nil {
if err := self.PatchManager.ApplyPatches(false); err != nil {
if self.status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
if err := self.rebase.GenericMergeOrRebaseAction("rebase", "abort"); err != nil {
return err
}
}
@ -179,54 +213,54 @@ func (c *GitCommand) MovePatchIntoIndex(commits []*models.Commit, commitIdx int,
}
if stash {
if err := c.StashDo(0, "apply"); err != nil {
if err := self.stash.Apply(0); err != nil {
return err
}
}
c.PatchManager.Reset()
self.PatchManager.Reset()
return nil
}
return c.GenericMergeOrRebaseAction("rebase", "continue")
return self.rebase.GenericMergeOrRebaseAction("rebase", "continue")
}
func (c *GitCommand) PullPatchIntoNewCommit(commits []*models.Commit, commitIdx int, p *patch.PatchManager) error {
if err := c.BeginInteractiveRebaseForCommit(commits, commitIdx); err != nil {
func (self *PatchCommands) PullPatchIntoNewCommit(commits []*models.Commit, commitIdx int) error {
if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIdx); err != nil {
return err
}
if err := p.ApplyPatches(true); err != nil {
if err := c.GenericMergeOrRebaseAction("rebase", "abort"); err != nil {
if err := self.PatchManager.ApplyPatches(true); err != nil {
if err := self.rebase.GenericMergeOrRebaseAction("rebase", "abort"); err != nil {
return err
}
return err
}
// amend the commit
if err := c.AmendHead(); err != nil {
if err := self.commit.AmendHead(); err != nil {
return err
}
// add patches to index
if err := p.ApplyPatches(false); err != nil {
if err := c.GenericMergeOrRebaseAction("rebase", "abort"); err != nil {
if err := self.PatchManager.ApplyPatches(false); err != nil {
if err := self.rebase.GenericMergeOrRebaseAction("rebase", "abort"); err != nil {
return err
}
return err
}
head_message, _ := c.GetHeadCommitMessage()
head_message, _ := self.commit.GetHeadCommitMessage()
new_message := fmt.Sprintf("Split from \"%s\"", head_message)
err := c.CommitCmdObj(new_message, "").Run()
err := self.commit.CommitCmdObj(new_message, "").Run()
if err != nil {
return err
}
if c.onSuccessfulContinue != nil {
if self.rebase.onSuccessfulContinue != nil {
return errors.New("You are midway through another rebase operation. Please abort to start again")
}
c.PatchManager.Reset()
return c.GenericMergeOrRebaseAction("rebase", "continue")
self.PatchManager.Reset()
return self.rebase.GenericMergeOrRebaseAction("rebase", "continue")
}

View File

@ -9,22 +9,56 @@ import (
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
)
func (c *GitCommand) RewordCommit(commits []*models.Commit, index int) (oscommands.ICmdObj, error) {
todo, sha, err := c.GenerateGenericRebaseTodo(commits, index, "reword")
type RebaseCommands struct {
*common.Common
cmd oscommands.ICmdObjBuilder
osCommand *oscommands.OSCommand
commit *CommitCommands
workingTree *WorkingTreeCommands
config *ConfigCommands
dotGitDir string
onSuccessfulContinue func() error
}
func NewRebaseCommands(
common *common.Common,
cmd oscommands.ICmdObjBuilder,
osCommand *oscommands.OSCommand,
commitCommands *CommitCommands,
workingTreeCommands *WorkingTreeCommands,
configCommands *ConfigCommands,
dotGitDir string,
) *RebaseCommands {
return &RebaseCommands{
Common: common,
cmd: cmd,
osCommand: osCommand,
commit: commitCommands,
workingTree: workingTreeCommands,
config: configCommands,
dotGitDir: dotGitDir,
}
}
func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int) (oscommands.ICmdObj, error) {
todo, sha, err := self.GenerateGenericRebaseTodo(commits, index, "reword")
if err != nil {
return nil, err
}
return c.PrepareInteractiveRebaseCommand(sha, todo, false)
return self.PrepareInteractiveRebaseCommand(sha, todo, false)
}
func (c *GitCommand) MoveCommitDown(commits []*models.Commit, index int) error {
func (self *RebaseCommands) MoveCommitDown(commits []*models.Commit, index int) error {
// we must ensure that we have at least two commits after the selected one
if len(commits) <= index+2 {
// assuming they aren't picking the bottom commit
return errors.New(c.Tr.NoRoom)
return errors.New(self.Tr.NoRoom)
}
todo := ""
@ -33,7 +67,7 @@ func (c *GitCommand) MoveCommitDown(commits []*models.Commit, index int) error {
todo = "pick " + commit.Sha + " " + commit.Name + "\n" + todo
}
cmdObj, err := c.PrepareInteractiveRebaseCommand(commits[index+2].Sha, todo, true)
cmdObj, err := self.PrepareInteractiveRebaseCommand(commits[index+2].Sha, todo, true)
if err != nil {
return err
}
@ -41,13 +75,13 @@ func (c *GitCommand) MoveCommitDown(commits []*models.Commit, index int) error {
return cmdObj.Run()
}
func (c *GitCommand) InteractiveRebase(commits []*models.Commit, index int, action string) error {
todo, sha, err := c.GenerateGenericRebaseTodo(commits, index, action)
func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index int, action string) error {
todo, sha, err := self.GenerateGenericRebaseTodo(commits, index, action)
if err != nil {
return err
}
cmdObj, err := c.PrepareInteractiveRebaseCommand(sha, todo, true)
cmdObj, err := self.PrepareInteractiveRebaseCommand(sha, todo, true)
if err != nil {
return err
}
@ -58,24 +92,24 @@ func (c *GitCommand) InteractiveRebase(commits []*models.Commit, index int, acti
// PrepareInteractiveRebaseCommand returns the cmd for an interactive rebase
// we tell git to run lazygit to edit the todo list, and we pass the client
// lazygit a todo string to write to the todo file
func (c *GitCommand) PrepareInteractiveRebaseCommand(baseSha string, todo string, overrideEditor bool) (oscommands.ICmdObj, error) {
func (self *RebaseCommands) PrepareInteractiveRebaseCommand(baseSha string, todo string, overrideEditor bool) (oscommands.ICmdObj, error) {
ex := oscommands.GetLazygitPath()
debug := "FALSE"
if c.Debug {
if self.Debug {
debug = "TRUE"
}
cmdStr := fmt.Sprintf("git rebase --interactive --autostash --keep-empty %s", baseSha)
c.Log.WithField("command", cmdStr).Info("RunCommand")
self.Log.WithField("command", cmdStr).Info("RunCommand")
cmdObj := c.Cmd.New(cmdStr)
cmdObj := self.cmd.New(cmdStr)
gitSequenceEditor := ex
if todo == "" {
gitSequenceEditor = "true"
} else {
c.OSCommand.LogCommand(fmt.Sprintf("Creating TODO file for interactive rebase: \n\n%s", todo), false)
self.osCommand.LogCommand(fmt.Sprintf("Creating TODO file for interactive rebase: \n\n%s", todo), false)
}
cmdObj.AddEnvVars(
@ -94,18 +128,18 @@ func (c *GitCommand) PrepareInteractiveRebaseCommand(baseSha string, todo string
return cmdObj, nil
}
func (c *GitCommand) GenerateGenericRebaseTodo(commits []*models.Commit, actionIndex int, action string) (string, string, error) {
func (self *RebaseCommands) GenerateGenericRebaseTodo(commits []*models.Commit, actionIndex int, action string) (string, string, error) {
baseIndex := actionIndex + 1
if len(commits) <= baseIndex {
return "", "", errors.New(c.Tr.CannotRebaseOntoFirstCommit)
return "", "", errors.New(self.Tr.CannotRebaseOntoFirstCommit)
}
if action == "squash" || action == "fixup" {
baseIndex++
if len(commits) <= baseIndex {
return "", "", errors.New(c.Tr.CannotSquashOntoSecondCommit)
return "", "", errors.New(self.Tr.CannotSquashOntoSecondCommit)
}
}
@ -129,24 +163,24 @@ func (c *GitCommand) GenerateGenericRebaseTodo(commits []*models.Commit, actionI
}
// AmendTo amends the given commit with whatever files are staged
func (c *GitCommand) AmendTo(sha string) error {
if err := c.CreateFixupCommit(sha); err != nil {
func (self *RebaseCommands) AmendTo(sha string) error {
if err := self.commit.CreateFixupCommit(sha); err != nil {
return err
}
return c.SquashAllAboveFixupCommits(sha)
return self.SquashAllAboveFixupCommits(sha)
}
// EditRebaseTodo sets the action at a given index in the git-rebase-todo file
func (c *GitCommand) EditRebaseTodo(index int, action string) error {
fileName := filepath.Join(c.DotGitDir, "rebase-merge/git-rebase-todo")
func (self *RebaseCommands) EditRebaseTodo(index int, action string) error {
fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
bytes, err := ioutil.ReadFile(fileName)
if err != nil {
return err
}
content := strings.Split(string(bytes), "\n")
commitCount := c.getTodoCommitCount(content)
commitCount := self.getTodoCommitCount(content)
// we have the most recent commit at the bottom whereas the todo file has
// it at the bottom, so we need to subtract our index from the commit count
@ -158,7 +192,7 @@ func (c *GitCommand) EditRebaseTodo(index int, action string) error {
return ioutil.WriteFile(fileName, []byte(result), 0644)
}
func (c *GitCommand) getTodoCommitCount(content []string) int {
func (self *RebaseCommands) getTodoCommitCount(content []string) int {
// count lines that are not blank and are not comments
commitCount := 0
for _, line := range content {
@ -170,15 +204,15 @@ func (c *GitCommand) getTodoCommitCount(content []string) int {
}
// MoveTodoDown moves a rebase todo item down by one position
func (c *GitCommand) MoveTodoDown(index int) error {
fileName := filepath.Join(c.DotGitDir, "rebase-merge/git-rebase-todo")
func (self *RebaseCommands) MoveTodoDown(index int) error {
fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
bytes, err := ioutil.ReadFile(fileName)
if err != nil {
return err
}
content := strings.Split(string(bytes), "\n")
commitCount := c.getTodoCommitCount(content)
commitCount := self.getTodoCommitCount(content)
contentIndex := commitCount - 1 - index
rearrangedContent := append(content[0:contentIndex-1], content[contentIndex], content[contentIndex-1])
@ -189,8 +223,8 @@ func (c *GitCommand) MoveTodoDown(index int) error {
}
// SquashAllAboveFixupCommits squashes all fixup! commits above the given one
func (c *GitCommand) SquashAllAboveFixupCommits(sha string) error {
return c.runSkipEditorCommand(
func (self *RebaseCommands) SquashAllAboveFixupCommits(sha string) error {
return self.runSkipEditorCommand(
fmt.Sprintf(
"git rebase --interactive --autostash --autosquash %s^",
sha,
@ -199,8 +233,8 @@ func (c *GitCommand) SquashAllAboveFixupCommits(sha string) error {
}
// BeginInteractiveRebaseForCommit starts an interactive rebase to edit the current
// commit and pick all others. After this you'll want to call `c.GenericMergeOrRebaseAction("rebase", "continue")`
func (c *GitCommand) BeginInteractiveRebaseForCommit(commits []*models.Commit, commitIndex int) error {
// commit and pick all others. After this you'll want to call `self.GenericMergeOrRebaseAction("rebase", "continue")`
func (self *RebaseCommands) BeginInteractiveRebaseForCommit(commits []*models.Commit, commitIndex int) error {
if len(commits)-1 < commitIndex {
return errors.New("index outside of range of commits")
}
@ -208,16 +242,16 @@ func (c *GitCommand) BeginInteractiveRebaseForCommit(commits []*models.Commit, c
// we can make this GPG thing possible it just means we need to do this in two parts:
// one where we handle the possibility of a credential request, and the other
// where we continue the rebase
if c.UsingGpg() {
return errors.New(c.Tr.DisabledForGPG)
if self.config.UsingGpg() {
return errors.New(self.Tr.DisabledForGPG)
}
todo, sha, err := c.GenerateGenericRebaseTodo(commits, commitIndex, "edit")
todo, sha, err := self.GenerateGenericRebaseTodo(commits, commitIndex, "edit")
if err != nil {
return err
}
cmdObj, err := c.PrepareInteractiveRebaseCommand(sha, todo, true)
cmdObj, err := self.PrepareInteractiveRebaseCommand(sha, todo, true)
if err != nil {
return err
}
@ -226,8 +260,8 @@ func (c *GitCommand) BeginInteractiveRebaseForCommit(commits []*models.Commit, c
}
// RebaseBranch interactive rebases onto a branch
func (c *GitCommand) RebaseBranch(branchName string) error {
cmdObj, err := c.PrepareInteractiveRebaseCommand(branchName, "", false)
func (self *RebaseCommands) RebaseBranch(branchName string) error {
cmdObj, err := self.PrepareInteractiveRebaseCommand(branchName, "", false)
if err != nil {
return err
}
@ -237,8 +271,8 @@ func (c *GitCommand) RebaseBranch(branchName string) error {
// GenericMerge takes a commandType of "merge" or "rebase" and a command of "abort", "skip" or "continue"
// By default we skip the editor in the case where a commit will be made
func (c *GitCommand) GenericMergeOrRebaseAction(commandType string, command string) error {
err := c.runSkipEditorCommand(
func (self *RebaseCommands) GenericMergeOrRebaseAction(commandType string, command string) error {
err := self.runSkipEditorCommand(
fmt.Sprintf(
"git %s --%s",
commandType,
@ -249,25 +283,25 @@ func (c *GitCommand) GenericMergeOrRebaseAction(commandType string, command stri
if !strings.Contains(err.Error(), "no rebase in progress") {
return err
}
c.Log.Warn(err)
self.Log.Warn(err)
}
// sometimes we need to do a sequence of things in a rebase but the user needs to
// fix merge conflicts along the way. When this happens we queue up the next step
// so that after the next successful rebase continue we can continue from where we left off
if commandType == "rebase" && command == "continue" && c.onSuccessfulContinue != nil {
f := c.onSuccessfulContinue
c.onSuccessfulContinue = nil
if commandType == "rebase" && command == "continue" && self.onSuccessfulContinue != nil {
f := self.onSuccessfulContinue
self.onSuccessfulContinue = nil
return f()
}
if command == "abort" {
c.onSuccessfulContinue = nil
self.onSuccessfulContinue = nil
}
return nil
}
func (c *GitCommand) runSkipEditorCommand(command string) error {
cmdObj := c.Cmd.New(command)
func (self *RebaseCommands) runSkipEditorCommand(command string) error {
cmdObj := self.cmd.New(command)
lazyGitPath := oscommands.GetLazygitPath()
return cmdObj.
AddEnvVars(
@ -278,3 +312,46 @@ func (c *GitCommand) runSkipEditorCommand(command string) error {
).
Run()
}
// DiscardOldFileChanges discards changes to a file from an old commit
func (self *RebaseCommands) DiscardOldFileChanges(commits []*models.Commit, commitIndex int, fileName string) error {
if err := self.BeginInteractiveRebaseForCommit(commits, commitIndex); err != nil {
return err
}
// 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 {
if err := self.osCommand.Remove(fileName); err != nil {
return err
}
if err := self.workingTree.StageFile(fileName); err != nil {
return err
}
} else if err := self.workingTree.CheckoutFile("HEAD^", fileName); err != nil {
return err
}
// amend the commit
err := self.commit.AmendHead()
if err != nil {
return err
}
// continue
return self.GenericMergeOrRebaseAction("rebase", "continue")
}
// CherryPickCommits begins an interactive rebase with the given shas being cherry picked onto HEAD
func (self *RebaseCommands) CherryPickCommits(commits []*models.Commit) error {
todo := ""
for _, commit := range commits {
todo = "pick " + commit.Sha + " " + commit.Name + "\n" + todo
}
cmdObj, err := self.PrepareInteractiveRebaseCommand("HEAD", todo, false)
if err != nil {
return err
}
return cmdObj.Run()
}

View File

@ -5,6 +5,8 @@ import (
"testing"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/stretchr/testify/assert"
@ -42,7 +44,7 @@ func TestGitCommandRebaseBranch(t *testing.T) {
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
s.test(gitCmd.RebaseBranch(s.arg))
s.test(gitCmd.Rebase.RebaseBranch(s.arg))
})
}
}
@ -70,7 +72,74 @@ func TestGitCommandSkipEditorCommand(t *testing.T) {
return "", nil
})
gitCmd := NewDummyGitCommandWithRunner(runner)
err := gitCmd.runSkipEditorCommand(commandStr)
err := gitCmd.Rebase.runSkipEditorCommand(commandStr)
assert.NoError(t, err)
runner.CheckForMissingCalls()
}
func TestGitCommandDiscardOldFileChanges(t *testing.T) {
type scenario struct {
testName string
gitConfigMockResponses map[string]string
commits []*models.Commit
commitIndex int
fileName string
runner *oscommands.FakeCmdObjRunner
test func(error)
}
scenarios := []scenario{
{
testName: "returns error when index outside of range of commits",
gitConfigMockResponses: nil,
commits: []*models.Commit{},
commitIndex: 0,
fileName: "test999.txt",
runner: oscommands.NewFakeRunner(t),
test: func(err error) {
assert.Error(t, err)
},
},
{
testName: "returns error when using gpg",
gitConfigMockResponses: map[string]string{"commit.gpgsign": "true"},
commits: []*models.Commit{{Name: "commit", Sha: "123456"}},
commitIndex: 0,
fileName: "test999.txt",
runner: oscommands.NewFakeRunner(t),
test: func(err error) {
assert.Error(t, err)
},
},
{
testName: "checks out file if it already existed",
gitConfigMockResponses: nil,
commits: []*models.Commit{
{Name: "commit", Sha: "123456"},
{Name: "commit2", Sha: "abcdef"},
},
commitIndex: 0,
fileName: "test999.txt",
runner: oscommands.NewFakeRunner(t).
Expect(`git rebase --interactive --autostash --keep-empty abcdef`, "", nil).
Expect(`git cat-file -e HEAD^:"test999.txt"`, "", nil).
Expect(`git checkout HEAD^ -- "test999.txt"`, "", nil).
Expect(`git commit --amend --no-edit --allow-empty`, "", nil).
Expect(`git rebase --continue`, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
},
// test for when the file was created within the commit requires a refactor to support proper mocks
// currently we'd need to mock out the os.Remove function and that's gonna introduce tech debt
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
gitCmd.gitConfig = git_config.NewFakeGitConfig(s.gitConfigMockResponses)
s.test(gitCmd.Rebase.DiscardOldFileChanges(s.commits, s.commitIndex, s.fileName))
s.runner.CheckForMissingCalls()
})
}
}

View File

@ -4,49 +4,60 @@ import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
)
func (c *GitCommand) AddRemote(name string, url string) error {
return c.Cmd.
New(fmt.Sprintf("git remote add %s %s", c.Cmd.Quote(name), c.Cmd.Quote(url))).
type RemoteCommands struct {
*common.Common
cmd oscommands.ICmdObjBuilder
}
func NewRemoteCommands(
common *common.Common,
cmd oscommands.ICmdObjBuilder,
) *RemoteCommands {
return &RemoteCommands{
Common: common,
cmd: cmd,
}
}
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()
}
func (c *GitCommand) RemoveRemote(name string) error {
return c.Cmd.
New(fmt.Sprintf("git remote remove %s", c.Cmd.Quote(name))).
func (self *RemoteCommands) RemoveRemote(name string) error {
return self.cmd.
New(fmt.Sprintf("git remote remove %s", self.cmd.Quote(name))).
Run()
}
func (c *GitCommand) RenameRemote(oldRemoteName string, newRemoteName string) error {
return c.Cmd.
New(fmt.Sprintf("git remote rename %s %s", c.Cmd.Quote(oldRemoteName), c.Cmd.Quote(newRemoteName))).
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()
}
func (c *GitCommand) UpdateRemoteUrl(remoteName string, updatedUrl string) error {
return c.Cmd.
New(fmt.Sprintf("git remote set-url %s %s", c.Cmd.Quote(remoteName), c.Cmd.Quote(updatedUrl))).
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()
}
func (c *GitCommand) DeleteRemoteBranch(remoteName string, branchName string, promptUserForCredential func(string) string) error {
command := fmt.Sprintf("git push %s --delete %s", c.Cmd.Quote(remoteName), c.Cmd.Quote(branchName))
cmdObj := c.Cmd.
New(command)
return c.DetectUnamePass(cmdObj, promptUserForCredential)
}
func (c *GitCommand) DetectUnamePass(cmdObj oscommands.ICmdObj, promptUserForCredential func(string) string) error {
return c.OSCommand.DetectUnamePass(cmdObj, c.GetCmdWriter(), promptUserForCredential)
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().Run()
}
// CheckRemoteBranchExists Returns remote branch
func (c *GitCommand) CheckRemoteBranchExists(branchName string) bool {
_, err := c.Cmd.
func (self *RemoteCommands) CheckRemoteBranchExists(branchName string) bool {
_, err := self.cmd.
New(
fmt.Sprintf("git show-ref --verify -- refs/remotes/origin/%s",
c.Cmd.Quote(branchName),
self.cmd.Quote(branchName),
),
).
DontLog().
@ -54,8 +65,3 @@ func (c *GitCommand) CheckRemoteBranchExists(branchName string) bool {
return err == nil
}
// GetRemoteURL returns current repo remote url
func (c *GitCommand) GetRemoteURL() string {
return c.GitConfig.Get("remote.origin.url")
}

View File

@ -5,59 +5,91 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
)
// StashDo modify stash
func (c *GitCommand) StashDo(index int, method string) error {
return c.Cmd.New(fmt.Sprintf("git stash %s stash@{%d}", method, index)).Run()
type StashCommands struct {
*common.Common
cmd oscommands.ICmdObjBuilder
fileLoader *loaders.FileLoader
osCommand *oscommands.OSCommand
workingTree *WorkingTreeCommands
}
// StashSave save stash
func NewStashCommands(
common *common.Common,
cmd oscommands.ICmdObjBuilder,
osCommand *oscommands.OSCommand,
fileLoader *loaders.FileLoader,
workingTree *WorkingTreeCommands,
) *StashCommands {
return &StashCommands{
Common: common,
cmd: cmd,
fileLoader: fileLoader,
osCommand: osCommand,
workingTree: workingTree,
}
}
func (self *StashCommands) Drop(index int) error {
return self.cmd.New(fmt.Sprintf("git stash drop stash@{%d}", index)).Run()
}
func (self *StashCommands) Pop(index int) error {
return self.cmd.New(fmt.Sprintf("git stash pop stash@{%d}", index)).Run()
}
func (self *StashCommands) Apply(index int) error {
return self.cmd.New(fmt.Sprintf("git stash apply stash@{%d}", index)).Run()
}
// Save save stash
// TODO: before calling this, check if there is anything to save
func (c *GitCommand) StashSave(message string) error {
return c.Cmd.New("git stash save " + c.OSCommand.Quote(message)).Run()
func (self *StashCommands) Save(message string) error {
return self.cmd.New("git stash save " + self.cmd.Quote(message)).Run()
}
func (c *GitCommand) ShowStashEntryCmdObj(index int) oscommands.ICmdObj {
cmdStr := fmt.Sprintf("git stash show -p --stat --color=%s --unified=%d stash@{%d}", c.colorArg(), c.UserConfig.Git.DiffContextSize, index)
func (self *StashCommands) ShowStashEntryCmdObj(index int) oscommands.ICmdObj {
cmdStr := fmt.Sprintf("git stash show -p --stat --color=%s --unified=%d stash@{%d}", self.UserConfig.Git.Paging.ColorArg, self.UserConfig.Git.DiffContextSize, index)
return c.Cmd.New(cmdStr).DontLog()
return self.cmd.New(cmdStr).DontLog()
}
// StashSaveStagedChanges stashes only the currently staged changes. This takes a few steps
// SaveStagedChanges stashes only the currently staged changes. This takes a few steps
// shoutouts to Joe on https://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible
func (c *GitCommand) StashSaveStagedChanges(message string) error {
func (self *StashCommands) SaveStagedChanges(message string) error {
// wrap in 'writing', which uses a mutex
if err := c.Cmd.New("git stash --keep-index").Run(); err != nil {
if err := self.cmd.New("git stash --keep-index").Run(); err != nil {
return err
}
if err := c.StashSave(message); err != nil {
if err := self.Save(message); err != nil {
return err
}
if err := c.Cmd.New("git stash apply stash@{1}").Run(); err != nil {
if err := self.cmd.New("git stash apply stash@{1}").Run(); err != nil {
return err
}
if err := c.OSCommand.PipeCommands("git stash show -p", "git apply -R"); err != nil {
if err := self.osCommand.PipeCommands("git stash show -p", "git apply -R"); err != nil {
return err
}
if err := c.Cmd.New("git stash drop stash@{1}").Run(); err != nil {
if err := self.cmd.New("git stash drop stash@{1}").Run(); err != nil {
return err
}
// if you had staged an untracked file, that will now appear as 'AD' in git status
// meaning it's deleted in your working tree but added in your index. Given that it's
// now safely stashed, we need to remove it.
files := loaders.
NewFileLoader(c.Common, c.Cmd, c.GitConfig).
files := self.fileLoader.
GetStatusFiles(loaders.GetStatusFileOptions{})
for _, file := range files {
if file.ShortStatus == "AD" {
if err := c.UnStageFile(file.Names(), false); err != nil {
if err := self.workingTree.UnStageFile(file.Names(), false); err != nil {
return err
}
}

View File

@ -7,12 +7,30 @@ import (
"github.com/stretchr/testify/assert"
)
func TestGitCommandStashDo(t *testing.T) {
func TestGitCommandStashDrop(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"stash", "drop", "stash@{1}"}, "", nil)
gitCmd := NewDummyGitCommandWithRunner(runner)
assert.NoError(t, gitCmd.StashDo(1, "drop"))
assert.NoError(t, gitCmd.Stash.Drop(1))
runner.CheckForMissingCalls()
}
func TestGitCommandStashApply(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"stash", "apply", "stash@{1}"}, "", nil)
gitCmd := NewDummyGitCommandWithRunner(runner)
assert.NoError(t, gitCmd.Stash.Apply(1))
runner.CheckForMissingCalls()
}
func TestGitCommandStashPop(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"stash", "pop", "stash@{1}"}, "", nil)
gitCmd := NewDummyGitCommandWithRunner(runner)
assert.NoError(t, gitCmd.Stash.Pop(1))
runner.CheckForMissingCalls()
}
@ -21,7 +39,7 @@ func TestGitCommandStashSave(t *testing.T) {
ExpectGitArgs([]string{"stash", "save", "A stash message"}, "", nil)
gitCmd := NewDummyGitCommandWithRunner(runner)
assert.NoError(t, gitCmd.StashSave("A stash message"))
assert.NoError(t, gitCmd.Stash.Save("A stash message"))
runner.CheckForMissingCalls()
}
@ -52,7 +70,7 @@ func TestGitCommandShowStashEntryCmdObj(t *testing.T) {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommand()
gitCmd.UserConfig.Git.DiffContextSize = s.contextSize
cmdStr := gitCmd.ShowStashEntryCmdObj(s.index).ToString()
cmdStr := gitCmd.Stash.ShowStashEntryCmdObj(s.index).ToString()
assert.Equal(t, s.expected, cmdStr)
})
}

View File

@ -4,20 +4,43 @@ import (
"path/filepath"
gogit "github.com/jesseduffield/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/common"
)
type StatusCommands struct {
*common.Common
osCommand *oscommands.OSCommand
repo *gogit.Repository
dotGitDir string
}
func NewStatusCommands(
common *common.Common,
osCommand *oscommands.OSCommand,
repo *gogit.Repository,
dotGitDir string,
) *StatusCommands {
return &StatusCommands{
Common: common,
osCommand: osCommand,
repo: repo,
dotGitDir: dotGitDir,
}
}
// RebaseMode returns "" for non-rebase mode, "normal" for normal rebase
// and "interactive" for interactive rebase
func (c *GitCommand) RebaseMode() (enums.RebaseMode, error) {
exists, err := c.OSCommand.FileExists(filepath.Join(c.DotGitDir, "rebase-apply"))
func (self *StatusCommands) RebaseMode() (enums.RebaseMode, error) {
exists, err := self.osCommand.FileExists(filepath.Join(self.dotGitDir, "rebase-apply"))
if err != nil {
return enums.REBASE_MODE_NONE, err
}
if exists {
return enums.REBASE_MODE_NORMAL, nil
}
exists, err = c.OSCommand.FileExists(filepath.Join(c.DotGitDir, "rebase-merge"))
exists, err = self.osCommand.FileExists(filepath.Join(self.dotGitDir, "rebase-merge"))
if exists {
return enums.REBASE_MODE_INTERACTIVE, err
} else {
@ -25,12 +48,12 @@ func (c *GitCommand) RebaseMode() (enums.RebaseMode, error) {
}
}
func (c *GitCommand) WorkingTreeState() enums.RebaseMode {
rebaseMode, _ := c.RebaseMode()
func (self *StatusCommands) WorkingTreeState() enums.RebaseMode {
rebaseMode, _ := self.RebaseMode()
if rebaseMode != enums.REBASE_MODE_NONE {
return enums.REBASE_MODE_REBASING
}
merging, _ := c.IsInMergeState()
merging, _ := self.IsInMergeState()
if merging {
return enums.REBASE_MODE_MERGING
}
@ -38,12 +61,12 @@ func (c *GitCommand) WorkingTreeState() enums.RebaseMode {
}
// IsInMergeState states whether we are still mid-merge
func (c *GitCommand) IsInMergeState() (bool, error) {
return c.OSCommand.FileExists(filepath.Join(c.DotGitDir, "MERGE_HEAD"))
func (self *StatusCommands) IsInMergeState() (bool, error) {
return self.osCommand.FileExists(filepath.Join(self.dotGitDir, "MERGE_HEAD"))
}
func (c *GitCommand) IsBareRepo() bool {
func (self *StatusCommands) IsBareRepo() bool {
// note: could use `git rev-parse --is-bare-repository` if we wanna drop go-git
_, err := c.Repo.Worktree()
_, err := self.repo.Worktree()
return err == gogit.ErrIsBareRepository
}

View File

@ -10,6 +10,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
)
// .gitmodules looks like this:
@ -17,7 +18,22 @@ import (
// path = blah/mysubmodule
// url = git@github.com:subbo.git
func (c *GitCommand) GetSubmoduleConfigs() ([]*models.SubmoduleConfig, error) {
type SubmoduleCommands struct {
*common.Common
cmd oscommands.ICmdObjBuilder
dotGitDir string
}
func NewSubmoduleCommands(common *common.Common, cmd oscommands.ICmdObjBuilder, dotGitDir string) *SubmoduleCommands {
return &SubmoduleCommands{
Common: common,
cmd: cmd,
dotGitDir: dotGitDir,
}
}
func (self *SubmoduleCommands) GetConfigs() ([]*models.SubmoduleConfig, error) {
file, err := os.Open(".gitmodules")
if err != nil {
if os.IsNotExist(err) {
@ -63,36 +79,36 @@ func (c *GitCommand) GetSubmoduleConfigs() ([]*models.SubmoduleConfig, error) {
return configs, nil
}
func (c *GitCommand) SubmoduleStash(submodule *models.SubmoduleConfig) error {
func (self *SubmoduleCommands) Stash(submodule *models.SubmoduleConfig) error {
// if the path does not exist then it hasn't yet been initialized so we'll swallow the error
// because the intention here is to have no dirty worktree state
if _, err := os.Stat(submodule.Path); os.IsNotExist(err) {
c.Log.Infof("submodule path %s does not exist, returning", submodule.Path)
self.Log.Infof("submodule path %s does not exist, returning", submodule.Path)
return nil
}
return c.Cmd.New("git -C " + c.Cmd.Quote(submodule.Path) + " stash --include-untracked").Run()
return self.cmd.New("git -C " + self.cmd.Quote(submodule.Path) + " stash --include-untracked").Run()
}
func (c *GitCommand) SubmoduleReset(submodule *models.SubmoduleConfig) error {
return c.Cmd.New("git submodule update --init --force -- " + c.Cmd.Quote(submodule.Path)).Run()
func (self *SubmoduleCommands) Reset(submodule *models.SubmoduleConfig) error {
return self.cmd.New("git submodule update --init --force -- " + self.cmd.Quote(submodule.Path)).Run()
}
func (c *GitCommand) SubmoduleUpdateAll() error {
func (self *SubmoduleCommands) UpdateAll() error {
// not doing an --init here because the user probably doesn't want that
return c.Cmd.New("git submodule update --force").Run()
return self.cmd.New("git submodule update --force").Run()
}
func (c *GitCommand) SubmoduleDelete(submodule *models.SubmoduleConfig) error {
func (self *SubmoduleCommands) Delete(submodule *models.SubmoduleConfig) error {
// based on https://gist.github.com/myusuf3/7f645819ded92bda6677
if err := c.Cmd.New("git submodule deinit --force -- " + c.Cmd.Quote(submodule.Path)).Run(); err != nil {
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 := c.Cmd.New("git config --file .gitmodules --remove-section submodule." + c.Cmd.Quote(submodule.Name)).Run(); err != nil {
if err := self.cmd.New("git config --file .gitmodules --remove-section submodule." + self.cmd.Quote(submodule.Name)).Run(); err != nil {
return err
}
if err := c.Cmd.New("git config --remove-section submodule." + c.Cmd.Quote(submodule.Name)).Run(); err != nil {
if err := self.cmd.New("git config --remove-section submodule." + self.cmd.Quote(submodule.Name)).Run(); err != nil {
return err
}
@ -102,69 +118,69 @@ func (c *GitCommand) SubmoduleDelete(submodule *models.SubmoduleConfig) error {
}
}
if err := c.Cmd.New("git rm --force -r " + submodule.Path).Run(); err != nil {
if err := self.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)
self.Log.Error(err)
}
return os.RemoveAll(filepath.Join(c.DotGitDir, "modules", submodule.Path))
return os.RemoveAll(filepath.Join(self.dotGitDir, "modules", submodule.Path))
}
func (c *GitCommand) SubmoduleAdd(name string, path string, url string) error {
return c.Cmd.
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 ",
c.Cmd.Quote(name),
c.Cmd.Quote(url),
c.Cmd.Quote(path),
self.cmd.Quote(name),
self.cmd.Quote(url),
self.cmd.Quote(path),
)).
Run()
}
func (c *GitCommand) SubmoduleUpdateUrl(name string, path string, newUrl string) error {
func (self *SubmoduleCommands) UpdateUrl(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.Cmd.New("git config --file .gitmodules submodule." + c.Cmd.Quote(name) + ".url " + c.Cmd.Quote(newUrl)).Run(); err != nil {
if err := self.cmd.New("git config --file .gitmodules submodule." + self.cmd.Quote(name) + ".url " + self.cmd.Quote(newUrl)).Run(); err != nil {
return err
}
if err := c.Cmd.New("git submodule sync -- " + c.Cmd.Quote(path)).Run(); err != nil {
if err := self.cmd.New("git submodule sync -- " + self.cmd.Quote(path)).Run(); err != nil {
return err
}
return nil
}
func (c *GitCommand) SubmoduleInit(path string) error {
return c.Cmd.New("git submodule init -- " + c.Cmd.Quote(path)).Run()
func (self *SubmoduleCommands) Init(path string) error {
return self.cmd.New("git submodule init -- " + self.cmd.Quote(path)).Run()
}
func (c *GitCommand) SubmoduleUpdate(path string) error {
return c.Cmd.New("git submodule update --init -- " + c.Cmd.Quote(path)).Run()
func (self *SubmoduleCommands) Update(path string) error {
return self.cmd.New("git submodule update --init -- " + self.cmd.Quote(path)).Run()
}
func (c *GitCommand) SubmoduleBulkInitCmdObj() oscommands.ICmdObj {
return c.Cmd.New("git submodule init")
func (self *SubmoduleCommands) BulkInitCmdObj() oscommands.ICmdObj {
return self.cmd.New("git submodule init")
}
func (c *GitCommand) SubmoduleBulkUpdateCmdObj() oscommands.ICmdObj {
return c.Cmd.New("git submodule update")
func (self *SubmoduleCommands) BulkUpdateCmdObj() oscommands.ICmdObj {
return self.cmd.New("git submodule update")
}
func (c *GitCommand) SubmoduleForceBulkUpdateCmdObj() oscommands.ICmdObj {
return c.Cmd.New("git submodule update --force")
func (self *SubmoduleCommands) ForceBulkUpdateCmdObj() oscommands.ICmdObj {
return self.cmd.New("git submodule update --force")
}
func (c *GitCommand) SubmoduleBulkDeinitCmdObj() oscommands.ICmdObj {
return c.Cmd.New("git submodule deinit --all --force")
func (self *SubmoduleCommands) BulkDeinitCmdObj() oscommands.ICmdObj {
return self.cmd.New("git submodule deinit --all --force")
}
func (c *GitCommand) ResetSubmodules(submodules []*models.SubmoduleConfig) error {
func (self *SubmoduleCommands) ResetSubmodules(submodules []*models.SubmoduleConfig) error {
for _, submodule := range submodules {
if err := c.SubmoduleStash(submodule); err != nil {
if err := self.Stash(submodule); err != nil {
return err
}
}
return c.SubmoduleUpdateAll()
return self.UpdateAll()
}

View File

@ -5,8 +5,25 @@ import (
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
)
type SyncCommands struct {
*common.Common
cmd oscommands.ICmdObjBuilder
}
func NewSyncCommands(
common *common.Common,
cmd oscommands.ICmdObjBuilder,
) *SyncCommands {
return &SyncCommands{
Common: common,
cmd: cmd,
}
}
// Push pushes to a branch
type PushOpts struct {
Force bool
@ -15,7 +32,7 @@ type PushOpts struct {
SetUpstream bool
}
func (c *GitCommand) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error) {
func (self *SyncCommands) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error) {
cmdStr := "git push"
if opts.Force {
@ -27,71 +44,62 @@ func (c *GitCommand) PushCmdObj(opts PushOpts) (oscommands.ICmdObj, error) {
}
if opts.UpstreamRemote != "" {
cmdStr += " " + c.OSCommand.Quote(opts.UpstreamRemote)
cmdStr += " " + self.cmd.Quote(opts.UpstreamRemote)
}
if opts.UpstreamBranch != "" {
if opts.UpstreamRemote == "" {
return nil, errors.New(c.Tr.MustSpecifyOriginError)
return nil, errors.New(self.Tr.MustSpecifyOriginError)
}
cmdStr += " " + c.OSCommand.Quote(opts.UpstreamBranch)
cmdStr += " " + self.cmd.Quote(opts.UpstreamBranch)
}
cmdObj := c.Cmd.New(cmdStr)
cmdObj := self.cmd.New(cmdStr).PromptOnCredentialRequest()
return cmdObj, nil
}
func (c *GitCommand) Push(opts PushOpts, promptUserForCredential func(string) string) error {
cmdObj, err := c.PushCmdObj(opts)
func (self *SyncCommands) Push(opts PushOpts) error {
cmdObj, err := self.PushCmdObj(opts)
if err != nil {
return err
}
return c.DetectUnamePass(cmdObj, promptUserForCredential)
return cmdObj.Run()
}
type FetchOptions struct {
PromptUserForCredential func(string) string
Background bool
RemoteName string
BranchName string
}
// Fetch fetch git repo
func (c *GitCommand) Fetch(opts FetchOptions) error {
func (self *SyncCommands) Fetch(opts FetchOptions) error {
cmdStr := "git fetch"
if opts.RemoteName != "" {
cmdStr = fmt.Sprintf("%s %s", cmdStr, c.OSCommand.Quote(opts.RemoteName))
cmdStr = fmt.Sprintf("%s %s", cmdStr, self.cmd.Quote(opts.RemoteName))
}
if opts.BranchName != "" {
cmdStr = fmt.Sprintf("%s %s", cmdStr, c.OSCommand.Quote(opts.BranchName))
cmdStr = fmt.Sprintf("%s %s", cmdStr, self.cmd.Quote(opts.BranchName))
}
cmdObj := c.Cmd.New(cmdStr)
userInitiated := opts.PromptUserForCredential != nil
if !userInitiated {
cmdObj.DontLog()
cmdObj := self.cmd.New(cmdStr)
if opts.Background {
cmdObj.DontLog().FailOnCredentialRequest()
} else {
cmdObj.PromptOnCredentialRequest()
}
return c.DetectUnamePass(cmdObj, func(question string) string {
if userInitiated {
return opts.PromptUserForCredential(question)
}
return "\n"
})
return cmdObj.Run()
}
type PullOptions struct {
PromptUserForCredential func(string) string
RemoteName string
BranchName string
FastForwardOnly bool
}
func (c *GitCommand) Pull(opts PullOptions) error {
if opts.PromptUserForCredential == nil {
return errors.New("PromptUserForCredential is required")
}
func (self *SyncCommands) Pull(opts PullOptions) error {
cmdStr := "git pull --no-edit"
if opts.FastForwardOnly {
@ -99,26 +107,23 @@ func (c *GitCommand) Pull(opts PullOptions) error {
}
if opts.RemoteName != "" {
cmdStr = fmt.Sprintf("%s %s", cmdStr, c.OSCommand.Quote(opts.RemoteName))
cmdStr = fmt.Sprintf("%s %s", cmdStr, self.cmd.Quote(opts.RemoteName))
}
if opts.BranchName != "" {
cmdStr = fmt.Sprintf("%s %s", cmdStr, c.OSCommand.Quote(opts.BranchName))
cmdStr = fmt.Sprintf("%s %s", cmdStr, self.cmd.Quote(opts.BranchName))
}
// setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user
// has 'pull.rebase = interactive' configured.
cmdObj := c.Cmd.New(cmdStr).AddEnvVars("GIT_SEQUENCE_EDITOR=:")
return c.DetectUnamePass(cmdObj, opts.PromptUserForCredential)
return self.cmd.New(cmdStr).AddEnvVars("GIT_SEQUENCE_EDITOR=:").PromptOnCredentialRequest().Run()
}
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.Cmd.New(cmdStr)
return c.DetectUnamePass(cmdObj, promptUserForCredential)
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))
return self.cmd.New(cmdStr).PromptOnCredentialRequest().Run()
}
func (c *GitCommand) FetchRemote(remoteName string, promptUserForCredential func(string) string) error {
cmdStr := fmt.Sprintf("git fetch %s", c.OSCommand.Quote(remoteName))
cmdObj := c.Cmd.New(cmdStr)
return c.DetectUnamePass(cmdObj, promptUserForCredential)
func (self *SyncCommands) FetchRemote(remoteName string) error {
cmdStr := fmt.Sprintf("git fetch %s", self.cmd.Quote(remoteName))
return self.cmd.New(cmdStr).PromptOnCredentialRequest().Run()
}

View File

@ -87,7 +87,7 @@ func TestGitCommandPush(t *testing.T) {
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(oscommands.NewFakeRunner(t))
s.test(gitCmd.PushCmdObj(s.opts))
s.test(gitCmd.Sync.PushCmdObj(s.opts))
})
}
}

View File

@ -2,22 +2,36 @@ package commands
import (
"fmt"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
)
func (c *GitCommand) CreateLightweightTag(tagName string, commitSha string) error {
return c.Cmd.New(fmt.Sprintf("git tag -- %s %s", c.OSCommand.Quote(tagName), commitSha)).Run()
type TagCommands struct {
*common.Common
cmd oscommands.ICmdObjBuilder
}
func (c *GitCommand) CreateAnnotatedTag(tagName, commitSha, msg string) error {
return c.Cmd.New(fmt.Sprintf("git tag %s %s -m %s", tagName, commitSha, c.OSCommand.Quote(msg))).Run()
func NewTagCommands(common *common.Common, cmd oscommands.ICmdObjBuilder) *TagCommands {
return &TagCommands{
Common: common,
cmd: cmd,
}
}
func (c *GitCommand) DeleteTag(tagName string) error {
return c.Cmd.New(fmt.Sprintf("git tag -d %s", c.OSCommand.Quote(tagName))).Run()
func (self *TagCommands) CreateLightweight(tagName string, commitSha string) error {
return self.cmd.New(fmt.Sprintf("git tag -- %s %s", self.cmd.Quote(tagName), commitSha)).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.Cmd.New(cmdStr)
return c.DetectUnamePass(cmdObj, promptUserForCredential)
func (self *TagCommands) CreateAnnotated(tagName, commitSha, msg string) error {
return self.cmd.New(fmt.Sprintf("git tag %s %s -m %s", tagName, commitSha, self.cmd.Quote(msg))).Run()
}
func (self *TagCommands) Delete(tagName string) error {
return self.cmd.New(fmt.Sprintf("git tag -d %s", self.cmd.Quote(tagName))).Run()
}
func (self *TagCommands) Push(remoteName string, tagName string) error {
return self.cmd.New(fmt.Sprintf("git push %s %s", self.cmd.Quote(remoteName), self.cmd.Quote(tagName))).PromptOnCredentialRequest().Run()
}

View File

@ -0,0 +1,347 @@
package commands
import (
"fmt"
"os"
"path/filepath"
"time"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
"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/filetree"
"github.com/jesseduffield/lazygit/pkg/utils"
)
type WorkingTreeCommands struct {
*common.Common
cmd oscommands.ICmdObjBuilder
os WorkingTreeOSCommand
submodule *SubmoduleCommands
fileLoader *loaders.FileLoader
}
type WorkingTreeOSCommand interface {
RemoveFile(string) error
CreateFileWithContent(string, string) error
AppendLineToFile(string, string) error
}
func NewWorkingTreeCommands(
common *common.Common,
cmd oscommands.ICmdObjBuilder,
submodulesCommands *SubmoduleCommands,
osCommand WorkingTreeOSCommand,
fileLoader *loaders.FileLoader,
) *WorkingTreeCommands {
return &WorkingTreeCommands{
Common: common,
cmd: cmd,
os: osCommand,
submodule: submodulesCommands,
fileLoader: fileLoader,
}
}
func (self *WorkingTreeCommands) OpenMergeToolCmdObj() oscommands.ICmdObj {
return self.cmd.New("git mergetool")
}
func (self *WorkingTreeCommands) OpenMergeTool() error {
return self.OpenMergeToolCmdObj().Run()
}
// StageFile stages a file
func (self *WorkingTreeCommands) StageFile(fileName string) error {
return self.cmd.New("git add -- " + self.cmd.Quote(fileName)).Run()
}
// StageAll stages all files
func (self *WorkingTreeCommands) StageAll() error {
return self.cmd.New("git add -A").Run()
}
// UnstageAll unstages all files
func (self *WorkingTreeCommands) UnstageAll() error {
return self.cmd.New("git reset").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()
if err != nil {
return err
}
}
return nil
}
func (c *WorkingTreeCommands) BeforeAndAfterFileForRename(file *models.File) (*models.File, *models.File, error) {
if !file.IsRename() {
return nil, nil, errors.New("Expected renamed file")
}
// we've got a file that represents a rename from one file to another. Here we will refetch
// all files, passing the --no-renames flag and then recursively call the function
// again for the before file and after file.
filesWithoutRenames := c.fileLoader.GetStatusFiles(loaders.GetStatusFileOptions{NoRenames: true})
var beforeFile *models.File
var afterFile *models.File
for _, f := range filesWithoutRenames {
if f.Name == file.PreviousName {
beforeFile = f
}
if f.Name == file.Name {
afterFile = f
}
}
if beforeFile == nil || afterFile == nil {
return nil, nil, errors.New("Could not find deleted file or new file for file rename")
}
if beforeFile.IsRename() || afterFile.IsRename() {
// probably won't happen but we want to ensure we don't get an infinite loop
return nil, nil, errors.New("Nested rename found")
}
return beforeFile, afterFile, nil
}
// DiscardAllFileChanges directly
func (c *WorkingTreeCommands) DiscardAllFileChanges(file *models.File) error {
if file.IsRename() {
beforeFile, afterFile, err := c.BeforeAndAfterFileForRename(file)
if err != nil {
return err
}
if err := c.DiscardAllFileChanges(beforeFile); err != nil {
return err
}
if err := c.DiscardAllFileChanges(afterFile); err != nil {
return err
}
return nil
}
quotedFileName := c.cmd.Quote(file.Name)
if file.ShortStatus == "AA" {
if err := c.cmd.New("git checkout --ours -- " + quotedFileName).Run(); err != nil {
return err
}
if err := c.cmd.New("git add -- " + quotedFileName).Run(); err != nil {
return err
}
return nil
}
if file.ShortStatus == "DU" {
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.cmd.New("git reset -- " + quotedFileName).Run(); err != nil {
return err
}
}
if file.ShortStatus == "DD" || file.ShortStatus == "AU" {
return nil
}
if file.Added {
return c.os.RemoveFile(file.Name)
}
return c.DiscardUnstagedFileChanges(file)
}
func (c *WorkingTreeCommands) DiscardAllDirChanges(node *filetree.FileNode) error {
// this could be more efficient but we would need to handle all the edge cases
return node.ForEachFile(c.DiscardAllFileChanges)
}
func (c *WorkingTreeCommands) DiscardUnstagedDirChanges(node *filetree.FileNode) error {
if err := c.RemoveUntrackedDirFiles(node); err != nil {
return err
}
quotedPath := c.cmd.Quote(node.GetPath())
if err := c.cmd.New("git checkout -- " + quotedPath).Run(); err != nil {
return err
}
return nil
}
func (c *WorkingTreeCommands) RemoveUntrackedDirFiles(node *filetree.FileNode) error {
untrackedFilePaths := node.GetPathsMatching(
func(n *filetree.FileNode) bool { return n.File != nil && !n.File.GetIsTracked() },
)
for _, path := range untrackedFilePaths {
err := os.Remove(path)
if err != nil {
return err
}
}
return nil
}
// DiscardUnstagedFileChanges directly
func (c *WorkingTreeCommands) DiscardUnstagedFileChanges(file *models.File) error {
quotedFileName := c.cmd.Quote(file.Name)
return c.cmd.New("git checkout -- " + quotedFileName).Run()
}
// Ignore adds a file to the gitignore for the repo
func (c *WorkingTreeCommands) Ignore(filename string) error {
return c.os.AppendLineToFile(".gitignore", filename)
}
// WorktreeFileDiff returns the diff of a file
func (c *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool, cached bool, ignoreWhitespace bool) string {
// for now we assume an error means the file was deleted
s, _ := c.WorktreeFileDiffCmdObj(file, plain, cached, ignoreWhitespace).RunWithOutput()
return s
}
func (c *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool, ignoreWhitespace bool) oscommands.ICmdObj {
cachedArg := ""
trackedArg := "--"
colorArg := c.UserConfig.Git.Paging.ColorArg
quotedPath := c.cmd.Quote(node.GetPath())
ignoreWhitespaceArg := ""
contextSize := c.UserConfig.Git.DiffContextSize
if cached {
cachedArg = "--cached"
}
if !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached {
trackedArg = "--no-index -- /dev/null"
}
if plain {
colorArg = "never"
}
if ignoreWhitespace {
ignoreWhitespaceArg = "--ignore-all-space"
}
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.cmd.New(cmdStr).DontLog()
}
func (c *WorkingTreeCommands) ApplyPatch(patch string, flags ...string) error {
filepath := filepath.Join(oscommands.GetTempDir(), utils.GetCurrentRepoName(), time.Now().Format("Jan _2 15.04.05.000000000")+".patch")
c.Log.Infof("saving temporary patch to %s", filepath)
if err := c.os.CreateFileWithContent(filepath, patch); err != nil {
return err
}
flagStr := ""
for _, flag := range flags {
flagStr += " --" + flag
}
return c.cmd.New(fmt.Sprintf("git apply%s %s", flagStr, c.cmd.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 *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bool, fileName string, plain bool) (string, error) {
return c.ShowFileDiffCmdObj(from, to, reverse, fileName, plain).RunWithOutput()
}
func (c *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool) oscommands.ICmdObj {
colorArg := c.UserConfig.Git.Paging.ColorArg
contextSize := c.UserConfig.Git.DiffContextSize
if plain {
colorArg = "never"
}
reverseFlag := ""
if reverse {
reverseFlag = " -R "
}
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.cmd.Quote(fileName)),
).
DontLog()
}
// CheckoutFile checks out the file for the given commit
func (c *WorkingTreeCommands) CheckoutFile(commitSha, fileName string) error {
return c.cmd.New(fmt.Sprintf("git checkout %s -- %s", commitSha, c.cmd.Quote(fileName))).Run()
}
// DiscardAnyUnstagedFileChanges discards any unstages file changes via `git checkout -- .`
func (c *WorkingTreeCommands) DiscardAnyUnstagedFileChanges() error {
return c.cmd.New("git checkout -- .").Run()
}
// RemoveTrackedFiles will delete the given file(s) even if they are currently tracked
func (c *WorkingTreeCommands) RemoveTrackedFiles(name string) error {
return c.cmd.New("git rm -r --cached -- " + c.cmd.Quote(name)).Run()
}
// RemoveUntrackedFiles runs `git clean -fd`
func (c *WorkingTreeCommands) RemoveUntrackedFiles() error {
return c.cmd.New("git clean -fd").Run()
}
// ResetAndClean removes all unstaged changes and removes all untracked files
func (c *WorkingTreeCommands) ResetAndClean() error {
submoduleConfigs, err := c.submodule.GetConfigs()
if err != nil {
return err
}
if len(submoduleConfigs) > 0 {
if err := c.submodule.ResetSubmodules(submoduleConfigs); err != nil {
return err
}
}
if err := c.ResetHard("HEAD"); err != nil {
return err
}
return c.RemoveUntrackedFiles()
}
// ResetHardHead runs `git reset --hard`
func (self *WorkingTreeCommands) ResetHard(ref string) error {
return self.cmd.New("git reset --hard " + self.cmd.Quote(ref)).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()
}
func (self *WorkingTreeCommands) ResetMixed(ref string) error {
return self.cmd.New("git reset --mixed " + self.cmd.Quote(ref)).Run()
}

View File

@ -0,0 +1,558 @@
package commands
import (
"fmt"
"io/ioutil"
"regexp"
"testing"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/stretchr/testify/assert"
)
func TestGitCommandStageFile(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
ExpectArgs([]string{"git", "add", "--", "test.txt"}, "", nil)
gitCmd := NewDummyGitCommandWithRunner(runner)
assert.NoError(t, gitCmd.WorkingTree.StageFile("test.txt"))
runner.CheckForMissingCalls()
}
func TestGitCommandUnstageFile(t *testing.T) {
type scenario struct {
testName string
reset bool
runner *oscommands.FakeCmdObjRunner
test func(error)
}
scenarios := []scenario{
{
testName: "Remove an untracked file from staging",
reset: false,
runner: oscommands.NewFakeRunner(t).
ExpectArgs([]string{"git", "rm", "--cached", "--force", "--", "test.txt"}, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
},
{
testName: "Remove a tracked file from staging",
reset: true,
runner: oscommands.NewFakeRunner(t).
ExpectArgs([]string{"git", "reset", "HEAD", "--", "test.txt"}, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
s.test(gitCmd.WorkingTree.UnStageFile([]string{"test.txt"}, s.reset))
})
}
}
// these tests don't cover everything, in part because we already have an integration
// test which does cover everything. I don't want to unnecessarily assert on the 'how'
// when the 'what' is what matters
func TestGitCommandDiscardAllFileChanges(t *testing.T) {
type scenario struct {
testName string
file *models.File
removeFile func(string) error
runner *oscommands.FakeCmdObjRunner
expectedError string
}
scenarios := []scenario{
{
testName: "An error occurred when resetting",
file: &models.File{
Name: "test",
HasStagedChanges: true,
},
removeFile: func(string) error { return nil },
runner: oscommands.NewFakeRunner(t).
ExpectArgs([]string{"git", "reset", "--", "test"}, "", errors.New("error")),
expectedError: "error",
},
{
testName: "An error occurred when removing file",
file: &models.File{
Name: "test",
Tracked: false,
Added: true,
},
removeFile: func(string) error {
return fmt.Errorf("an error occurred when removing file")
},
runner: oscommands.NewFakeRunner(t),
expectedError: "an error occurred when removing file",
},
{
testName: "An error occurred with checkout",
file: &models.File{
Name: "test",
Tracked: true,
HasStagedChanges: false,
},
removeFile: func(string) error { return nil },
runner: oscommands.NewFakeRunner(t).
ExpectArgs([]string{"git", "checkout", "--", "test"}, "", errors.New("error")),
expectedError: "error",
},
{
testName: "Checkout only",
file: &models.File{
Name: "test",
Tracked: true,
HasStagedChanges: false,
},
removeFile: func(string) error { return nil },
runner: oscommands.NewFakeRunner(t).
ExpectArgs([]string{"git", "checkout", "--", "test"}, "", nil),
expectedError: "",
},
{
testName: "Reset and checkout staged changes",
file: &models.File{
Name: "test",
Tracked: true,
HasStagedChanges: true,
},
removeFile: func(string) error { return nil },
runner: oscommands.NewFakeRunner(t).
ExpectArgs([]string{"git", "reset", "--", "test"}, "", nil).
ExpectArgs([]string{"git", "checkout", "--", "test"}, "", nil),
expectedError: "",
},
{
testName: "Reset and checkout merge conflicts",
file: &models.File{
Name: "test",
Tracked: true,
HasMergeConflicts: true,
},
removeFile: func(string) error { return nil },
runner: oscommands.NewFakeRunner(t).
ExpectArgs([]string{"git", "reset", "--", "test"}, "", nil).
ExpectArgs([]string{"git", "checkout", "--", "test"}, "", nil),
expectedError: "",
},
{
testName: "Reset and remove",
file: &models.File{
Name: "test",
Tracked: false,
Added: true,
HasStagedChanges: true,
},
removeFile: func(filename string) error {
assert.Equal(t, "test", filename)
return nil
},
runner: oscommands.NewFakeRunner(t).
ExpectArgs([]string{"git", "reset", "--", "test"}, "", nil),
expectedError: "",
},
{
testName: "Remove only",
file: &models.File{
Name: "test",
Tracked: false,
Added: true,
HasStagedChanges: false,
},
removeFile: func(filename string) error {
assert.Equal(t, "test", filename)
return nil
},
runner: oscommands.NewFakeRunner(t),
expectedError: "",
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
gitCmd.OSCommand.SetRemoveFile(s.removeFile)
err := gitCmd.WorkingTree.DiscardAllFileChanges(s.file)
if s.expectedError == "" {
assert.Nil(t, err)
} else {
assert.Equal(t, s.expectedError, err.Error())
}
s.runner.CheckForMissingCalls()
})
}
}
func TestGitCommandDiff(t *testing.T) {
type scenario struct {
testName string
file *models.File
plain bool
cached bool
ignoreWhitespace bool
contextSize int
runner *oscommands.FakeCmdObjRunner
}
const expectedResult = "pretend this is an actual git diff"
scenarios := []scenario{
{
testName: "Default case",
file: &models.File{
Name: "test.txt",
HasStagedChanges: false,
Tracked: true,
},
plain: false,
cached: false,
ignoreWhitespace: false,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectArgs([]string{"git", "diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "cached",
file: &models.File{
Name: "test.txt",
HasStagedChanges: false,
Tracked: true,
},
plain: false,
cached: true,
ignoreWhitespace: false,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectArgs([]string{"git", "diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--cached", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "plain",
file: &models.File{
Name: "test.txt",
HasStagedChanges: false,
Tracked: true,
},
plain: true,
cached: false,
ignoreWhitespace: false,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectArgs([]string{"git", "diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=never", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "File not tracked and file has no staged changes",
file: &models.File{
Name: "test.txt",
HasStagedChanges: false,
Tracked: false,
},
plain: false,
cached: false,
ignoreWhitespace: false,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectArgs([]string{"git", "diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--no-index", "--", "/dev/null", "test.txt"}, expectedResult, nil),
},
{
testName: "Default case (ignore whitespace)",
file: &models.File{
Name: "test.txt",
HasStagedChanges: false,
Tracked: true,
},
plain: false,
cached: false,
ignoreWhitespace: true,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectArgs([]string{"git", "diff", "--submodule", "--no-ext-diff", "--unified=3", "--color=always", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "Show diff with custom context size",
file: &models.File{
Name: "test.txt",
HasStagedChanges: false,
Tracked: true,
},
plain: false,
cached: false,
ignoreWhitespace: false,
contextSize: 17,
runner: oscommands.NewFakeRunner(t).
ExpectArgs([]string{"git", "diff", "--submodule", "--no-ext-diff", "--unified=17", "--color=always", "--", "test.txt"}, expectedResult, nil),
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
gitCmd.UserConfig.Git.DiffContextSize = s.contextSize
result := gitCmd.WorkingTree.WorktreeFileDiff(s.file, s.plain, s.cached, s.ignoreWhitespace)
assert.Equal(t, expectedResult, result)
s.runner.CheckForMissingCalls()
})
}
}
func TestGitCommandShowFileDiff(t *testing.T) {
type scenario struct {
testName string
from string
to string
reverse bool
plain bool
contextSize int
runner *oscommands.FakeCmdObjRunner
}
const expectedResult = "pretend this is an actual git diff"
scenarios := []scenario{
{
testName: "Default case",
from: "1234567890",
to: "0987654321",
reverse: false,
plain: false,
contextSize: 3,
runner: oscommands.NewFakeRunner(t).
ExpectArgs([]string{"git", "diff", "--submodule", "--no-ext-diff", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
},
{
testName: "Show diff with custom context size",
from: "1234567890",
to: "0987654321",
reverse: false,
plain: false,
contextSize: 123,
runner: oscommands.NewFakeRunner(t).
ExpectArgs([]string{"git", "diff", "--submodule", "--no-ext-diff", "--unified=123", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
gitCmd.UserConfig.Git.DiffContextSize = s.contextSize
result, err := gitCmd.WorkingTree.ShowFileDiff(s.from, s.to, s.reverse, "test.txt", s.plain)
assert.NoError(t, err)
assert.Equal(t, expectedResult, result)
s.runner.CheckForMissingCalls()
})
}
}
func TestGitCommandCheckoutFile(t *testing.T) {
type scenario struct {
testName string
commitSha string
fileName string
runner *oscommands.FakeCmdObjRunner
test func(error)
}
scenarios := []scenario{
{
testName: "typical case",
commitSha: "11af912",
fileName: "test999.txt",
runner: oscommands.NewFakeRunner(t).
Expect(`git checkout 11af912 -- "test999.txt"`, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
},
{
testName: "returns error if there is one",
commitSha: "11af912",
fileName: "test999.txt",
runner: oscommands.NewFakeRunner(t).
Expect(`git checkout 11af912 -- "test999.txt"`, "", errors.New("error")),
test: func(err error) {
assert.Error(t, err)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
s.test(gitCmd.WorkingTree.CheckoutFile(s.commitSha, s.fileName))
s.runner.CheckForMissingCalls()
})
}
}
func TestGitCommandApplyPatch(t *testing.T) {
type scenario struct {
testName string
runner *oscommands.FakeCmdObjRunner
test func(error)
}
expectFn := func(regexStr string, errToReturn error) func(cmdObj oscommands.ICmdObj) (string, error) {
return func(cmdObj oscommands.ICmdObj) (string, error) {
re := regexp.MustCompile(regexStr)
matches := re.FindStringSubmatch(cmdObj.ToString())
assert.Equal(t, 2, len(matches))
filename := matches[1]
content, err := ioutil.ReadFile(filename)
assert.NoError(t, err)
assert.Equal(t, "test", string(content))
return "", errToReturn
}
}
scenarios := []scenario{
{
testName: "valid case",
runner: oscommands.NewFakeRunner(t).
ExpectFunc(expectFn(`git apply --cached "(.*)"`, nil)),
test: func(err error) {
assert.NoError(t, err)
},
},
{
testName: "command returns error",
runner: oscommands.NewFakeRunner(t).
ExpectFunc(expectFn(`git apply --cached "(.*)"`, errors.New("error"))),
test: func(err error) {
assert.Error(t, err)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
s.test(gitCmd.WorkingTree.ApplyPatch("test", "cached"))
s.runner.CheckForMissingCalls()
})
}
}
func TestGitCommandDiscardUnstagedFileChanges(t *testing.T) {
type scenario struct {
testName string
file *models.File
runner *oscommands.FakeCmdObjRunner
test func(error)
}
scenarios := []scenario{
{
testName: "valid case",
file: &models.File{Name: "test.txt"},
runner: oscommands.NewFakeRunner(t).
Expect(`git checkout -- "test.txt"`, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
s.test(gitCmd.WorkingTree.DiscardUnstagedFileChanges(s.file))
s.runner.CheckForMissingCalls()
})
}
}
func TestGitCommandDiscardAnyUnstagedFileChanges(t *testing.T) {
type scenario struct {
testName string
runner *oscommands.FakeCmdObjRunner
test func(error)
}
scenarios := []scenario{
{
testName: "valid case",
runner: oscommands.NewFakeRunner(t).
Expect(`git checkout -- .`, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
s.test(gitCmd.WorkingTree.DiscardAnyUnstagedFileChanges())
s.runner.CheckForMissingCalls()
})
}
}
func TestGitCommandRemoveUntrackedFiles(t *testing.T) {
type scenario struct {
testName string
runner *oscommands.FakeCmdObjRunner
test func(error)
}
scenarios := []scenario{
{
testName: "valid case",
runner: oscommands.NewFakeRunner(t).
Expect(`git clean -fd`, "", nil),
test: func(err error) {
assert.NoError(t, err)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
s.test(gitCmd.WorkingTree.RemoveUntrackedFiles())
s.runner.CheckForMissingCalls()
})
}
}
func TestGitCommandResetHard(t *testing.T) {
type scenario struct {
testName string
ref string
runner *oscommands.FakeCmdObjRunner
test func(error)
}
scenarios := []scenario{
{
"valid case",
"HEAD",
oscommands.NewFakeRunner(t).
Expect(`git reset --hard "HEAD"`, "", nil),
func(err error) {
assert.NoError(t, err)
},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
gitCmd := NewDummyGitCommandWithRunner(s.runner)
s.test(gitCmd.WorkingTree.ResetHard(s.ref))
})
}
}

View File

@ -31,7 +31,7 @@ func (gui *Gui) branchesRenderToMain() error {
if branch == nil {
task = NewRenderStringTask(gui.Tr.NoBranchesThisRepo)
} else {
cmdObj := gui.GitCommand.GetBranchGraphCmdObj(branch.Name)
cmdObj := gui.GitCommand.Branch.GetGraphCmdObj(branch.Name)
task = NewRunPtyTask(cmdObj.GetCmd())
}
@ -103,7 +103,7 @@ func (gui *Gui) handleCopyPullRequestURLPress() error {
branch := gui.getSelectedBranch()
branchExistsOnRemote := gui.GitCommand.CheckRemoteBranchExists(branch.Name)
branchExistsOnRemote := gui.GitCommand.Remote.CheckRemoteBranchExists(branch.Name)
if !branchExistsOnRemote {
return gui.surfaceError(errors.New(gui.Tr.NoBranchOnRemote))
@ -146,7 +146,7 @@ func (gui *Gui) handleForceCheckout() error {
prompt: message,
handleConfirm: func() error {
gui.logAction(gui.Tr.Actions.ForceCheckoutBranch)
if err := gui.GitCommand.Checkout(branch.Name, commands.CheckoutOptions{Force: true}); err != nil {
if err := gui.GitCommand.Branch.Checkout(branch.Name, commands.CheckoutOptions{Force: true}); err != nil {
_ = gui.surfaceError(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
@ -176,7 +176,7 @@ func (gui *Gui) handleCheckoutRef(ref string, options handleCheckoutRefOptions)
}
return gui.WithWaitingStatus(waitingStatus, func() error {
if err := gui.GitCommand.Checkout(ref, cmdOptions); err != nil {
if err := gui.GitCommand.Branch.Checkout(ref, cmdOptions); err != nil {
// note, this will only work for english-language git commands. If we force git to use english, and the error isn't this one, then the user will receive an english command they may not understand. I'm not sure what the best solution to this is. Running the command once in english and a second time in the native language is one option
if options.onRefNotFound != nil && strings.Contains(err.Error(), "did not match any file(s) known to git") {
@ -190,15 +190,15 @@ func (gui *Gui) handleCheckoutRef(ref string, options handleCheckoutRefOptions)
title: gui.Tr.AutoStashTitle,
prompt: gui.Tr.AutoStashPrompt,
handleConfirm: func() error {
if err := gui.GitCommand.StashSave(gui.Tr.StashPrefix + ref); err != nil {
if err := gui.GitCommand.Stash.Save(gui.Tr.StashPrefix + ref); err != nil {
return gui.surfaceError(err)
}
if err := gui.GitCommand.Checkout(ref, cmdOptions); err != nil {
if err := gui.GitCommand.Branch.Checkout(ref, cmdOptions); err != nil {
return gui.surfaceError(err)
}
onSuccess()
if err := gui.GitCommand.StashDo(0, "pop"); err != nil {
if err := gui.GitCommand.Stash.Pop(0); err != nil {
if err := gui.refreshSidePanels(refreshOptions{mode: BLOCK_UI}); err != nil {
return err
}
@ -254,7 +254,7 @@ func (gui *Gui) createNewBranchWithName(newBranchName string) error {
return nil
}
if err := gui.GitCommand.NewBranch(newBranchName, branch.Name); err != nil {
if err := gui.GitCommand.Branch.New(newBranchName, branch.Name); err != nil {
return gui.surfaceError(err)
}
@ -298,7 +298,7 @@ func (gui *Gui) deleteNamedBranch(selectedBranch *models.Branch, force bool) err
prompt: message,
handleConfirm: func() error {
gui.logAction(gui.Tr.Actions.DeleteBranch)
if err := gui.GitCommand.DeleteBranch(selectedBranch.Name, force); err != nil {
if err := gui.GitCommand.Branch.Delete(selectedBranch.Name, force); err != nil {
errMessage := err.Error()
if !force && strings.Contains(errMessage, "git branch -D ") {
return gui.deleteNamedBranch(selectedBranch, true)
@ -315,7 +315,7 @@ func (gui *Gui) mergeBranchIntoCheckedOutBranch(branchName string) error {
return err
}
if gui.GitCommand.IsHeadDetached() {
if gui.GitCommand.Branch.IsHeadDetached() {
return gui.createErrorPanel("Cannot merge branch in detached head state. You might have checked out a commit directly or a remote branch, in which case you should checkout the local branch you want to be on")
}
checkedOutBranchName := gui.getCheckedOutBranch().Name
@ -335,7 +335,7 @@ func (gui *Gui) mergeBranchIntoCheckedOutBranch(branchName string) error {
prompt: prompt,
handleConfirm: func() error {
gui.logAction(gui.Tr.Actions.Merge)
err := gui.GitCommand.Merge(branchName, commands.MergeOpts{})
err := gui.GitCommand.Branch.Merge(branchName, commands.MergeOpts{})
return gui.handleGenericMergeCommandResult(err)
},
})
@ -377,7 +377,7 @@ func (gui *Gui) handleRebaseOntoBranch(selectedBranchName string) error {
prompt: prompt,
handleConfirm: func() error {
gui.logAction(gui.Tr.Actions.RebaseBranch)
err := gui.GitCommand.RebaseBranch(selectedBranchName)
err := gui.GitCommand.Rebase.RebaseBranch(selectedBranchName)
return gui.handleGenericMergeCommandResult(err)
},
})
@ -396,7 +396,7 @@ func (gui *Gui) handleFastForward() error {
return gui.createErrorPanel(gui.Tr.FwdCommitsToPush)
}
upstream, err := gui.GitCommand.GetUpstreamForBranch(branch.Name)
upstream, err := gui.GitCommand.Branch.GetUpstream(branch.Name)
if err != nil {
return gui.surfaceError(err)
}
@ -421,7 +421,7 @@ func (gui *Gui) handleFastForward() error {
_ = gui.pullWithLock(PullFilesOptions{action: action, FastForwardOnly: true})
} else {
gui.logAction(action)
err := gui.GitCommand.FastForward(branch.Name, remoteName, remoteBranchName, gui.promptUserForCredential)
err := gui.GitCommand.Sync.FastForward(branch.Name, remoteName, remoteBranchName)
gui.handleCredentialsPopup(err)
_ = gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{BRANCHES}})
}
@ -450,7 +450,7 @@ func (gui *Gui) handleRenameBranch() error {
initialContent: branch.Name,
handleConfirm: func(newBranchName string) error {
gui.logAction(gui.Tr.Actions.RenameBranch)
if err := gui.GitCommand.RenameBranch(branch.Name, newBranchName); err != nil {
if err := gui.GitCommand.Branch.Rename(branch.Name, newBranchName); err != nil {
return gui.surfaceError(err)
}
@ -519,7 +519,7 @@ func (gui *Gui) handleNewBranchOffCurrentItem() error {
initialContent: prefilledName,
handleConfirm: func(response string) error {
gui.logAction(gui.Tr.Actions.CreateBranch)
if err := gui.GitCommand.NewBranch(sanitizedBranchName(response), item.ID()); err != nil {
if err := gui.GitCommand.Branch.New(sanitizedBranchName(response), item.ID()); err != nil {
return err
}

View File

@ -149,7 +149,7 @@ func (gui *Gui) HandlePasteCommits() error {
handleConfirm: func() error {
return gui.WithWaitingStatus(gui.Tr.CherryPickingStatus, func() error {
gui.logAction(gui.Tr.Actions.CherryPick)
err := gui.GitCommand.CherryPickCommits(gui.State.Modes.CherryPicking.CherryPickedCommits)
err := gui.GitCommand.Rebase.CherryPickCommits(gui.State.Modes.CherryPicking.CherryPickedCommits)
return gui.handleGenericMergeCommandResult(err)
})
},

View File

@ -45,7 +45,7 @@ func (gui *Gui) commitFilesRenderToMain() error {
to := gui.State.CommitFileManager.GetParent()
from, reverse := gui.getFromAndReverseArgsForDiff(to)
cmdObj := gui.GitCommand.ShowFileDiffCmdObj(from, to, reverse, node.GetPath(), false)
cmdObj := gui.GitCommand.WorkingTree.ShowFileDiffCmdObj(from, to, reverse, node.GetPath(), false)
task := NewRunPtyTask(cmdObj.GetCmd())
return gui.refreshMainViews(refreshMainOpts{
@ -64,7 +64,7 @@ func (gui *Gui) handleCheckoutCommitFile() error {
}
gui.logAction(gui.Tr.Actions.CheckoutFile)
if err := gui.GitCommand.CheckoutFile(gui.State.CommitFileManager.GetParent(), node.GetPath()); err != nil {
if err := gui.GitCommand.WorkingTree.CheckoutFile(gui.State.CommitFileManager.GetParent(), node.GetPath()); err != nil {
return gui.surfaceError(err)
}
@ -84,7 +84,7 @@ func (gui *Gui) handleDiscardOldFileChange() error {
handleConfirm: func() error {
return gui.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
gui.logAction(gui.Tr.Actions.DiscardOldFileChange)
if err := gui.GitCommand.DiscardOldFileChanges(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, fileName); err != nil {
if err := gui.GitCommand.Rebase.DiscardOldFileChanges(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, fileName); err != nil {
if err := gui.handleGenericMergeCommandResult(err); err != nil {
return err
}
@ -145,7 +145,7 @@ func (gui *Gui) handleToggleFileForPatch() error {
}
toggleTheFile := func() error {
if !gui.GitCommand.PatchManager.Active() {
if !gui.GitCommand.Patch.PatchManager.Active() {
if err := gui.startPatchManager(); err != nil {
return err
}
@ -154,14 +154,14 @@ func (gui *Gui) handleToggleFileForPatch() error {
// if there is any file that hasn't been fully added we'll fully add everything,
// otherwise we'll remove everything
adding := node.AnyFile(func(file *models.CommitFile) bool {
return gui.GitCommand.PatchManager.GetFileStatus(file.Name, gui.State.CommitFileManager.GetParent()) != patch.WHOLE
return gui.GitCommand.Patch.PatchManager.GetFileStatus(file.Name, gui.State.CommitFileManager.GetParent()) != patch.WHOLE
})
err := node.ForEachFile(func(file *models.CommitFile) error {
if adding {
return gui.GitCommand.PatchManager.AddFileWhole(file.Name)
return gui.GitCommand.Patch.PatchManager.AddFileWhole(file.Name)
} else {
return gui.GitCommand.PatchManager.RemoveFile(file.Name)
return gui.GitCommand.Patch.PatchManager.RemoveFile(file.Name)
}
})
@ -169,19 +169,19 @@ func (gui *Gui) handleToggleFileForPatch() error {
return gui.surfaceError(err)
}
if gui.GitCommand.PatchManager.IsEmpty() {
gui.GitCommand.PatchManager.Reset()
if gui.GitCommand.Patch.PatchManager.IsEmpty() {
gui.GitCommand.Patch.PatchManager.Reset()
}
return gui.postRefreshUpdate(gui.State.Contexts.CommitFiles)
}
if gui.GitCommand.PatchManager.Active() && gui.GitCommand.PatchManager.To != gui.State.CommitFileManager.GetParent() {
if gui.GitCommand.Patch.PatchManager.Active() && gui.GitCommand.Patch.PatchManager.To != gui.State.CommitFileManager.GetParent() {
return gui.ask(askOpts{
title: gui.Tr.DiscardPatch,
prompt: gui.Tr.DiscardPatchConfirm,
handleConfirm: func() error {
gui.GitCommand.PatchManager.Reset()
gui.GitCommand.Patch.PatchManager.Reset()
return toggleTheFile()
},
})
@ -196,7 +196,7 @@ func (gui *Gui) startPatchManager() error {
to := gui.State.Panels.CommitFiles.refName
from, reverse := gui.getFromAndReverseArgsForDiff(to)
gui.GitCommand.PatchManager.Start(from, to, reverse, canRebase)
gui.GitCommand.Patch.PatchManager.Start(from, to, reverse, canRebase)
return nil
}
@ -215,7 +215,7 @@ func (gui *Gui) enterCommitFile(opts OnFocusOpts) error {
}
enterTheFile := func() error {
if !gui.GitCommand.PatchManager.Active() {
if !gui.GitCommand.Patch.PatchManager.Active() {
if err := gui.startPatchManager(); err != nil {
return err
}
@ -224,13 +224,13 @@ func (gui *Gui) enterCommitFile(opts OnFocusOpts) error {
return gui.pushContext(gui.State.Contexts.PatchBuilding, opts)
}
if gui.GitCommand.PatchManager.Active() && gui.GitCommand.PatchManager.To != gui.State.CommitFileManager.GetParent() {
if gui.GitCommand.Patch.PatchManager.Active() && gui.GitCommand.Patch.PatchManager.To != gui.State.CommitFileManager.GetParent() {
return gui.ask(askOpts{
title: gui.Tr.DiscardPatch,
prompt: gui.Tr.DiscardPatchConfirm,
handlersManageFocus: true,
handleConfirm: func() error {
gui.GitCommand.PatchManager.Reset()
gui.GitCommand.Patch.PatchManager.Reset()
return enterTheFile()
},
handleClose: func() error {

View File

@ -24,7 +24,7 @@ func (gui *Gui) handleCommitConfirm() error {
flags = append(flags, "--signoff")
}
cmdObj := gui.GitCommand.CommitCmdObj(message, strings.Join(flags, " "))
cmdObj := gui.GitCommand.Commit.CommitCmdObj(message, strings.Join(flags, " "))
gui.logAction(gui.Tr.Actions.Commit)
_ = gui.returnFromContext()

View File

@ -45,7 +45,7 @@ func (gui *Gui) branchCommitsRenderToMain() error {
if commit == nil {
task = NewRenderStringTask(gui.Tr.NoCommitsThisBranch)
} else {
cmdObj := gui.GitCommand.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath())
cmdObj := gui.GitCommand.Commit.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath())
task = NewRunPtyTask(cmdObj.GetCmd())
}
@ -173,7 +173,7 @@ func (gui *Gui) handleCommitSquashDown() error {
handleConfirm: func() error {
return gui.WithWaitingStatus(gui.Tr.SquashingStatus, func() error {
gui.logAction(gui.Tr.Actions.SquashCommitDown)
err := gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "squash")
err := gui.GitCommand.Rebase.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "squash")
return gui.handleGenericMergeCommandResult(err)
})
},
@ -203,14 +203,14 @@ func (gui *Gui) handleCommitFixup() error {
handleConfirm: func() error {
return gui.WithWaitingStatus(gui.Tr.FixingStatus, func() error {
gui.logAction(gui.Tr.Actions.FixupCommit)
err := gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "fixup")
err := gui.GitCommand.Rebase.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "fixup")
return gui.handleGenericMergeCommandResult(err)
})
},
})
}
func (gui *Gui) handleRenameCommit() error {
func (gui *Gui) handleRewordCommit() error {
if ok, err := gui.validateNotInFilterMode(); err != nil || !ok {
return err
}
@ -224,7 +224,7 @@ func (gui *Gui) handleRenameCommit() error {
}
if gui.State.Panels.Commits.SelectedLineIdx != 0 {
return gui.createErrorPanel(gui.Tr.OnlyRenameTopCommit)
return gui.createErrorPanel(gui.Tr.OnlyRewordTopCommit)
}
commit := gui.getSelectedLocalCommit()
@ -232,17 +232,17 @@ func (gui *Gui) handleRenameCommit() error {
return nil
}
message, err := gui.GitCommand.GetCommitMessage(commit.Sha)
message, err := gui.GitCommand.Commit.GetCommitMessage(commit.Sha)
if err != nil {
return gui.surfaceError(err)
}
return gui.prompt(promptOpts{
title: gui.Tr.LcRenameCommit,
title: gui.Tr.LcRewordCommit,
initialContent: message,
handleConfirm: func(response string) error {
gui.logAction(gui.Tr.Actions.RewordCommit)
if err := gui.GitCommand.RenameCommit(response); err != nil {
if err := gui.GitCommand.Commit.RewordLastCommit(response); err != nil {
return gui.surfaceError(err)
}
@ -265,7 +265,7 @@ func (gui *Gui) handleRenameCommitEditor() error {
}
gui.logAction(gui.Tr.Actions.RewordCommit)
subProcess, err := gui.GitCommand.RewordCommit(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx)
subProcess, err := gui.GitCommand.Rebase.RewordCommit(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx)
if err != nil {
return gui.surfaceError(err)
}
@ -299,7 +299,7 @@ func (gui *Gui) handleMidRebaseCommand(action string) (bool, error) {
false,
)
if err := gui.GitCommand.EditRebaseTodo(gui.State.Panels.Commits.SelectedLineIdx, action); err != nil {
if err := gui.GitCommand.Rebase.EditRebaseTodo(gui.State.Panels.Commits.SelectedLineIdx, action); err != nil {
return false, gui.surfaceError(err)
}
@ -325,7 +325,7 @@ func (gui *Gui) handleCommitDelete() error {
handleConfirm: func() error {
return gui.WithWaitingStatus(gui.Tr.DeletingStatus, func() error {
gui.logAction(gui.Tr.Actions.DropCommit)
err := gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "drop")
err := gui.GitCommand.Rebase.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "drop")
return gui.handleGenericMergeCommandResult(err)
})
},
@ -349,7 +349,7 @@ func (gui *Gui) handleCommitMoveDown() error {
gui.logAction(gui.Tr.Actions.MoveCommitDown)
gui.logCommand(fmt.Sprintf("Moving commit %s down", selectedCommit.ShortSha()), false)
if err := gui.GitCommand.MoveTodoDown(index); err != nil {
if err := gui.GitCommand.Rebase.MoveTodoDown(index); err != nil {
return gui.surfaceError(err)
}
gui.State.Panels.Commits.SelectedLineIdx++
@ -358,7 +358,7 @@ func (gui *Gui) handleCommitMoveDown() error {
return gui.WithWaitingStatus(gui.Tr.MovingStatus, func() error {
gui.logAction(gui.Tr.Actions.MoveCommitDown)
err := gui.GitCommand.MoveCommitDown(gui.State.Commits, index)
err := gui.GitCommand.Rebase.MoveCommitDown(gui.State.Commits, index)
if err == nil {
gui.State.Panels.Commits.SelectedLineIdx++
}
@ -386,7 +386,7 @@ func (gui *Gui) handleCommitMoveUp() error {
false,
)
if err := gui.GitCommand.MoveTodoDown(index - 1); err != nil {
if err := gui.GitCommand.Rebase.MoveTodoDown(index - 1); err != nil {
return gui.surfaceError(err)
}
gui.State.Panels.Commits.SelectedLineIdx--
@ -395,7 +395,7 @@ func (gui *Gui) handleCommitMoveUp() error {
return gui.WithWaitingStatus(gui.Tr.MovingStatus, func() error {
gui.logAction(gui.Tr.Actions.MoveCommitUp)
err := gui.GitCommand.MoveCommitDown(gui.State.Commits, index-1)
err := gui.GitCommand.Rebase.MoveCommitDown(gui.State.Commits, index-1)
if err == nil {
gui.State.Panels.Commits.SelectedLineIdx--
}
@ -418,7 +418,7 @@ func (gui *Gui) handleCommitEdit() error {
return gui.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
gui.logAction(gui.Tr.Actions.EditCommit)
err = gui.GitCommand.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "edit")
err = gui.GitCommand.Rebase.InteractiveRebase(gui.State.Commits, gui.State.Panels.Commits.SelectedLineIdx, "edit")
return gui.handleGenericMergeCommandResult(err)
})
}
@ -434,7 +434,7 @@ func (gui *Gui) handleCommitAmendTo() error {
handleConfirm: func() error {
return gui.WithWaitingStatus(gui.Tr.AmendingStatus, func() error {
gui.logAction(gui.Tr.Actions.AmendCommit)
err := gui.GitCommand.AmendTo(gui.State.Commits[gui.State.Panels.Commits.SelectedLineIdx].Sha)
err := gui.GitCommand.Rebase.AmendTo(gui.State.Commits[gui.State.Panels.Commits.SelectedLineIdx].Sha)
return gui.handleGenericMergeCommandResult(err)
})
},
@ -470,7 +470,7 @@ func (gui *Gui) handleCommitRevert() error {
return gui.createRevertMergeCommitMenu(commit)
} else {
gui.logAction(gui.Tr.Actions.RevertCommit)
if err := gui.GitCommand.Revert(commit.Sha); err != nil {
if err := gui.GitCommand.Commit.Revert(commit.Sha); err != nil {
return gui.surfaceError(err)
}
return gui.afterRevertCommit()
@ -481,7 +481,7 @@ func (gui *Gui) createRevertMergeCommitMenu(commit *models.Commit) error {
menuItems := make([]*menuItem, len(commit.Parents))
for i, parentSha := range commit.Parents {
i := i
message, err := gui.GitCommand.GetCommitMessageFirstLine(parentSha)
message, err := gui.GitCommand.Commit.GetCommitMessageFirstLine(parentSha)
if err != nil {
return gui.surfaceError(err)
}
@ -491,7 +491,7 @@ func (gui *Gui) createRevertMergeCommitMenu(commit *models.Commit) error {
onPress: func() error {
parentNumber := i + 1
gui.logAction(gui.Tr.Actions.RevertCommit)
if err := gui.GitCommand.RevertMerge(commit.Sha, parentNumber); err != nil {
if err := gui.GitCommand.Commit.RevertMerge(commit.Sha, parentNumber); err != nil {
return gui.surfaceError(err)
}
return gui.afterRevertCommit()
@ -538,7 +538,7 @@ func (gui *Gui) handleCreateFixupCommit() error {
prompt: prompt,
handleConfirm: func() error {
gui.logAction(gui.Tr.Actions.CreateFixupCommit)
if err := gui.GitCommand.CreateFixupCommit(commit.Sha); err != nil {
if err := gui.GitCommand.Commit.CreateFixupCommit(commit.Sha); err != nil {
return gui.surfaceError(err)
}
@ -570,7 +570,7 @@ func (gui *Gui) handleSquashAllAboveFixupCommits() error {
handleConfirm: func() error {
return gui.WithWaitingStatus(gui.Tr.SquashingStatus, func() error {
gui.logAction(gui.Tr.Actions.SquashAllAboveFixupCommits)
err := gui.GitCommand.SquashAllAboveFixupCommits(commit.Sha)
err := gui.GitCommand.Rebase.SquashAllAboveFixupCommits(commit.Sha)
return gui.handleGenericMergeCommandResult(err)
})
},
@ -618,7 +618,7 @@ func (gui *Gui) handleCreateAnnotatedTag(commitSha string) error {
title: gui.Tr.TagMessageTitle,
handleConfirm: func(msg string) error {
gui.logAction(gui.Tr.Actions.CreateAnnotatedTag)
if err := gui.GitCommand.CreateAnnotatedTag(tagName, commitSha, msg); err != nil {
if err := gui.GitCommand.Tag.CreateAnnotated(tagName, commitSha, msg); err != nil {
return gui.surfaceError(err)
}
return gui.afterTagCreate(tagName)
@ -633,7 +633,7 @@ func (gui *Gui) handleCreateLightweightTag(commitSha string) error {
title: gui.Tr.TagNameTitle,
handleConfirm: func(tagName string) error {
gui.logAction(gui.Tr.Actions.CreateLightweightTag)
if err := gui.GitCommand.CreateLightweightTag(tagName, commitSha); err != nil {
if err := gui.GitCommand.Tag.CreateLightweight(tagName, commitSha); err != nil {
return gui.surfaceError(err)
}
return gui.afterTagCreate(tagName)
@ -702,7 +702,7 @@ func (gui *Gui) handleCopySelectedCommitMessageToClipboard() error {
return nil
}
message, err := gui.GitCommand.GetCommitMessage(commit.Sha)
message, err := gui.GitCommand.Commit.GetCommitMessage(commit.Sha)
if err != nil {
return gui.surfaceError(err)
}

View File

@ -39,7 +39,7 @@ func (gui *Gui) DecreaseContextInDiffView() error {
}
func (gui *Gui) CheckCanChangeContext() error {
if gui.GitCommand.PatchManager.Active() {
if gui.GitCommand.Patch.PatchManager.Active() {
return errors.New(gui.Tr.CantChangeContextSizeError)
}

View File

@ -27,7 +27,7 @@ func setupGuiForTest(gui *Gui) {
gui.g = &gocui.Gui{}
gui.Views.Main, _ = gui.prepareView("main")
gui.Views.Secondary, _ = gui.prepareView("secondary")
gui.GitCommand.PatchManager = &patch.PatchManager{}
gui.GitCommand.Patch.PatchManager = &patch.PatchManager{}
_, _ = gui.refreshLineByLinePanel(diffForTest, "", false, 11)
}
@ -136,7 +136,7 @@ func TestDoesntIncreaseContextInDiffViewInContextWhenInPatchBuildingMode(t *test
setupGuiForTest(gui)
gui.UserConfig.Git.DiffContextSize = 2
_ = gui.pushContextDirect(gui.State.Contexts.CommitFiles)
gui.GitCommand.PatchManager.Start("from", "to", false, false)
gui.GitCommand.Patch.PatchManager.Start("from", "to", false, false)
errorCount := 0
gui.PopupHandler = &TestPopupHandler{
@ -158,7 +158,7 @@ func TestDoesntDecreaseContextInDiffViewInContextWhenInPatchBuildingMode(t *test
setupGuiForTest(gui)
gui.UserConfig.Git.DiffContextSize = 2
_ = gui.pushContextDirect(gui.State.Contexts.CommitFiles)
gui.GitCommand.PatchManager.Start("from", "to", false, false)
gui.GitCommand.Patch.PatchManager.Start("from", "to", false, false)
errorCount := 0
gui.PopupHandler = &TestPopupHandler{

View File

@ -13,7 +13,7 @@ func (gui *Gui) handleCreateDiscardMenu() error {
displayString: gui.Tr.LcDiscardAllChanges,
onPress: func() error {
gui.logAction(gui.Tr.Actions.DiscardAllChangesInDirectory)
if err := gui.GitCommand.DiscardAllDirChanges(node); err != nil {
if err := gui.GitCommand.WorkingTree.DiscardAllDirChanges(node); err != nil {
return gui.surfaceError(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{FILES}})
@ -26,7 +26,7 @@ func (gui *Gui) handleCreateDiscardMenu() error {
displayString: gui.Tr.LcDiscardUnstagedChanges,
onPress: func() error {
gui.logAction(gui.Tr.Actions.DiscardUnstagedChangesInDirectory)
if err := gui.GitCommand.DiscardUnstagedDirChanges(node); err != nil {
if err := gui.GitCommand.WorkingTree.DiscardUnstagedDirChanges(node); err != nil {
return gui.surfaceError(err)
}
@ -55,7 +55,7 @@ func (gui *Gui) handleCreateDiscardMenu() error {
displayString: gui.Tr.LcDiscardAllChanges,
onPress: func() error {
gui.logAction(gui.Tr.Actions.DiscardAllChangesInFile)
if err := gui.GitCommand.DiscardAllFileChanges(file); err != nil {
if err := gui.GitCommand.WorkingTree.DiscardAllFileChanges(file); err != nil {
return gui.surfaceError(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{FILES}})
@ -68,7 +68,7 @@ func (gui *Gui) handleCreateDiscardMenu() error {
displayString: gui.Tr.LcDiscardUnstagedChanges,
onPress: func() error {
gui.logAction(gui.Tr.Actions.DiscardAllUnstagedChangesInFile)
if err := gui.GitCommand.DiscardUnstagedFileChanges(file); err != nil {
if err := gui.GitCommand.WorkingTree.DiscardUnstagedFileChanges(file); err != nil {
return gui.surfaceError(err)
}

View File

@ -1,7 +1,6 @@
package gui
import (
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/updates"
@ -17,6 +16,6 @@ func NewDummyUpdater() *updates.Updater {
func NewDummyGui() *Gui {
newAppConfig := config.NewDummyAppConfig()
dummyGui, _ := NewGui(utils.NewDummyCommon(), commands.NewDummyGitCommand(), oscommands.NewDummyOSCommand(), newAppConfig, NewDummyUpdater(), "", false)
dummyGui, _ := NewGui(utils.NewDummyCommon(), newAppConfig, utils.NewDummyGitConfig(), NewDummyUpdater(), "", false)
return dummyGui
}

View File

@ -58,7 +58,7 @@ func (gui *Gui) filesRenderToMain() error {
return gui.refreshMergePanelWithLock()
}
cmdObj := gui.GitCommand.WorktreeFileDiffCmdObj(node, false, !node.GetHasUnstagedChanges() && node.GetHasStagedChanges(), gui.State.IgnoreWhitespaceInDiffView)
cmdObj := gui.GitCommand.WorkingTree.WorktreeFileDiffCmdObj(node, false, !node.GetHasUnstagedChanges() && node.GetHasStagedChanges(), gui.State.IgnoreWhitespaceInDiffView)
refreshOpts := refreshMainOpts{main: &viewUpdateOpts{
title: gui.Tr.UnstagedChanges,
@ -67,7 +67,7 @@ func (gui *Gui) filesRenderToMain() error {
if node.GetHasUnstagedChanges() {
if node.GetHasStagedChanges() {
cmdObj := gui.GitCommand.WorktreeFileDiffCmdObj(node, false, true, gui.State.IgnoreWhitespaceInDiffView)
cmdObj := gui.GitCommand.WorkingTree.WorktreeFileDiffCmdObj(node, false, true, gui.State.IgnoreWhitespaceInDiffView)
refreshOpts.secondary = &viewUpdateOpts{
title: gui.Tr.StagedChanges,
@ -157,7 +157,7 @@ func (gui *Gui) stageSelectedFile() error {
return nil
}
return gui.GitCommand.StageFile(file.Name)
return gui.GitCommand.WorkingTree.StageFile(file.Name)
}
func (gui *Gui) handleEnterFile() error {
@ -207,12 +207,12 @@ func (gui *Gui) handleFilePress() error {
if file.HasUnstagedChanges {
gui.logAction(gui.Tr.Actions.StageFile)
if err := gui.GitCommand.StageFile(file.Name); err != nil {
if err := gui.GitCommand.WorkingTree.StageFile(file.Name); err != nil {
return gui.surfaceError(err)
}
} else {
gui.logAction(gui.Tr.Actions.UnstageFile)
if err := gui.GitCommand.UnStageFile(file.Names(), file.Tracked); err != nil {
if err := gui.GitCommand.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
return gui.surfaceError(err)
}
}
@ -225,13 +225,13 @@ func (gui *Gui) handleFilePress() error {
if node.GetHasUnstagedChanges() {
gui.logAction(gui.Tr.Actions.StageFile)
if err := gui.GitCommand.StageFile(node.Path); err != nil {
if err := gui.GitCommand.WorkingTree.StageFile(node.Path); err != nil {
return gui.surfaceError(err)
}
} else {
// pretty sure it doesn't matter that we're always passing true here
gui.logAction(gui.Tr.Actions.UnstageFile)
if err := gui.GitCommand.UnStageFile([]string{node.Path}, true); err != nil {
if err := gui.GitCommand.WorkingTree.UnStageFile([]string{node.Path}, true); err != nil {
return gui.surfaceError(err)
}
}
@ -262,10 +262,10 @@ func (gui *Gui) handleStageAll() error {
var err error
if gui.allFilesStaged() {
gui.logAction(gui.Tr.Actions.UnstageAllFiles)
err = gui.GitCommand.UnstageAll()
err = gui.GitCommand.WorkingTree.UnstageAll()
} else {
gui.logAction(gui.Tr.Actions.StageAllFiles)
err = gui.GitCommand.StageAll()
err = gui.GitCommand.WorkingTree.StageAll()
}
if err != nil {
_ = gui.surfaceError(err)
@ -288,12 +288,10 @@ func (gui *Gui) handleIgnoreFile() error {
return gui.createErrorPanel("Cannot ignore .gitignore")
}
gui.logAction(gui.Tr.Actions.IgnoreFile)
unstageFiles := func() error {
return node.ForEachFile(func(file *models.File) error {
if file.HasStagedChanges {
if err := gui.GitCommand.UnStageFile(file.Names(), file.Tracked); err != nil {
if err := gui.GitCommand.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
return err
}
}
@ -307,16 +305,17 @@ func (gui *Gui) handleIgnoreFile() error {
title: gui.Tr.IgnoreTracked,
prompt: gui.Tr.IgnoreTrackedPrompt,
handleConfirm: func() error {
gui.logAction(gui.Tr.Actions.IgnoreFile)
// not 100% sure if this is necessary but I'll assume it is
if err := unstageFiles(); err != nil {
return err
}
if err := gui.GitCommand.RemoveTrackedFiles(node.GetPath()); err != nil {
if err := gui.GitCommand.WorkingTree.RemoveTrackedFiles(node.GetPath()); err != nil {
return err
}
if err := gui.GitCommand.Ignore(node.GetPath()); err != nil {
if err := gui.GitCommand.WorkingTree.Ignore(node.GetPath()); err != nil {
return err
}
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{FILES}})
@ -324,11 +323,13 @@ func (gui *Gui) handleIgnoreFile() error {
})
}
gui.logAction(gui.Tr.Actions.IgnoreFile)
if err := unstageFiles(); err != nil {
return err
}
if err := gui.GitCommand.Ignore(node.GetPath()); err != nil {
if err := gui.GitCommand.WorkingTree.Ignore(node.GetPath()); err != nil {
return gui.surfaceError(err)
}
@ -362,7 +363,7 @@ func (gui *Gui) prepareFilesForCommit() error {
noStagedFiles := len(gui.stagedFiles()) == 0
if noStagedFiles && gui.UserConfig.Gui.SkipNoStagedFilesWarning {
gui.logAction(gui.Tr.Actions.StageAllFiles)
err := gui.GitCommand.StageAll()
err := gui.GitCommand.WorkingTree.StageAll()
if err != nil {
return err
}
@ -423,7 +424,7 @@ func (gui *Gui) promptToStageAllAndRetry(retry func() error) error {
prompt: gui.Tr.NoFilesStagedPrompt,
handleConfirm: func() error {
gui.logAction(gui.Tr.Actions.StageAllFiles)
if err := gui.GitCommand.StageAll(); err != nil {
if err := gui.GitCommand.WorkingTree.StageAll(); err != nil {
return gui.surfaceError(err)
}
if err := gui.refreshFilesAndSubmodules(); err != nil {
@ -452,7 +453,7 @@ func (gui *Gui) handleAmendCommitPress() error {
title: strings.Title(gui.Tr.AmendLastCommit),
prompt: gui.Tr.SureToAmend,
handleConfirm: func() error {
cmdObj := gui.GitCommand.AmendHeadCmdObj()
cmdObj := gui.GitCommand.Commit.AmendHeadCmdObj()
gui.logAction(gui.Tr.Actions.AmendCommit)
return gui.withGpgHandling(cmdObj, gui.Tr.AmendingStatus, nil)
},
@ -520,7 +521,7 @@ func (gui *Gui) editFile(filename string) error {
}
func (gui *Gui) editFileAtLine(filename string, lineNumber int) error {
cmdStr, err := gui.GitCommand.EditFileCmdStr(filename, lineNumber)
cmdStr, err := gui.GitCommand.File.GetEditCmdStr(filename, lineNumber)
if err != nil {
return gui.surfaceError(err)
}
@ -569,8 +570,7 @@ func (gui *Gui) refreshStateFiles() error {
prevNodes := gui.State.FileManager.GetAllItems()
prevSelectedLineIdx := gui.State.Panels.Files.SelectedLineIdx
files := loaders.
NewFileLoader(gui.Common, gui.GitCommand.Cmd, gui.GitCommand.GitConfig).
files := gui.GitCommand.Loaders.Files.
GetStatusFiles(loaders.GetStatusFileOptions{})
// for when you stage the old file of a rename and the new file is in a collapsed dir
@ -685,7 +685,7 @@ func (gui *Gui) handlePullFiles() error {
initialContent: suggestedRemote + "/" + currentBranch.Name,
findSuggestionsFunc: gui.getRemoteBranchesSuggestionsFunc("/"),
handleConfirm: func(upstream string) error {
if err := gui.GitCommand.SetUpstreamBranch(upstream); err != nil {
if err := gui.GitCommand.Branch.SetCurrentBranchUpstream(upstream); err != nil {
errorMessage := err.Error()
if strings.Contains(errorMessage, "does not exist") {
errorMessage = fmt.Sprintf("upstream branch %s not found.\nIf you expect it to exist, you should fetch (with 'f').\nOtherwise, you should push (with 'shift+P')", upstream)
@ -724,9 +724,8 @@ func (gui *Gui) pullWithLock(opts PullFilesOptions) error {
gui.logAction(opts.action)
err := gui.GitCommand.Pull(
err := gui.GitCommand.Sync.Pull(
commands.PullOptions{
PromptUserForCredential: gui.promptUserForCredential,
RemoteName: opts.RemoteName,
BranchName: opts.BranchName,
FastForwardOnly: opts.FastForwardOnly,
@ -751,12 +750,12 @@ func (gui *Gui) push(opts pushOpts) error {
}
go utils.Safe(func() {
gui.logAction(gui.Tr.Actions.Push)
err := gui.GitCommand.Push(commands.PushOpts{
err := gui.GitCommand.Sync.Push(commands.PushOpts{
Force: opts.force,
UpstreamRemote: opts.upstreamRemote,
UpstreamBranch: opts.upstreamBranch,
SetUpstream: opts.setUpstream,
}, gui.promptUserForCredential)
})
if err != nil && !opts.force && strings.Contains(err.Error(), "Updates were rejected") {
forcePushDisabled := gui.UserConfig.Git.DisableForcePushing
@ -819,7 +818,7 @@ func (gui *Gui) pushFiles() error {
suggestedRemote := getSuggestedRemote(gui.State.Remotes)
if gui.GitCommand.PushToCurrent {
if gui.GitCommand.Config.GetPushToCurrent() {
return gui.push(pushOpts{setUpstream: true})
} else {
return gui.prompt(promptOpts{
@ -954,14 +953,14 @@ func (gui *Gui) handleCreateStashMenu() error {
displayString: gui.Tr.LcStashAllChanges,
onPress: func() error {
gui.logAction(gui.Tr.Actions.StashAllChanges)
return gui.handleStashSave(gui.GitCommand.StashSave)
return gui.handleStashSave(gui.GitCommand.Stash.Save)
},
},
{
displayString: gui.Tr.LcStashStagedChanges,
onPress: func() error {
gui.logAction(gui.Tr.Actions.StashStagedChanges)
return gui.handleStashSave(gui.GitCommand.StashSaveStagedChanges)
return gui.handleStashSave(gui.GitCommand.Stash.SaveStagedChanges)
},
},
}
@ -970,7 +969,7 @@ func (gui *Gui) handleCreateStashMenu() error {
}
func (gui *Gui) handleStashChanges() error {
return gui.handleStashSave(gui.GitCommand.StashSave)
return gui.handleStashSave(gui.GitCommand.Stash.Save)
}
func (gui *Gui) handleCreateResetToUpstreamMenu() error {
@ -1026,7 +1025,7 @@ func (gui *Gui) handleOpenMergeTool() error {
handleConfirm: func() error {
gui.logAction(gui.Tr.Actions.OpenMergeTool)
return gui.runSubprocessWithSuspenseAndRefresh(
gui.GitCommand.OpenMergeToolCmdObj(),
gui.GitCommand.WorkingTree.OpenMergeToolCmdObj(),
)
},
})

View File

@ -215,8 +215,7 @@ func (gui *Gui) fetch() (err error) {
defer gui.Mutexes.FetchMutex.Unlock()
gui.logAction("Fetch")
err = gui.GitCommand.Fetch(commands.FetchOptions{PromptUserForCredential: gui.promptUserForCredential})
err = gui.GitCommand.Sync.Fetch(commands.FetchOptions{})
if err != nil && strings.Contains(err.Error(), "exit status 128") {
_ = gui.createErrorPanel(gui.Tr.PassUnameWrong)
@ -231,7 +230,7 @@ func (gui *Gui) backgroundFetch() (err error) {
gui.Mutexes.FetchMutex.Lock()
defer gui.Mutexes.FetchMutex.Unlock()
err = gui.GitCommand.Fetch(commands.FetchOptions{})
err = gui.GitCommand.Sync.Fetch(commands.FetchOptions{Background: true})
_ = gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{BRANCHES, COMMITS, REMOTES, TAGS}, mode: ASYNC})

View File

@ -15,7 +15,7 @@ import (
func (gui *Gui) withGpgHandling(cmdObj oscommands.ICmdObj, waitingStatus string, onSuccess func() error) error {
gui.logCommand(cmdObj.ToString(), true)
useSubprocess := gui.GitCommand.UsingGpg()
useSubprocess := gui.GitCommand.Config.UsingGpg()
if useSubprocess {
success, err := gui.runSubprocessWithSuspense(gui.OSCommand.Cmd.NewShell(cmdObj.ToString()))
if success && onSuccess != nil {

View File

@ -12,6 +12,7 @@ import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands"
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
@ -433,12 +434,16 @@ func (gui *Gui) resetState(filterPath string, reuseState bool) {
// for now the split view will always be on
// NewGui builds a new gui handler
func NewGui(cmn *common.Common, gitCommand *commands.GitCommand, oSCommand *oscommands.OSCommand, config config.AppConfigurer, updater *updates.Updater, filterPath string, showRecentRepos bool) (*Gui, error) {
func NewGui(
cmn *common.Common,
config config.AppConfigurer,
gitConfig git_config.IGitConfig,
updater *updates.Updater,
filterPath string,
showRecentRepos bool,
) (*Gui, error) {
gui := &Gui{
Common: cmn,
GitCommand: gitCommand,
OSCommand: oSCommand,
Config: config,
Updater: updater,
statusManager: &statusManager{},
@ -455,11 +460,30 @@ func NewGui(cmn *common.Common, gitCommand *commands.GitCommand, oSCommand *osco
ShowExtrasWindow: cmn.UserConfig.Gui.ShowCommandLog && !config.GetAppState().HideCommandLog,
}
guiIO := oscommands.NewGuiIO(
cmn.Log,
gui.logCommand,
gui.getCmdWriter,
gui.promptUserForCredential,
)
osCommand := oscommands.NewOSCommand(cmn, oscommands.GetPlatform(), guiIO)
gui.OSCommand = osCommand
var err error
gui.GitCommand, err = commands.NewGitCommand(
cmn,
osCommand,
gitConfig,
)
if err != nil {
return nil, err
}
gui.resetState(filterPath, false)
gui.watchFilesForChanges()
oSCommand.SetLogCommandFn(gui.logCommand)
gui.PopupHandler = &RealPopupHandler{gui: gui}
authors.SetCustomAuthors(gui.UserConfig.Gui.AuthorColors)

View File

@ -743,8 +743,8 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
ViewName: "commits",
Contexts: []string{string(BRANCH_COMMITS_CONTEXT_KEY)},
Key: gui.getKey(config.Commits.RenameCommit),
Handler: gui.handleRenameCommit,
Description: gui.Tr.LcRenameCommit,
Handler: gui.handleRewordCommit,
Description: gui.Tr.LcRewordCommit,
},
{
ViewName: "commits",

View File

@ -124,8 +124,6 @@ func (gui *Gui) createAllViews() error {
return err
}
gui.GitCommand.GetCmdWriter = gui.getCmdWriter
return nil
}

View File

@ -144,7 +144,7 @@ func (gui *Gui) refreshMainViewForLineByLine(state *LblPanelState) error {
if gui.currentContext().GetKey() == gui.State.Contexts.PatchBuilding.GetKey() {
filename := gui.getSelectedCommitFileName()
var err error
includedLineIndices, err = gui.GitCommand.PatchManager.GetFileIncLineIndices(filename)
includedLineIndices, err = gui.GitCommand.Patch.PatchManager.GetFileIncLineIndices(filename)
if err != nil {
return err
}

View File

@ -312,7 +312,7 @@ func (gui *Gui) commitFilesListContext() IListContext {
return [][]string{{style.FgRed.Sprint("(none)")}}
}
lines := gui.State.CommitFileManager.Render(gui.State.Modes.Diffing.Ref, gui.GitCommand.PatchManager)
lines := gui.State.CommitFileManager.Render(gui.State.Modes.Diffing.Ref, gui.GitCommand.Patch.PatchManager)
mappedLines := make([][]string, len(lines))
for i, line := range lines {
mappedLines[i] = []string{line}

View File

@ -195,7 +195,7 @@ func (gui *Gui) catSelectedFile() (string, error) {
return "", errors.New(gui.Tr.NotAFile)
}
cat, err := gui.GitCommand.CatFile(item.Name)
cat, err := gui.GitCommand.File.Cat(item.Name)
if err != nil {
gui.Log.Error(err)
return "", err
@ -263,7 +263,7 @@ func (gui *Gui) handleCompleteMerge() error {
}
// if we got conflicts after unstashing, we don't want to call any git
// commands to continue rebasing/merging here
if gui.GitCommand.WorkingTreeState() == enums.REBASE_MODE_NONE {
if gui.GitCommand.Status.WorkingTreeState() == enums.REBASE_MODE_NONE {
return gui.handleEscapeMerge()
}
// if there are no more files with merge conflicts, we should ask whether the user wants to continue

View File

@ -26,7 +26,7 @@ func (gui *Gui) modeStatuses() []modeStatus {
reset: gui.exitDiffMode,
},
{
isActive: gui.GitCommand.PatchManager.Active,
isActive: gui.GitCommand.Patch.PatchManager.Active,
description: func() string {
return style.FgYellow.SetBold().Sprintf(
"%s %s",
@ -61,10 +61,10 @@ func (gui *Gui) modeStatuses() []modeStatus {
},
{
isActive: func() bool {
return gui.GitCommand.WorkingTreeState() != enums.REBASE_MODE_NONE
return gui.GitCommand.Status.WorkingTreeState() != enums.REBASE_MODE_NONE
},
description: func() string {
workingTreeState := gui.GitCommand.WorkingTreeState()
workingTreeState := gui.GitCommand.Status.WorkingTreeState()
return style.FgYellow.Sprintf(
"%s %s",
workingTreeState,

View File

@ -18,7 +18,7 @@ func (gui *Gui) getFromAndReverseArgsForDiff(to string) (string, bool) {
}
func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int, state *LblPanelState) error {
if !gui.GitCommand.PatchManager.Active() {
if !gui.GitCommand.Patch.PatchManager.Active() {
return gui.handleEscapePatchBuildingPanel()
}
@ -33,12 +33,12 @@ func (gui *Gui) refreshPatchBuildingPanel(selectedLineIdx int, state *LblPanelSt
to := gui.State.CommitFileManager.GetParent()
from, reverse := gui.getFromAndReverseArgsForDiff(to)
diff, err := gui.GitCommand.ShowFileDiff(from, to, reverse, node.GetPath(), true)
diff, err := gui.GitCommand.WorkingTree.ShowFileDiff(from, to, reverse, node.GetPath(), true)
if err != nil {
return err
}
secondaryDiff := gui.GitCommand.PatchManager.RenderPatchForFile(node.GetPath(), true, false, true)
secondaryDiff := gui.GitCommand.Patch.PatchManager.RenderPatchForFile(node.GetPath(), true, false, true)
if err != nil {
return err
}
@ -64,15 +64,15 @@ func (gui *Gui) handleRefreshPatchBuildingPanel(selectedLineIdx int) error {
func (gui *Gui) handleToggleSelectionForPatch() error {
err := gui.withLBLActiveCheck(func(state *LblPanelState) error {
toggleFunc := gui.GitCommand.PatchManager.AddFileLineRange
toggleFunc := gui.GitCommand.Patch.PatchManager.AddFileLineRange
filename := gui.getSelectedCommitFileName()
includedLineIndices, err := gui.GitCommand.PatchManager.GetFileIncLineIndices(filename)
includedLineIndices, err := gui.GitCommand.Patch.PatchManager.GetFileIncLineIndices(filename)
if err != nil {
return err
}
currentLineIsStaged := utils.IncludesInt(includedLineIndices, state.GetSelectedLineIdx())
if currentLineIsStaged {
toggleFunc = gui.GitCommand.PatchManager.RemoveFileLineRange
toggleFunc = gui.GitCommand.Patch.PatchManager.RemoveFileLineRange
}
// add range of lines to those set for the file
@ -105,8 +105,8 @@ func (gui *Gui) handleToggleSelectionForPatch() error {
func (gui *Gui) handleEscapePatchBuildingPanel() error {
gui.escapeLineByLinePanel()
if gui.GitCommand.PatchManager.IsEmpty() {
gui.GitCommand.PatchManager.Reset()
if gui.GitCommand.Patch.PatchManager.IsEmpty() {
gui.GitCommand.Patch.PatchManager.Reset()
}
if gui.currentContext().GetKey() == gui.State.Contexts.PatchBuilding.GetKey() {
@ -118,8 +118,8 @@ func (gui *Gui) handleEscapePatchBuildingPanel() error {
}
func (gui *Gui) secondaryPatchPanelUpdateOpts() *viewUpdateOpts {
if gui.GitCommand.PatchManager.Active() {
patch := gui.GitCommand.PatchManager.RenderAggregatedPatchColored(false)
if gui.GitCommand.Patch.PatchManager.Active() {
patch := gui.GitCommand.Patch.PatchManager.RenderAggregatedPatchColored(false)
return &viewUpdateOpts{
title: "Custom Patch",

View File

@ -7,7 +7,7 @@ import (
)
func (gui *Gui) handleCreatePatchOptionsMenu() error {
if !gui.GitCommand.PatchManager.Active() {
if !gui.GitCommand.Patch.PatchManager.Active() {
return gui.createErrorPanel(gui.Tr.NoPatchError)
}
@ -26,10 +26,10 @@ func (gui *Gui) handleCreatePatchOptionsMenu() error {
},
}
if gui.GitCommand.PatchManager.CanRebase && gui.GitCommand.WorkingTreeState() == enums.REBASE_MODE_NONE {
if gui.GitCommand.Patch.PatchManager.CanRebase && gui.GitCommand.Status.WorkingTreeState() == enums.REBASE_MODE_NONE {
menuItems = append(menuItems, []*menuItem{
{
displayString: fmt.Sprintf("remove patch from original commit (%s)", gui.GitCommand.PatchManager.To),
displayString: fmt.Sprintf("remove patch from original commit (%s)", gui.GitCommand.Patch.PatchManager.To),
onPress: gui.handleDeletePatchFromCommit,
},
{
@ -44,7 +44,7 @@ func (gui *Gui) handleCreatePatchOptionsMenu() error {
if gui.currentContext().GetKey() == gui.State.Contexts.BranchCommits.GetKey() {
selectedCommit := gui.getSelectedLocalCommit()
if selectedCommit != nil && gui.GitCommand.PatchManager.To != selectedCommit.Sha {
if selectedCommit != nil && gui.GitCommand.Patch.PatchManager.To != selectedCommit.Sha {
// adding this option to index 1
menuItems = append(
menuItems[:1],
@ -66,7 +66,7 @@ func (gui *Gui) handleCreatePatchOptionsMenu() error {
func (gui *Gui) getPatchCommitIndex() int {
for index, commit := range gui.State.Commits {
if commit.Sha == gui.GitCommand.PatchManager.To {
if commit.Sha == gui.GitCommand.Patch.PatchManager.To {
return index
}
}
@ -74,7 +74,7 @@ func (gui *Gui) getPatchCommitIndex() int {
}
func (gui *Gui) validateNormalWorkingTreeState() (bool, error) {
if gui.GitCommand.WorkingTreeState() != enums.REBASE_MODE_NONE {
if gui.GitCommand.Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
return false, gui.createErrorPanel(gui.Tr.CantPatchWhileRebasingError)
}
return true, nil
@ -99,7 +99,7 @@ func (gui *Gui) handleDeletePatchFromCommit() error {
return gui.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
commitIndex := gui.getPatchCommitIndex()
gui.logAction(gui.Tr.Actions.RemovePatchFromCommit)
err := gui.GitCommand.DeletePatchesFromCommit(gui.State.Commits, commitIndex, gui.GitCommand.PatchManager)
err := gui.GitCommand.Patch.DeletePatchesFromCommit(gui.State.Commits, commitIndex)
return gui.handleGenericMergeCommandResult(err)
})
}
@ -116,7 +116,7 @@ func (gui *Gui) handleMovePatchToSelectedCommit() error {
return gui.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
commitIndex := gui.getPatchCommitIndex()
gui.logAction(gui.Tr.Actions.MovePatchToSelectedCommit)
err := gui.GitCommand.MovePatchToSelectedCommit(gui.State.Commits, commitIndex, gui.State.Panels.Commits.SelectedLineIdx, gui.GitCommand.PatchManager)
err := gui.GitCommand.Patch.MovePatchToSelectedCommit(gui.State.Commits, commitIndex, gui.State.Panels.Commits.SelectedLineIdx)
return gui.handleGenericMergeCommandResult(err)
})
}
@ -134,7 +134,7 @@ func (gui *Gui) handleMovePatchIntoWorkingTree() error {
return gui.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
commitIndex := gui.getPatchCommitIndex()
gui.logAction(gui.Tr.Actions.MovePatchIntoIndex)
err := gui.GitCommand.MovePatchIntoIndex(gui.State.Commits, commitIndex, gui.GitCommand.PatchManager, stash)
err := gui.GitCommand.Patch.MovePatchIntoIndex(gui.State.Commits, commitIndex, stash)
return gui.handleGenericMergeCommandResult(err)
})
}
@ -164,7 +164,7 @@ func (gui *Gui) handlePullPatchIntoNewCommit() error {
return gui.WithWaitingStatus(gui.Tr.RebasingStatus, func() error {
commitIndex := gui.getPatchCommitIndex()
gui.logAction(gui.Tr.Actions.MovePatchIntoNewCommit)
err := gui.GitCommand.PullPatchIntoNewCommit(gui.State.Commits, commitIndex, gui.GitCommand.PatchManager)
err := gui.GitCommand.Patch.PullPatchIntoNewCommit(gui.State.Commits, commitIndex)
return gui.handleGenericMergeCommandResult(err)
})
}
@ -179,14 +179,14 @@ func (gui *Gui) handleApplyPatch(reverse bool) error {
action = "Apply patch in reverse"
}
gui.logAction(action)
if err := gui.GitCommand.PatchManager.ApplyPatches(reverse); err != nil {
if err := gui.GitCommand.Patch.PatchManager.ApplyPatches(reverse); err != nil {
return gui.surfaceError(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC})
}
func (gui *Gui) handleResetPatch() error {
gui.GitCommand.PatchManager.Reset()
gui.GitCommand.Patch.PatchManager.Reset()
if gui.currentContextKeyIgnoringPopups() == MAIN_PATCH_BUILDING_CONTEXT_KEY {
if err := gui.pushContext(gui.State.Contexts.CommitFiles); err != nil {
return err

View File

@ -41,7 +41,7 @@ func (gui *Gui) onResize() error {
// command.
func (gui *Gui) newPtyTask(view *gocui.View, cmd *exec.Cmd, prefix string) error {
width, _ := gui.Views.Main.Size()
pager := gui.GitCommand.GetPager(width)
pager := gui.GitCommand.Config.GetPager(width)
if pager == "" {
// if we're not using a custom pager we don't need to use a pty

View File

@ -71,7 +71,7 @@ func (gui *Gui) createPullRequest(from string, to string) error {
}
func (gui *Gui) getHostingServiceMgr() *hosting_service.HostingServiceMgr {
remoteUrl := gui.GitCommand.GetRemoteURL()
remoteUrl := gui.GitCommand.Config.GetRemoteURL()
configServices := gui.UserConfig.Services
return hosting_service.NewHostingServiceMgr(gui.Log, gui.Tr, remoteUrl, configServices)
}

View File

@ -18,7 +18,7 @@ const (
func (gui *Gui) handleCreateRebaseOptionsMenu() error {
options := []string{REBASE_OPTION_CONTINUE, REBASE_OPTION_ABORT}
if gui.GitCommand.WorkingTreeState() == enums.REBASE_MODE_REBASING {
if gui.GitCommand.Status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
options = append(options, REBASE_OPTION_SKIP)
}
@ -35,7 +35,7 @@ func (gui *Gui) handleCreateRebaseOptionsMenu() error {
}
var title string
if gui.GitCommand.WorkingTreeState() == enums.REBASE_MODE_MERGING {
if gui.GitCommand.Status.WorkingTreeState() == enums.REBASE_MODE_MERGING {
title = gui.Tr.MergeOptionsTitle
} else {
title = gui.Tr.RebaseOptionsTitle
@ -45,7 +45,7 @@ func (gui *Gui) handleCreateRebaseOptionsMenu() error {
}
func (gui *Gui) genericMergeCommand(command string) error {
status := gui.GitCommand.WorkingTreeState()
status := gui.GitCommand.Status.WorkingTreeState()
if status != enums.REBASE_MODE_MERGING && status != enums.REBASE_MODE_REBASING {
return gui.createErrorPanel(gui.Tr.NotMergingOrRebasing)
@ -71,7 +71,7 @@ func (gui *Gui) genericMergeCommand(command string) error {
}
return nil
}
result := gui.GitCommand.GenericMergeOrRebaseAction(commandType, command)
result := gui.GitCommand.Rebase.GenericMergeOrRebaseAction(commandType, command)
if err := gui.handleGenericMergeCommandResult(result); err != nil {
return err
}
@ -142,7 +142,7 @@ func (gui *Gui) abortMergeOrRebaseWithConfirm() error {
}
func (gui *Gui) workingTreeStateNoun() string {
workingTreeState := gui.GitCommand.WorkingTreeState()
workingTreeState := gui.GitCommand.Status.WorkingTreeState()
switch workingTreeState {
case enums.REBASE_MODE_NONE:
return ""

View File

@ -99,7 +99,7 @@ func (gui *Gui) dispatchSwitchToRepo(path string, reuse bool) error {
// updateRecentRepoList registers the fact that we opened lazygit in this repo,
// so that we can open the same repo via the 'recent repos' menu
func (gui *Gui) updateRecentRepoList() error {
if gui.GitCommand.IsBareRepo() {
if gui.GitCommand.Status.IsBareRepo() {
// we could totally do this but it would require storing both the git-dir and the
// worktree in our recent repos list, which is a change that would need to be
// backwards compatible

View File

@ -1,7 +1,6 @@
package gui
import (
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
"github.com/jesseduffield/lazygit/pkg/commands/models"
)
@ -23,7 +22,7 @@ func (gui *Gui) reflogCommitsRenderToMain() error {
if commit == nil {
task = NewRenderStringTask("No reflog history")
} else {
cmdObj := gui.GitCommand.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath())
cmdObj := gui.GitCommand.Commit.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath())
task = NewRunPtyTask(cmdObj.GetCmd())
}
@ -53,8 +52,7 @@ func (gui *Gui) refreshReflogCommits() error {
}
refresh := func(stateCommits *[]*models.Commit, filterPath string) error {
commits, onlyObtainedNewReflogCommits, err := loaders.
NewReflogCommitLoader(gui.Common, gui.GitCommand.Cmd).
commits, onlyObtainedNewReflogCommits, err := gui.GitCommand.Loaders.ReflogCommits.
GetReflogCommits(lastReflogCommit, filterPath)
if err != nil {
return gui.surfaceError(err)

View File

@ -24,7 +24,7 @@ func (gui *Gui) remoteBranchesRenderToMain() error {
if remoteBranch == nil {
task = NewRenderStringTask("No branches for this remote")
} else {
cmdObj := gui.GitCommand.GetBranchGraphCmdObj(remoteBranch.FullName())
cmdObj := gui.GitCommand.Branch.GetGraphCmdObj(remoteBranch.FullName())
task = NewRunCommandTask(cmdObj.GetCmd())
}
@ -58,7 +58,7 @@ func (gui *Gui) handleDeleteRemoteBranch() error {
handleConfirm: func() error {
return gui.WithWaitingStatus(gui.Tr.DeletingStatus, func() error {
gui.logAction(gui.Tr.Actions.DeleteRemoteBranch)
err := gui.GitCommand.DeleteRemoteBranch(remoteBranch.RemoteName, remoteBranch.Name, gui.promptUserForCredential)
err := gui.GitCommand.Remote.DeleteRemoteBranch(remoteBranch.RemoteName, remoteBranch.Name)
gui.handleCredentialsPopup(err)
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{BRANCHES, REMOTES}})
@ -89,7 +89,7 @@ func (gui *Gui) handleSetBranchUpstream() error {
prompt: message,
handleConfirm: func() error {
gui.logAction(gui.Tr.Actions.SetBranchUpstream)
if err := gui.GitCommand.SetBranchUpstream(selectedBranch.RemoteName, selectedBranch.Name, checkedOutBranch.Name); err != nil {
if err := gui.GitCommand.Branch.SetUpstream(selectedBranch.RemoteName, selectedBranch.Name, checkedOutBranch.Name); err != nil {
return gui.surfaceError(err)
}

View File

@ -86,7 +86,7 @@ func (gui *Gui) handleAddRemote() error {
title: gui.Tr.LcNewRemoteUrl,
handleConfirm: func(remoteUrl string) error {
gui.logAction(gui.Tr.Actions.AddRemote)
if err := gui.GitCommand.AddRemote(remoteName, remoteUrl); err != nil {
if err := gui.GitCommand.Remote.AddRemote(remoteName, remoteUrl); err != nil {
return err
}
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{REMOTES}})
@ -108,7 +108,7 @@ func (gui *Gui) handleRemoveRemote() error {
prompt: gui.Tr.LcRemoveRemotePrompt + " '" + remote.Name + "'?",
handleConfirm: func() error {
gui.logAction(gui.Tr.Actions.RemoveRemote)
if err := gui.GitCommand.RemoveRemote(remote.Name); err != nil {
if err := gui.GitCommand.Remote.RemoveRemote(remote.Name); err != nil {
return gui.surfaceError(err)
}
@ -136,7 +136,7 @@ func (gui *Gui) handleEditRemote() error {
handleConfirm: func(updatedRemoteName string) error {
if updatedRemoteName != remote.Name {
gui.logAction(gui.Tr.Actions.UpdateRemote)
if err := gui.GitCommand.RenameRemote(remote.Name, updatedRemoteName); err != nil {
if err := gui.GitCommand.Remote.RenameRemote(remote.Name, updatedRemoteName); err != nil {
return gui.surfaceError(err)
}
}
@ -159,7 +159,7 @@ func (gui *Gui) handleEditRemote() error {
initialContent: url,
handleConfirm: func(updatedRemoteUrl string) error {
gui.logAction(gui.Tr.Actions.UpdateRemote)
if err := gui.GitCommand.UpdateRemoteUrl(updatedRemoteName, updatedRemoteUrl); err != nil {
if err := gui.GitCommand.Remote.UpdateRemoteUrl(updatedRemoteName, updatedRemoteUrl); err != nil {
return gui.surfaceError(err)
}
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{BRANCHES, REMOTES}})
@ -179,7 +179,7 @@ func (gui *Gui) handleFetchRemote() error {
gui.Mutexes.FetchMutex.Lock()
defer gui.Mutexes.FetchMutex.Unlock()
err := gui.GitCommand.FetchRemote(remote.Name, gui.promptUserForCredential)
err := gui.GitCommand.Sync.FetchRemote(remote.Name)
gui.handleCredentialsPopup(err)
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{BRANCHES, REMOTES}})

View File

@ -7,7 +7,7 @@ import (
)
func (gui *Gui) resetToRef(ref string, strength string, envVars []string) error {
if err := gui.GitCommand.ResetToCommit(ref, strength, envVars); err != nil {
if err := gui.GitCommand.Commit.ResetToCommit(ref, strength, envVars); err != nil {
return gui.surfaceError(err)
}

View File

@ -34,8 +34,8 @@ func (gui *Gui) refreshStagingPanel(forceSecondaryFocused bool, selectedLineIdx
}
// note for custom diffs, we'll need to send a flag here saying not to use the custom diff
diff := gui.GitCommand.WorktreeFileDiff(file, true, secondaryFocused, false)
secondaryDiff := gui.GitCommand.WorktreeFileDiff(file, true, !secondaryFocused, false)
diff := gui.GitCommand.WorkingTree.WorktreeFileDiff(file, true, secondaryFocused, false)
secondaryDiff := gui.GitCommand.WorkingTree.WorktreeFileDiff(file, true, !secondaryFocused, false)
// if we have e.g. a deleted file with nothing else to the diff will have only
// 4-5 lines in which case we'll swap panels
@ -144,7 +144,7 @@ func (gui *Gui) applySelection(reverse bool, state *LblPanelState) error {
applyFlags = append(applyFlags, "cached")
}
gui.logAction(gui.Tr.Actions.ApplyPatch)
err := gui.GitCommand.ApplyPatch(patch, applyFlags...)
err := gui.GitCommand.WorkingTree.ApplyPatch(patch, applyFlags...)
if err != nil {
return gui.surfaceError(err)
}

View File

@ -1,9 +1,7 @@
package gui
import (
"github.com/jesseduffield/lazygit/pkg/commands/loaders"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/utils"
)
// list panel functions
@ -23,7 +21,7 @@ func (gui *Gui) stashRenderToMain() error {
if stashEntry == nil {
task = NewRenderStringTask(gui.Tr.NoStashEntries)
} else {
task = NewRunPtyTask(gui.GitCommand.ShowStashEntryCmdObj(stashEntry.Index).GetCmd())
task = NewRunPtyTask(gui.GitCommand.Stash.ShowStashEntryCmdObj(stashEntry.Index).GetCmd())
}
return gui.refreshMainViews(refreshMainOpts{
@ -35,8 +33,7 @@ func (gui *Gui) stashRenderToMain() error {
}
func (gui *Gui) refreshStashEntries() error {
gui.State.StashEntries = loaders.
NewStashLoader(gui.Common, gui.GitCommand.Cmd).
gui.State.StashEntries = gui.GitCommand.Loaders.Stash.
GetStashEntries(gui.State.Modes.Filtering.GetPath())
return gui.State.Contexts.Stash.HandleRender()
@ -45,10 +42,19 @@ func (gui *Gui) refreshStashEntries() error {
// specific functions
func (gui *Gui) handleStashApply() error {
stashEntry := gui.getSelectedStashEntry()
if stashEntry == nil {
return nil
}
skipStashWarning := gui.UserConfig.Gui.SkipStashWarning
apply := func() error {
return gui.stashDo("apply")
gui.logAction(gui.Tr.Actions.Stash)
if err := gui.GitCommand.Stash.Apply(stashEntry.Index); err != nil {
return gui.surfaceError(err)
}
return gui.postStashRefresh()
}
if skipStashWarning {
@ -65,10 +71,19 @@ func (gui *Gui) handleStashApply() error {
}
func (gui *Gui) handleStashPop() error {
stashEntry := gui.getSelectedStashEntry()
if stashEntry == nil {
return nil
}
skipStashWarning := gui.UserConfig.Gui.SkipStashWarning
pop := func() error {
return gui.stashDo("pop")
gui.logAction(gui.Tr.Actions.Stash)
if err := gui.GitCommand.Stash.Pop(stashEntry.Index); err != nil {
return gui.surfaceError(err)
}
return gui.postStashRefresh()
}
if skipStashWarning {
@ -85,31 +100,25 @@ func (gui *Gui) handleStashPop() error {
}
func (gui *Gui) handleStashDrop() error {
stashEntry := gui.getSelectedStashEntry()
if stashEntry == nil {
return nil
}
return gui.ask(askOpts{
title: gui.Tr.StashDrop,
prompt: gui.Tr.SureDropStashEntry,
handleConfirm: func() error {
return gui.stashDo("drop")
gui.logAction(gui.Tr.Actions.Stash)
if err := gui.GitCommand.Stash.Drop(stashEntry.Index); err != nil {
return gui.surfaceError(err)
}
return gui.postStashRefresh()
},
})
}
func (gui *Gui) stashDo(method string) error {
stashEntry := gui.getSelectedStashEntry()
if stashEntry == nil {
errorMessage := utils.ResolvePlaceholderString(
gui.Tr.NoStashTo,
map[string]string{
"method": method,
},
)
return gui.createErrorPanel(errorMessage)
}
gui.logAction(gui.Tr.Actions.Stash)
if err := gui.GitCommand.StashDo(stashEntry.Index, method); err != nil {
return gui.surfaceError(err)
}
func (gui *Gui) postStashRefresh() error {
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{STASH, FILES}})
}

View File

@ -28,8 +28,8 @@ func (gui *Gui) refreshStatus() {
status += presentation.ColoredBranchStatus(currentBranch) + " "
}
if gui.GitCommand.WorkingTreeState() != enums.REBASE_MODE_NONE {
status += style.FgYellow.Sprintf("(%s) ", gui.GitCommand.WorkingTreeState())
if gui.GitCommand.Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
status += style.FgYellow.Sprintf("(%s) ", gui.GitCommand.Status.WorkingTreeState())
}
name := presentation.GetBranchTextStyle(currentBranch.Name).Sprint(currentBranch.Name)
@ -71,7 +71,7 @@ func (gui *Gui) handleStatusClick() error {
cx, _ := gui.Views.Status.Cursor()
upstreamStatus := presentation.BranchStatus(currentBranch)
repoName := utils.GetCurrentRepoName()
workingTreeState := gui.GitCommand.WorkingTreeState()
workingTreeState := gui.GitCommand.Status.WorkingTreeState()
switch workingTreeState {
case enums.REBASE_MODE_REBASING, enums.REBASE_MODE_MERGING:
var formattedState string

View File

@ -23,7 +23,7 @@ func (gui *Gui) subCommitsRenderToMain() error {
if commit == nil {
task = NewRenderStringTask("No commits")
} else {
cmdObj := gui.GitCommand.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath())
cmdObj := gui.GitCommand.Commit.ShowCmdObj(commit.Sha, gui.State.Modes.Filtering.GetPath())
task = NewRunPtyTask(cmdObj.GetCmd())
}

View File

@ -36,7 +36,7 @@ func (gui *Gui) submodulesRenderToMain() error {
if file == nil {
task = NewRenderStringTask(prefix)
} else {
cmdObj := gui.GitCommand.WorktreeFileDiffCmdObj(file, false, !file.HasUnstagedChanges && file.HasStagedChanges, gui.State.IgnoreWhitespaceInDiffView)
cmdObj := gui.GitCommand.WorkingTree.WorktreeFileDiffCmdObj(file, false, !file.HasUnstagedChanges && file.HasStagedChanges, gui.State.IgnoreWhitespaceInDiffView)
task = NewRunCommandTaskWithPrefix(cmdObj.GetCmd(), prefix)
}
}
@ -50,7 +50,7 @@ func (gui *Gui) submodulesRenderToMain() error {
}
func (gui *Gui) refreshStateSubmoduleConfigs() error {
configs, err := gui.GitCommand.GetSubmoduleConfigs()
configs, err := gui.GitCommand.Submodule.GetConfigs()
if err != nil {
return err
}
@ -80,7 +80,7 @@ func (gui *Gui) removeSubmodule(submodule *models.SubmoduleConfig) error {
prompt: fmt.Sprintf(gui.Tr.RemoveSubmodulePrompt, submodule.Name),
handleConfirm: func() error {
gui.logAction(gui.Tr.Actions.RemoveSubmodule)
if err := gui.GitCommand.SubmoduleDelete(submodule); err != nil {
if err := gui.GitCommand.Submodule.Delete(submodule); err != nil {
return gui.surfaceError(err)
}
@ -110,15 +110,15 @@ func (gui *Gui) resetSubmodule(submodule *models.SubmoduleConfig) error {
file := gui.fileForSubmodule(submodule)
if file != nil {
if err := gui.GitCommand.UnStageFile(file.Names(), file.Tracked); err != nil {
if err := gui.GitCommand.WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
return gui.surfaceError(err)
}
}
if err := gui.GitCommand.SubmoduleStash(submodule); err != nil {
if err := gui.GitCommand.Submodule.Stash(submodule); err != nil {
return gui.surfaceError(err)
}
if err := gui.GitCommand.SubmoduleReset(submodule); err != nil {
if err := gui.GitCommand.Submodule.Reset(submodule); err != nil {
return gui.surfaceError(err)
}
@ -142,7 +142,7 @@ func (gui *Gui) handleAddSubmodule() error {
handleConfirm: func(submodulePath string) error {
return gui.WithWaitingStatus(gui.Tr.LcAddingSubmoduleStatus, func() error {
gui.logAction(gui.Tr.Actions.AddSubmodule)
err := gui.GitCommand.SubmoduleAdd(submoduleName, submodulePath, submoduleUrl)
err := gui.GitCommand.Submodule.Add(submoduleName, submodulePath, submoduleUrl)
gui.handleCredentialsPopup(err)
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{SUBMODULES}})
@ -163,7 +163,7 @@ func (gui *Gui) handleEditSubmoduleUrl(submodule *models.SubmoduleConfig) error
handleConfirm: func(newUrl string) error {
return gui.WithWaitingStatus(gui.Tr.LcUpdatingSubmoduleUrlStatus, func() error {
gui.logAction(gui.Tr.Actions.UpdateSubmoduleUrl)
err := gui.GitCommand.SubmoduleUpdateUrl(submodule.Name, submodule.Path, newUrl)
err := gui.GitCommand.Submodule.UpdateUrl(submodule.Name, submodule.Path, newUrl)
gui.handleCredentialsPopup(err)
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{SUBMODULES}})
@ -175,7 +175,7 @@ func (gui *Gui) handleEditSubmoduleUrl(submodule *models.SubmoduleConfig) error
func (gui *Gui) handleSubmoduleInit(submodule *models.SubmoduleConfig) error {
return gui.WithWaitingStatus(gui.Tr.LcInitializingSubmoduleStatus, func() error {
gui.logAction(gui.Tr.Actions.InitialiseSubmodule)
err := gui.GitCommand.SubmoduleInit(submodule.Path)
err := gui.GitCommand.Submodule.Init(submodule.Path)
gui.handleCredentialsPopup(err)
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{SUBMODULES}})
@ -215,11 +215,11 @@ func (gui *Gui) handleResetRemoveSubmodule(submodule *models.SubmoduleConfig) er
func (gui *Gui) handleBulkSubmoduleActionsMenu() error {
menuItems := []*menuItem{
{
displayStrings: []string{gui.Tr.LcBulkInitSubmodules, style.FgGreen.Sprint(gui.GitCommand.SubmoduleBulkInitCmdObj().ToString())},
displayStrings: []string{gui.Tr.LcBulkInitSubmodules, style.FgGreen.Sprint(gui.GitCommand.Submodule.BulkInitCmdObj().ToString())},
onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.LcRunningCommand, func() error {
gui.logAction(gui.Tr.Actions.BulkInitialiseSubmodules)
err := gui.GitCommand.SubmoduleBulkInitCmdObj().Run()
err := gui.GitCommand.Submodule.BulkInitCmdObj().Run()
if err != nil {
return gui.surfaceError(err)
}
@ -229,11 +229,11 @@ func (gui *Gui) handleBulkSubmoduleActionsMenu() error {
},
},
{
displayStrings: []string{gui.Tr.LcBulkUpdateSubmodules, style.FgYellow.Sprint(gui.GitCommand.SubmoduleBulkUpdateCmdObj().ToString())},
displayStrings: []string{gui.Tr.LcBulkUpdateSubmodules, style.FgYellow.Sprint(gui.GitCommand.Submodule.BulkUpdateCmdObj().ToString())},
onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.LcRunningCommand, func() error {
gui.logAction(gui.Tr.Actions.BulkUpdateSubmodules)
if err := gui.GitCommand.SubmoduleBulkUpdateCmdObj().Run(); err != nil {
if err := gui.GitCommand.Submodule.BulkUpdateCmdObj().Run(); err != nil {
return gui.surfaceError(err)
}
@ -242,11 +242,11 @@ func (gui *Gui) handleBulkSubmoduleActionsMenu() error {
},
},
{
displayStrings: []string{gui.Tr.LcSubmoduleStashAndReset, style.FgRed.Sprintf("git stash in each submodule && %s", gui.GitCommand.SubmoduleForceBulkUpdateCmdObj().ToString())},
displayStrings: []string{gui.Tr.LcSubmoduleStashAndReset, style.FgRed.Sprintf("git stash in each submodule && %s", gui.GitCommand.Submodule.ForceBulkUpdateCmdObj().ToString())},
onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.LcRunningCommand, func() error {
gui.logAction(gui.Tr.Actions.BulkStashAndResetSubmodules)
if err := gui.GitCommand.ResetSubmodules(gui.State.Submodules); err != nil {
if err := gui.GitCommand.Submodule.ResetSubmodules(gui.State.Submodules); err != nil {
return gui.surfaceError(err)
}
@ -255,11 +255,11 @@ func (gui *Gui) handleBulkSubmoduleActionsMenu() error {
},
},
{
displayStrings: []string{gui.Tr.LcBulkDeinitSubmodules, style.FgRed.Sprint(gui.GitCommand.SubmoduleBulkDeinitCmdObj().ToString())},
displayStrings: []string{gui.Tr.LcBulkDeinitSubmodules, style.FgRed.Sprint(gui.GitCommand.Submodule.BulkDeinitCmdObj().ToString())},
onPress: func() error {
return gui.WithWaitingStatus(gui.Tr.LcRunningCommand, func() error {
gui.logAction(gui.Tr.Actions.BulkDeinitialiseSubmodules)
if err := gui.GitCommand.SubmoduleBulkDeinitCmdObj().Run(); err != nil {
if err := gui.GitCommand.Submodule.BulkDeinitCmdObj().Run(); err != nil {
return gui.surfaceError(err)
}
@ -275,7 +275,7 @@ func (gui *Gui) handleBulkSubmoduleActionsMenu() error {
func (gui *Gui) handleUpdateSubmodule(submodule *models.SubmoduleConfig) error {
return gui.WithWaitingStatus(gui.Tr.LcUpdatingSubmoduleStatus, func() error {
gui.logAction(gui.Tr.Actions.UpdateSubmodule)
err := gui.GitCommand.SubmoduleUpdate(submodule.Path)
err := gui.GitCommand.Submodule.Update(submodule.Path)
gui.handleCredentialsPopup(err)
return gui.refreshSidePanels(refreshOptions{scope: []RefreshableView{SUBMODULES}})

View File

@ -25,7 +25,7 @@ func (gui *Gui) tagsRenderToMain() error {
if tag == nil {
task = NewRenderStringTask("No tags")
} else {
cmdObj := gui.GitCommand.GetBranchGraphCmdObj(tag.Name)
cmdObj := gui.GitCommand.Branch.GetGraphCmdObj(tag.Name)
task = NewRunCommandTask(cmdObj.GetCmd())
}
@ -83,7 +83,7 @@ func (gui *Gui) handleDeleteTag(tag *models.Tag) error {
prompt: prompt,
handleConfirm: func() error {
gui.logAction(gui.Tr.Actions.DeleteTag)
if err := gui.GitCommand.DeleteTag(tag.Name); err != nil {
if err := gui.GitCommand.Tag.Delete(tag.Name); err != nil {
return gui.surfaceError(err)
}
return gui.refreshSidePanels(refreshOptions{mode: ASYNC, scope: []RefreshableView{COMMITS, TAGS}})
@ -106,7 +106,7 @@ func (gui *Gui) handlePushTag(tag *models.Tag) error {
handleConfirm: func(response string) error {
return gui.WithWaitingStatus(gui.Tr.PushingTagStatus, func() error {
gui.logAction(gui.Tr.Actions.PushTag)
err := gui.GitCommand.PushTag(response, tag.Name, gui.promptUserForCredential)
err := gui.GitCommand.Tag.Push(response, tag.Name)
gui.handleCredentialsPopup(err)
return nil

View File

@ -88,7 +88,7 @@ func (gui *Gui) reflogUndo() error {
undoEnvVars := []string{"GIT_REFLOG_ACTION=[lazygit undo]"}
undoingStatus := gui.Tr.UndoingStatus
if gui.GitCommand.WorkingTreeState() == enums.REBASE_MODE_REBASING {
if gui.GitCommand.Status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
return gui.createErrorPanel(gui.Tr.LcCantUndoWhileRebasing)
}
@ -121,7 +121,7 @@ func (gui *Gui) reflogRedo() error {
redoEnvVars := []string{"GIT_REFLOG_ACTION=[lazygit redo]"}
redoingStatus := gui.Tr.RedoingStatus
if gui.GitCommand.WorkingTreeState() == enums.REBASE_MODE_REBASING {
if gui.GitCommand.Status.WorkingTreeState() == enums.REBASE_MODE_REBASING {
return gui.createErrorPanel(gui.Tr.LcCantRedoWhileRebasing)
}
@ -176,14 +176,14 @@ func (gui *Gui) handleHardResetWithAutoStash(commitSha string, options handleHar
prompt: gui.Tr.AutoStashPrompt,
handleConfirm: func() error {
return gui.WithWaitingStatus(options.WaitingStatus, func() error {
if err := gui.GitCommand.StashSave(gui.Tr.StashPrefix + commitSha); err != nil {
if err := gui.GitCommand.Stash.Save(gui.Tr.StashPrefix + commitSha); err != nil {
return gui.surfaceError(err)
}
if err := reset(); err != nil {
return err
}
err := gui.GitCommand.StashDo(0, "pop")
err := gui.GitCommand.Stash.Pop(0)
if err := gui.refreshSidePanels(refreshOptions{}); err != nil {
return err
}

View File

@ -22,7 +22,7 @@ func (gui *Gui) handleCreateResetMenu() error {
},
onPress: func() error {
gui.logAction(gui.Tr.Actions.NukeWorkingTree)
if err := gui.GitCommand.ResetAndClean(); err != nil {
if err := gui.GitCommand.WorkingTree.ResetAndClean(); err != nil {
return gui.surfaceError(err)
}
@ -36,7 +36,7 @@ func (gui *Gui) handleCreateResetMenu() error {
},
onPress: func() error {
gui.logAction(gui.Tr.Actions.DiscardUnstagedFileChanges)
if err := gui.GitCommand.DiscardAnyUnstagedFileChanges(); err != nil {
if err := gui.GitCommand.WorkingTree.DiscardAnyUnstagedFileChanges(); err != nil {
return gui.surfaceError(err)
}
@ -50,7 +50,7 @@ func (gui *Gui) handleCreateResetMenu() error {
},
onPress: func() error {
gui.logAction(gui.Tr.Actions.RemoveUntrackedFiles)
if err := gui.GitCommand.RemoveUntrackedFiles(); err != nil {
if err := gui.GitCommand.WorkingTree.RemoveUntrackedFiles(); err != nil {
return gui.surfaceError(err)
}
@ -64,7 +64,7 @@ func (gui *Gui) handleCreateResetMenu() error {
},
onPress: func() error {
gui.logAction(gui.Tr.Actions.SoftReset)
if err := gui.GitCommand.ResetSoft("HEAD"); err != nil {
if err := gui.GitCommand.WorkingTree.ResetSoft("HEAD"); err != nil {
return gui.surfaceError(err)
}
@ -78,7 +78,7 @@ func (gui *Gui) handleCreateResetMenu() error {
},
onPress: func() error {
gui.logAction(gui.Tr.Actions.MixedReset)
if err := gui.GitCommand.ResetMixed("HEAD"); err != nil {
if err := gui.GitCommand.WorkingTree.ResetMixed("HEAD"); err != nil {
return gui.surfaceError(err)
}
@ -92,7 +92,7 @@ func (gui *Gui) handleCreateResetMenu() error {
},
onPress: func() error {
gui.logAction(gui.Tr.Actions.HardReset)
if err := gui.GitCommand.ResetHard("HEAD"); err != nil {
if err := gui.GitCommand.WorkingTree.ResetHard("HEAD"); err != nil {
return gui.surfaceError(err)
}

View File

@ -109,8 +109,8 @@ func chineseTranslationSet() TranslationSet {
Squash: "压缩",
LcPickCommit: "选择提交(变基过程中)",
LcRevertCommit: "还原提交",
OnlyRenameTopCommit: "只能从 lazygit 内部重写最高的提交。请使用 shift-R",
LcRenameCommit: "改写提交",
OnlyRewordTopCommit: "只能从 lazygit 内部重写最高的提交。请使用 shift-R",
LcRewordCommit: "改写提交",
LcDeleteCommit: "删除提交",
LcMoveDownCommit: "下移提交",
LcMoveUpCommit: "上移提交",
@ -135,7 +135,6 @@ func chineseTranslationSet() TranslationSet {
SurePopStashEntry: "您确定要应用并删除此贮藏条目吗?",
StashApply: "应用贮藏",
SureApplyStashEntry: "您确定要应用此贮藏条目?",
NoStashTo: "没有贮藏条目可以 {{.method}}",
NoTrackedStagedFilesStash: "没有可以贮藏的已跟踪/暂存文件",
StashChanges: "贮藏更改",
MergeAborted: "合并中止",

View File

@ -79,8 +79,8 @@ func dutchTranslationSet() TranslationSet {
Squash: "Squash",
LcPickCommit: "kies commit (wanneer midden in rebase)",
LcRevertCommit: "commit ongedaan maken",
OnlyRenameTopCommit: "Je kan alleen de bovenste commit hernoemen",
LcRenameCommit: "hernoem commit",
OnlyRewordTopCommit: "Je kan alleen de bovenste commit hernoemen",
LcRewordCommit: "hernoem commit",
LcDeleteCommit: "verwijder commit",
LcMoveDownCommit: "verplaats commit 1 naar beneden",
LcMoveUpCommit: "verplaats commit 1 naar boven",
@ -106,7 +106,6 @@ func dutchTranslationSet() TranslationSet {
SurePopStashEntry: "Weet je zeker dat je deze stash entry wil poppen?",
StashApply: "Stash toepassen",
SureApplyStashEntry: "Weet je zeker dat je deze stash entry wil toepassen?",
NoStashTo: "Geen stash voor {{.method}}",
NoTrackedStagedFilesStash: "Je hebt geen tracked/staged bestanden om te laten stashen",
StashChanges: "Stash veranderingen",
NoChangedFiles: "Geen veranderde bestanden",

View File

@ -93,8 +93,8 @@ type TranslationSet struct {
Squash string
LcPickCommit string
LcRevertCommit string
OnlyRenameTopCommit string
LcRenameCommit string
OnlyRewordTopCommit string
LcRewordCommit string
LcDeleteCommit string
LcMoveDownCommit string
LcMoveUpCommit string
@ -120,7 +120,6 @@ type TranslationSet struct {
SurePopStashEntry string
StashApply string
SureApplyStashEntry string
NoStashTo string
NoTrackedStagedFilesStash string
StashChanges string
MergeAborted string
@ -646,8 +645,8 @@ func EnglishTranslationSet() TranslationSet {
Squash: "Squash",
LcPickCommit: "pick commit (when mid-rebase)",
LcRevertCommit: "revert commit",
OnlyRenameTopCommit: "Can only reword topmost commit from within lazygit. Use shift+R instead",
LcRenameCommit: "reword commit",
OnlyRewordTopCommit: "Can only reword topmost commit from within lazygit. Use shift+R instead",
LcRewordCommit: "reword commit",
LcDeleteCommit: "delete commit",
LcMoveDownCommit: "move commit down one",
LcMoveUpCommit: "move commit up one",
@ -672,7 +671,6 @@ func EnglishTranslationSet() TranslationSet {
SurePopStashEntry: "Are you sure you want to pop this stash entry?",
StashApply: "Stash apply",
SureApplyStashEntry: "Are you sure you want to apply this stash entry?",
NoStashTo: "No stash to {{.method}}",
NoTrackedStagedFilesStash: "You have no tracked/staged files to stash",
StashChanges: "Stash changes",
MergeAborted: "Merge aborted",

View File

@ -69,8 +69,8 @@ func polishTranslationSet() TranslationSet {
YouNoCommitsToSquash: "Nie masz commitów do spłaszczenia",
Fixup: "Napraw",
SureFixupThisCommit: "Jesteś pewny, ze chcesz naprawić ten commit? Commit poniżej zostanie spłaszczony w górę wraz z tym",
OnlyRenameTopCommit: "Można zmienić nazwę tylko ostatniemu commitowi",
LcRenameCommit: "zmień nazwę commita",
OnlyRewordTopCommit: "Można zmienić nazwę tylko ostatniemu commitowi",
LcRewordCommit: "zmień nazwę commita",
LcRenameCommitEditor: "zmień nazwę commita w edytorze",
Error: "Błąd",
LcSelectHunk: "wybierz kawałek",
@ -84,7 +84,6 @@ func polishTranslationSet() TranslationSet {
NoStashEntries: "Brak pozycji w schowku",
StashDrop: "Porzuć schowek",
SureDropStashEntry: "Jesteś pewny, że chcesz porzucić tę pozycję w schowku?",
NoStashTo: "Brak schowka dla {{.method}}",
NoTrackedStagedFilesStash: "Nie masz śledzonych/zatwierdzonych plików do przechowania",
StashChanges: "Przechowaj zmiany",
MergeAborted: "Scalanie anulowane",

View File

@ -3,6 +3,7 @@ package utils
import (
"io/ioutil"
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/i18n"
@ -24,3 +25,7 @@ func NewDummyCommon() *common.Common {
UserConfig: config.GetDefaultConfig(),
}
}
func NewDummyGitConfig() git_config.IGitConfig {
return git_config.NewFakeGitConfig(map[string]string{})
}