summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2021-04-10 11:40:42 +1000
committerJesse Duffield <jessedduffield@gmail.com>2021-04-10 11:54:38 +1000
commite42e7e5cbd9d075ee24ae8f91ba9e12bdd42fafc (patch)
tree61d65a544c056b3bf0384cf6954b81b292eb4b07
parent93fac1f3124f87009091230f61cc13b5e5473cb5 (diff)
fix commit amend
-rw-r--r--go.mod2
-rw-r--r--go.sum2
-rw-r--r--pkg/commands/branches_test.go338
-rw-r--r--pkg/commands/commits.go28
-rw-r--r--pkg/commands/commits_test.go111
-rw-r--r--pkg/commands/config.go14
-rw-r--r--pkg/commands/config_test.go70
-rw-r--r--pkg/commands/files.go397
-rw-r--r--pkg/commands/files_test.go467
-rw-r--r--pkg/commands/git_test.go1852
-rw-r--r--pkg/commands/loading_files_test.go122
-rw-r--r--pkg/commands/loading_stash_test.go61
-rw-r--r--pkg/commands/patch_rebases.go16
-rw-r--r--pkg/commands/rebasing.go2
-rw-r--r--pkg/commands/rebasing_test.go96
-rw-r--r--pkg/commands/stash_entries_test.go35
-rw-r--r--pkg/commands/sync.go15
-rw-r--r--pkg/commands/sync_test.go98
-rw-r--r--pkg/gui/commit_message_panel.go34
-rw-r--r--pkg/gui/commits_panel.go2
-rw-r--r--pkg/gui/custom_commands.go2
-rw-r--r--pkg/gui/files_panel.go26
-rw-r--r--pkg/gui/git_flow.go4
-rw-r--r--pkg/gui/gpg.go41
-rw-r--r--pkg/gui/gui.go40
-rw-r--r--pkg/gui/rebase_options_panel.go2
-rw-r--r--pkg/i18n/english.go2
-rw-r--r--vendor/github.com/jesseduffield/gocui/gui.go44
-rw-r--r--vendor/github.com/jesseduffield/gocui/loader.go13
-rw-r--r--vendor/github.com/jesseduffield/gocui/tcell_driver.go6
-rw-r--r--vendor/modules.txt2
31 files changed, 1961 insertions, 1983 deletions
diff --git a/go.mod b/go.mod
index 6ca0594de..566576679 100644
--- a/go.mod
+++ b/go.mod
@@ -20,7 +20,7 @@ require (
github.com/imdario/mergo v0.3.11
github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/go-git/v5 v5.1.2-0.20201006095850-341962be15a4
- github.com/jesseduffield/gocui v0.3.1-0.20210409121040-210802112d8a
+ github.com/jesseduffield/gocui v0.3.1-0.20210410011117-a2bb4baca390
github.com/jesseduffield/termbox-go v0.0.0-20200823212418-a2289ed6aafe // indirect
github.com/jesseduffield/yaml v2.1.0+incompatible
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
diff --git a/go.sum b/go.sum
index 7bcf90094..0c9ed0165 100644
--- a/go.sum
+++ b/go.sum
@@ -108,6 +108,8 @@ github.com/jesseduffield/gocui v0.3.1-0.20210406065942-1b0c68414064 h1:Oe+QJuUIO
github.com/jesseduffield/gocui v0.3.1-0.20210406065942-1b0c68414064/go.mod h1:QWq79xplEoyhQO+dgpk3sojjTVRKjQklyTlzm5nC5Kg=
github.com/jesseduffield/gocui v0.3.1-0.20210409121040-210802112d8a h1:ocrSuZxQIgWWt27b+rjiyIIPz6fzfFeoL5Q4cpa2cAo=
github.com/jesseduffield/gocui v0.3.1-0.20210409121040-210802112d8a/go.mod h1:QWq79xplEoyhQO+dgpk3sojjTVRKjQklyTlzm5nC5Kg=
+github.com/jesseduffield/gocui v0.3.1-0.20210410011117-a2bb4baca390 h1:Es72JiUjt01TtvqCugdvOR91baB3DhuWF1DNuxA0frA=
+github.com/jesseduffield/gocui v0.3.1-0.20210410011117-a2bb4baca390/go.mod h1:QWq79xplEoyhQO+dgpk3sojjTVRKjQklyTlzm5nC5Kg=
github.com/jesseduffield/termbox-go v0.0.0-20200823212418-a2289ed6aafe h1:qsVhCf2RFyyKIUe/+gJblbCpXMUki9rZrHuEctg6M/E=
github.com/jesseduffield/termbox-go v0.0.0-20200823212418-a2289ed6aafe/go.mod h1:anMibpZtqNxjDbxrcDEAwSdaJ37vyUeM1f/M4uekib4=
github.com/jesseduffield/yaml v2.1.0+incompatible h1:HWQJ1gIv2zHKbDYNp0Jwjlj24K8aqpFHnMCynY1EpmE=
diff --git a/pkg/commands/branches_test.go b/pkg/commands/branches_test.go
new file mode 100644
index 000000000..85c477523
--- /dev/null
+++ b/pkg/commands/branches_test.go
@@ -0,0 +1,338 @@
+package commands
+
+import (
+ "os/exec"
+ "testing"
+
+ "github.com/jesseduffield/lazygit/pkg/secureexec"
+ "github.com/jesseduffield/lazygit/pkg/test"
+ "github.com/stretchr/testify/assert"
+)
+
+// TestGitCommandGetCommitDifferences is a function.
+func TestGitCommandGetCommitDifferences(t *testing.T) {
+ type scenario struct {
+ testName string
+ command func(string, ...string) *exec.Cmd
+ test func(string, string)
+ }
+
+ scenarios := []scenario{
+ {
+ "Can't retrieve pushable count",
+ func(string, ...string) *exec.Cmd {
+ return secureexec.Command("test")
+ },
+ func(pushableCount string, pullableCount string) {
+ assert.EqualValues(t, "?", pushableCount)
+ assert.EqualValues(t, "?", pullableCount)
+ },
+ },
+ {
+ "Can't retrieve pullable count",
+ func(cmd string, args ...string) *exec.Cmd {
+ if args[1] == "HEAD..@{u}" {
+ return secureexec.Command("test")
+ }
+
+ return secureexec.Command("echo")
+ },
+ func(pushableCount string, pullableCount string) {
+ assert.EqualValues(t, "?", pushableCount)
+ assert.EqualValues(t, "?", pullableCount)
+ },
+ },
+ {
+ "Retrieve pullable and pushable count",
+ func(cmd string, args ...string) *exec.Cmd {
+ if args[1] == "HEAD..@{u}" {
+ return secureexec.Command("echo", "10")
+ }
+
+ return secureexec.Command("echo", "11")
+ },
+ func(pushableCount string, pullableCount string) {
+ assert.EqualValues(t, "11", pushableCount)
+ assert.EqualValues(t, "10", pullableCount)
+ },
+ },
+ }
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ gitCmd := NewDummyGitCommand()
+ gitCmd.OSCommand.Command = s.command
+ s.test(gitCmd.GetCommitDifferences("HEAD", "@{u}"))
+ })
+ }
+}
+
+// TestGitCommandNewBranch is a function.
+func TestGitCommandNewBranch(t *testing.T) {
+ gitCmd := NewDummyGitCommand()
+ gitCmd.OSCommand.Command = func(cmd string, args ...string) *exec.Cmd {
+ assert.EqualValues(t, "git", cmd)
+ assert.EqualValues(t, []string{"checkout", "-b", "test", "master"}, args)
+
+ return secureexec.Command("echo")
+ }
+
+ assert.NoError(t, gitCmd.NewBranch("test", "master"))
+}
+
+// TestGitCommandDeleteBranch is a function.
+func TestGitCommandDeleteBranch(t *testing.T) {
+ type scenario struct {
+ testName string
+ branch string
+ force bool
+ command func(string, ...string) *exec.Cmd
+ test func(error)
+ }
+
+ scenarios := []scenario{
+ {
+ "Delete a branch",
+ "test",
+ false,
+ func(cmd string, args ...string) *exec.Cmd {
+ assert.EqualValues(t, "git", cmd)
+ assert.EqualValues(t, []string{"branch", "-d", "test"}, args)
+
+ return secureexec.Command("echo")
+ },
+ func(err error) {
+ assert.NoError(t, err)
+ },
+ },
+ {
+ "Force delete a branch",
+ "test",
+ true,
+ func(cmd string, args ...string) *exec.Cmd {
+ assert.EqualValues(t, "git", cmd)
+ assert.EqualValues(t, []string{"branch", "-D", "test"}, args)
+
+ return secureexec.Command("echo")
+ },
+ func(err error) {
+ assert.NoError(t, err)
+ },
+ },
+ }
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ gitCmd := NewDummyGitCommand()
+ gitCmd.OSCommand.Command = s.command
+ s.test(gitCmd.DeleteBranch(s.branch, s.force))
+ })
+ }
+}
+
+// TestGitCommandMerge is a function.
+func TestGitCommandMerge(t *testing.T) {
+ gitCmd := NewDummyGitCommand()
+ gitCmd.OSCommand.Command = func(cmd string, args ...string) *exec.Cmd {
+ assert.EqualValues(t, "git", cmd)
+ assert.EqualValues(t, []string{"merge", "--no-edit", "test"}, args)
+
+ return secureexec.Command("echo")
+ }
+
+ assert.NoError(t, gitCmd.Merge("test", MergeOpts{}))
+}
+
+// TestGitCommandCheckout is a function.
+func TestGitCommandCheckout(t *testing.T) {
+ type scenario struct {
+ testName string
+ command func(string, ...string) *exec.Cmd
+ test func(error)
+ force bool
+ }
+
+ scenarios := []scenario{
+ {
+ "Checkout",
+ func(cmd string, args ...string) *exec.Cmd {
+ assert.EqualValues(t, "git", cmd)
+ assert.EqualValues(t, []string{"checkout", "test"}, args)
+
+ return secureexec.Command("echo")
+ },
+ func(err error) {
+ assert.NoError(t, err)
+ },
+ false,
+ },
+ {
+ "Checkout forced",
+ func(cmd string, args ...string) *exec.Cmd {
+ assert.EqualValues(t, "git", cmd)
+ assert.EqualValues(t, []string{"checkout", "--force", "test"}, args)
+
+ return secureexec.Command("echo")
+ },
+ func(err error) {
+ assert.NoError(t, err)
+ },
+ true,
+ },
+ }
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ gitCmd := NewDummyGitCommand()
+ gitCmd.OSCommand.Command = s.command
+ s.test(gitCmd.Checkout("test", CheckoutOptions{Force: s.force}))
+ })
+ }
+}
+
+// TestGitCommandGetBranchGraph is a function.
+func TestGitCommandGetBranchGraph(t *testing.T) {
+ gitCmd := NewDummyGitCommand()
+ gitCmd.OSCommand.Command = func(cmd string, args ...string) *exec.Cmd {
+ assert.EqualValues(t, "git", cmd)
+ assert.EqualValues(t, []string{"log", "--graph", "--color=always", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium", "test", "--"}, args)
+ return secureexec.Command("echo")
+ }
+ _, err := gitCmd.GetBranchGraph("test")
+ assert.NoError(t, err)
+}
+
+func TestGitCommandGetAllBranchGraph(t *testing.T) {
+ gitCmd := NewDummyGitCommand()
+ gitCmd.OSCommand.Command = func(cmd string, args ...string) *exec.Cmd {
+ assert.EqualValues(t, "git", cmd)
+ assert.EqualValues(t, []string{"log", "--graph", "--all", "--color=always", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium"}, args)
+ return secureexec.Command("echo")
+ }
+ cmdStr := gitCmd.Config.GetUserConfig().Git.AllBranchesLogCmd
+ _, err := gitCmd.OSCommand.RunCommandWithOutput(cmdStr)
+ assert.NoError(t, err)
+}
+
+// TestGitCommandCurrentBranchName is a function.
+func TestGitCommandCurrentBranchName(t *testing.T) {
+ type scenario struct {
+ testName string
+ command func(string, ...string) *exec.Cmd
+ test func(string, string, error)
+ }
+
+ scenarios := []scenario{
+ {
+ "says we are on the master branch if we are",
+ func(cmd string, args ...string) *exec.Cmd {
+ assert.Equal(t, "git", cmd)
+ return secureexec.Command("echo", "master")
+ },
+ func(name string, displayname string, err error) {
+ assert.NoError(t, err)
+ assert.EqualValues(t, "master", name)
+ assert.EqualValues(t, "master", displayname)
+ },
+ },
+ {
+ "falls back to git `git branch --contains` if symbolic-ref fails",
+ func(cmd string, args ...string) *exec.Cmd {
+ assert.EqualValues(t, "git", cmd)
+
+ switch args[0] {
+ case "symbolic-ref":
+ assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
+ return secureexec.Command("test")
+ case "branch":
+ assert.EqualValues(t, []string{"branch", "--contains"}, args)
+ return secureexec.Command("echo", "* master")
+ }
+
+ return nil
+ },
+ func(name string, displayname string, err error) {
+ assert.NoError(t, err)
+ assert.EqualValues(t, "master", name)
+ assert.EqualValues(t, "master", displayname)
+ },
+ },
+ {
+ "handles a detached head",
+ func(cmd string, args ...string) *exec.Cmd {
+ assert.EqualValues(t, "git", cmd)
+
+ switch args[0] {
+ case "symbolic-ref":
+ assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
+ return secureexec.Command("test")
+ case "branch":
+ assert.EqualValues(t, []string{"branch", "--contains"}, args)
+ return secureexec.Command("echo", "* (HEAD detached at 123abcd)")
+ }
+
+ return nil
+ },
+ func(name string, displayname string, err error) {
+ assert.NoError(t, err)
+ assert.EqualValues(t, "123abcd", name)
+ assert.EqualValues(t, "(HEAD detached at 123abcd)", displayname)
+ },
+ },
+ {
+ "bubbles up error if there is one",
+ func(cmd string, args ...string) *exec.Cmd {
+ assert.Equal(t, "git", cmd)
+ return secureexec.Command("test")
+ },
+ func(name string, displayname string, err error) {
+ assert.Error(t, err)
+ assert.EqualValues(t, "", name)
+ assert.EqualValues(t, "", displayname)
+ },
+ },
+ }
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ gitCmd := NewDummyGitCommand()
+ gitCmd.OSCommand.Command = s.command
+ s.test(gitCmd.CurrentBranchName())
+ })
+ }
+}
+
+// TestGitCommandResetHard is a function.
+func TestGitCommandResetHard(t *testing.T) {
+ type scenario struct {
+ testName string
+ ref string
+ command func(string, ...string) *exec.Cmd
+ test func(error)
+ }
+
+ scenarios := []scenario{
+ {
+ "valid case",
+ "HEAD",
+ test.CreateMockCommand(t, []*test.CommandSwapper{
+ {
+ Expect: `git reset --hard HEAD`,
+ Replace: "echo",
+ },
+ }),
+ func(err error) {
+ assert.NoError(t, err)
+ },
+ },
+ }
+
+ gitCmd := NewDummyGitCommand()
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ gitCmd.OSCommand.Command = s.command
+ s.test(gitCmd.ResetHard(s.ref))
+ })
+ }
+}
diff --git a/pkg/commands/commits.go b/pkg/commands/commits.go
index 74043518d..3582131a6 100644
--- a/pkg/commands/commits.go
+++ b/pkg/commands/commits.go
@@ -19,20 +19,19 @@ func (c *GitCommand) ResetToCommit(sha string, strength string, options oscomman
return c.OSCommand.RunCommandWithOptions(fmt.Sprintf("git reset --%s %s", strength, sha), options)
}
-// Commit commits to git
-func (c *GitCommand) Commit(message string, flags string) (*exec.Cmd, error) {
+func (c *GitCommand) CommitCmdStr(message string, flags string) string {
splitMessage := strings.Split(message, "\n")
lineArgs := ""
for _, line := range splitMessage {
lineArgs += fmt.Sprintf(" -m %s", c.OSCommand.Quote(line))
}
- command := fmt.Sprintf("git commit %s%s", flags, lineArgs)
- if c.usingGpg() {
- return c.OSCommand.ShellCommandFromString(command), nil
+ flagsStr := ""
+ if flags != "" {
+ flagsStr = fmt.Sprintf(" %s", flags)
}
- return nil, c.OSCommand.RunCommand(command)
+ return fmt.Sprintf("git commit%s%s", flagsStr, lineArgs)
}
// Get the subject of the HEAD commit
@@ -50,18 +49,17 @@ func (c *GitCommand) GetCommitMessage(commitSha string) (string, error) {
}
// AmendHead amends HEAD with whatever is staged in your working tree
-func (c *GitCommand) AmendHead() (*exec.Cmd, error) {
- command := "git commit --amend --no-edit --allow-empty"
- if c.usingGpg() {
- return c.OSCommand.ShellCommandFromString(command), nil
- }
+func (c *GitCommand) AmendHead() error {
+ return c.OSCommand.RunCommand(c.AmendHeadCmdStr())
+}
- return nil, c.OSCommand.RunCommand(command)
+// PrepareCommitAmendHeadSubProcess prepares a subprocess for `git commit --amend --allow-empty`
+func (c *GitCommand) PrepareCommitAmendHeadSubProcess() *exec.Cmd {
+ return c.OSCommand.ShellCommandFromString(c.AmendHeadCmdStr())
}
-// PrepareCommitAmendSubProcess prepares a subprocess for `git commit --amend --allow-empty`
-func (c *GitCommand) PrepareCommitAmendSubProcess() *exec.Cmd {
- return c.OSCommand.PrepareSubProcess("git", "commit", "--amend", "--allow-empty")
+func (c *GitCommand) AmendHeadCmdStr() string {
+ return "git commit --amend --no-edit --allow-empty"
}
func (c *GitCommand) ShowCmdStr(sha string, filterPath string) string {
diff --git a/pkg/commands/commits_test.go b/pkg/commands/commits_test.go
new file mode 100644
index 000000000..f979e9507
--- /dev/null
+++ b/pkg/commands/commits_test.go
@@ -0,0 +1,111 @@
+package commands
+
+import (
+ "os/exec"
+ "testing"
+
+ "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
+ "github.com/jesseduffield/lazygit/pkg/secureexec"
+ "github.com/jesseduffield/lazygit/pkg/test"
+ "github.com/stretchr/testify/assert"
+)
+
+// TestGitCommandRenameCommit is a function.
+func TestGitCommandRenameCommit(t *testing.T) {
+ gitCmd := NewDummyGitCommand()
+ gitCmd.OSCommand.Command = func(cmd string, args ...string) *exec.Cmd {
+ assert.EqualValues(t, "git", cmd)
+ assert.EqualValues(t, []string{"commit", "--allow-empty", "--amend", "--only", "-m", "test"}, args)
+
+ return secureexec.Command("echo")
+ }
+
+ assert.NoError(t, gitCmd.RenameCommit("test"))
+}
+
+// TestGitCommandResetToCommit is a function.
+func TestGitCommandResetToCommit(t *testing.T) {
+ gitCmd := NewDummyGitCommand()
+ gitCmd.OSCommand.Command = func(cmd string, args ...string) *exec.Cmd {
+ assert.EqualValues(t, "git", cmd)
+ assert.EqualValues(t, []string{"reset", "--hard", "78976bc"}, args)
+
+ return secureexec.Command("echo")
+ }
+
+ assert.NoError(t, gitCmd.ResetToCommit("78976bc", "hard", oscommands.RunCommandOptions{}))
+}
+
+// TestGitCommandCommitStr is a function.
+func TestGitCommandCommitStr(t *testing.T) {
+ type scenario struct {
+ testName string
+ message string
+ flags string
+ expected string
+ }
+
+ scenarios := []scenario{
+ {
+ testName: "Commit",
+ message: "test",
+ flags: "",
+ expected: "git commit -m \"test\"",
+ },
+ {
+ testName: "Commit with --no-verify flag",
+ message: "test",
+ flags: "--no-verify",
+ expected: "git commit --no-verify -m \"test\"",
+ },
+ {
+ testName: "Commit with multiline message",
+ message: "line1\nline2",
+ flags: "",
+ expected: "git commit -m \"line1\" -m \"line2\"",
+ },
+ }
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ gitCmd := NewDummyGitCommand()
+ cmdStr := gitCmd.CommitCmdStr(s.message, s.flags)
+ assert.Equal(t, s.expected, cmdStr)
+ })
+ }
+}
+
+// TestGitCommandCreateFixupCommit is a function.
+func TestGitCommandCreateFixupCommit(t *testing.T) {
+ type scenario struct {
+ testName string
+ sha string
+ command func(string, ...string) *exec.Cmd
+ test func(error)
+ }
+
+ scenarios := []scenario{
+ {
+ "valid case",
+ "12345",
+ test.CreateMockCommand(t, []*test.CommandSwapper{
+ {
+ Expect: `git commit --fixup=12345`,
+ Replace: "echo",
+ },
+ }),
+ func(err error) {
+ assert.NoError(t, err)
+ },
+ },
+ }
+
+ gitCmd := NewDummyGitCommand()
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ gitCmd.OSCommand.Command = s.command
+ s.test(gitCmd.CreateFixupCommit(s.sha))
+ })
+ }
+}
diff --git a/pkg/commands/config.go b/pkg/commands/config.go
index 0bb310e6d..9600c3403 100644
--- a/pkg/commands/config.go
+++ b/pkg/commands/config.go
@@ -46,3 +46,17 @@ func (c *GitCommand) GetConfigValue(key string) string {
output, _ := c.getGitConfigValue(key)
return output
}
+
+// 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.Config.GetUserConfig().Git.OverrideGpg
+ if overrideGpg {
+ return false
+ }
+
+ gpgsign := c.GetConfigValue("commit.gpgsign")
+ value := strings.ToLower(gpgsign)
+
+ return value == "true" || value == "1" || value == "yes" || value == "on"
+}
diff --git a/pkg/commands/config_test.go b/pkg/commands/config_test.go
new file mode 100644
index 000000000..c16b53901
--- /dev/null
+++ b/pkg/commands/config_test.go
@@ -0,0 +1,70 @@
+package commands
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+// TestGitCommandUsingGpg is a function.
+func TestGitCommandUsingGpg(t *testing.T) {
+ type scenario struct {
+ testName string
+ getGitConfigValue func(string) (string, error)
+ test func(bool)
+ }
+
+ scenarios := []scenario{
+ {
+ "Option global and local config commit.gpgsign is not set",
+ func(string) (string, error) { return "", nil },
+ func(gpgEnabled bool) {
+ assert.False(t, gpgEnabled)
+ },
+ },
+ {
+ "Option commit.gpgsign is true",
+ func(string) (string, error) {
+ return "True", nil
+ },
+ func(gpgEnabled bool) {
+ assert.True(t, gpgEnabled)
+ },
+ },
+ {
+ "Option commit.gpgsign is on",
+ func(string) (string, error) {
+ return "ON", nil
+ },
+ func(gpgEnabled bool) {
+ assert.True(t, gpgEnabled)
+ },
+ },
+ {
+ "Option commit.gpgsign is yes",
+ func(string) (string, error) {
+ return "YeS", nil
+ },
+ func(gpgEnabled bool) {
+ assert.True(t, gpgEnabled)
+ },
+ },
+ {
+ "Option commit.gpgsign is 1",
+ func(string) (string, error) {
+ return "1", nil
+ },
+ func(gpgEnabled bool) {
+ assert.True(t, gpgEnabled)
+ },
+ },
+ }
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ gitCmd := NewDummyGitCommand()
+ gitCmd.getGitConfigValue = s.getGitConfigValue
+ s.test(gitCmd.UsingGpg())
+ })
+ }
+}
diff --git a/pkg/commands/files.go b/pkg/commands/files.go
index fcf3ea51e..579a3a252 100644
--- a/pkg/commands/files.go
+++ b/pkg/commands/files.go
@@ -2,16 +2,20 @@ package commands
import (
"fmt"
+ "io/ioutil"
"os"
"os/exec"
"path/filepath"
+ "testing"
"time"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
+ "github.com/jesseduffield/lazygit/pkg/secureexec"
+ "github.com/jesseduffield/lazygit/pkg/test"
"github.com/jesseduffield/lazygit/pkg/utils"
- "github.com/mgutz/str"
+ "github.com/stretchr/testify/assert"
)
// CatFile obtains the content of a file
@@ -267,10 +271,7 @@ func (c *GitCommand) DiscardOldFileChanges(commits []*models.Commit, commitIndex
}
// amend the commit
- cmd, err := c.AmendHead()
- if cmd != nil {
- return errors.New("received unexpected pointer to cmd")
- }
+ err := c.AmendHead()
if err != nil {
return err
}
@@ -314,9 +315,7 @@ func (c *GitCommand) ResetAndClean() error {
return c.RemoveUntrackedFiles()
}
-// EditFile opens a file in a subprocess using whatever editor is available,
-// falling back to core.editor, GIT_EDITOR, VISUAL, EDITOR, then vi
-func (c *GitCommand) EditFile(filename string) (*exec.Cmd, error) {
+func (c *GitCommand) EditFileCmdStr(filename string) (string, error) {
editor := c.GetConfigValue("core.editor")
if editor == "" {
@@ -334,10 +333,386 @@ func (c *GitCommand) EditFile(filename string) (*exec.Cmd, error) {
}
}
if editor == "" {
- return nil, errors.New("No editor defined in $GIT_EDITOR, $VISUAL, $EDITOR, or git config")
+ return "", errors.New("No editor defined in $GIT_EDITOR, $VISUAL, $EDITOR, or git config")
+ }
+
+ return fmt.Sprintf("%s %s", editor, c.OSCommand.Quote(filename)), nil
+}
+
+func TestGitCommandApplyPatch(t *testing.T) {
+ type scenario struct {
+ testName string
+ command func(string, ...string) *exec.Cmd
+ test func(error)
+ }
+
+ scenarios := []scenario{
+ {
+ "valid case",
+ func(cmd string, args ...string) *exec.Cmd {
+ assert.Equal(t, "git", cmd)
+ assert.EqualValues(t, []string{"apply", "--cached"}, args[0:2])
+ filename := args[2]
+ content, err := ioutil.ReadFile(filename)
+ assert.NoError(t, err)
+
+ assert.Equal(t, "test", string(content))
+
+ return secureexec.Command("echo", "done")
+ },
+ func(err error) {
+ assert.NoError(t, err)
+ },
+ },
+ {
+ "command returns error",
+ func(cmd string, args ...string) *exec.Cmd {
+ assert.Equal(t, "git", cmd)
+ assert.EqualValues(t, []string{"apply", "--cached"}, args[0:2])
+ filename := args[2]
+ // TODO: Ideally we want to mock out OSCommand here so that we're not
+ // double handling testing it's CreateTempFile functionality,
+ // but it is going to take a bit of work to make a proper mock for it
+ // so I'm leaving it for another PR
+ content, err := ioutil.ReadFile(filename)
+ assert.NoError(t, err)
+
+ assert.Equal(t, "test", string(content))
+
+ return secureexec.Command("test")
+ },
+ func(err error) {
+ assert.Error(t, err)
+ },
+ },
+ }
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ gitCmd := NewDummyGitCommand()
+ gitCmd.OSCommand.Command = s.command
+ s.test(gitCmd.ApplyPatch("test", "cached"))
+ })
+ }
+}
+
+// TestGitCommandDiscardOldFileChanges is a function.
+func TestGitCommandDiscardOldFileChanges(t *testing.T) {
+ type scenario struct {
+ testName string
+ getGitConfigValue func(string) (string, error)
+ commits []*models.Commit
+ commitIndex int
+ fileName string
+ command func(string, ...string) *exec.Cmd
+ test func(error)
+ }
+
+ scenarios := []scenario{
+ {
+ "returns error when index outside of range of commits",
+ func(string) (string, error) {
+ return "", nil
+ },
+ []*models.Commit{},
+ 0,
+ "test999.txt",
+ nil,
+ func(err error) {
+ assert.Error(t, err)
+ },
+ },
+ {
+ "returns error when using gpg",
+ func(string) (string, error) {
+ return "true", nil
+ },
+ []*models.Commit{{Name: "commit", Sha: "123456"}},
+ 0,
+ "test999.txt",
+ nil,
+ func(err error) {
+ assert.Error(t, err)
+ },
+ },
+ {
+ "checks out file if it already existed",
+ func(string) (string, error) {
+ return "", nil
+ },
+ []*models.Commit{
+ {Name: "commit", Sha: "123456"},
+ {Name: "commit2", Sha: "abcdef"},
+ },
+ 0,
+ "test999.txt",
+ test.CreateMockCommand(t, []*test.CommandSwapper{
+ {
+ Expect: "git rebase --interactive --autostash --keep-empty abcdef",
+ Replace: "echo",
+ },
+ {
+ Expect: "git cat-file -e HEAD^:test999.txt",
+ Replace: "echo",
+ },
+ {
+ Expect: "git checkout HEAD^ test999.txt",
+ Replace: "echo",
+ },
+ {
+ Expect: "git commit --amend --no-edit --allow-empty",
+ Replace: "echo",
+ },
+ {
+ Expect: "git rebase --continue",
+ Replace: "echo",
+ },
+ }),
+ 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
+ }
+
+ gitCmd := NewDummyGitCommand()
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ gitCmd.OSCommand.Command = s.command
+ gitCmd.getGitConfigValue = s.getGitConfigValue
+ s.test(gitCmd.DiscardOldFileChanges(s.commits, s.commitIndex, s.fileName))
+ })
+ }
+}
+
+// TestGitCommandDiscardUnstagedFileChanges is a function.
+func TestGitCommandDiscardUnstagedFileChanges(t *testing.T) {
+ type scenario struct {
+ testName string