mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-06-15 00:15:32 +02:00
start adding support for logging of commands
This commit is contained in:
@ -2,7 +2,6 @@ package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
@ -53,11 +52,6 @@ func (c *GitCommand) AmendHead() error {
|
||||
return c.OSCommand.RunCommand(c.AmendHeadCmdStr())
|
||||
}
|
||||
|
||||
// PrepareCommitAmendHeadSubProcess prepares a subprocess for `git commit --amend --allow-empty`
|
||||
func (c *GitCommand) PrepareCommitAmendHeadSubProcess() *exec.Cmd {
|
||||
return c.OSCommand.ShellCommandFromString(c.AmendHeadCmdStr())
|
||||
}
|
||||
|
||||
func (c *GitCommand) AmendHeadCmdStr() string {
|
||||
return "git commit --amend --no-edit --allow-empty"
|
||||
}
|
||||
|
@ -20,6 +20,5 @@ func NewDummyGitCommandWithOSCommand(osCommand *oscommands.OSCommand) *GitComman
|
||||
Tr: i18n.NewTranslationSet(utils.NewDummyLog()),
|
||||
Config: config.NewDummyAppConfig(),
|
||||
getGitConfigValue: func(string) (string, error) { return "", nil },
|
||||
removeFile: func(string) error { return nil },
|
||||
}
|
||||
}
|
||||
|
@ -2,20 +2,14 @@ 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/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// CatFile obtains the content of a file
|
||||
@ -145,7 +139,7 @@ func (c *GitCommand) DiscardAllFileChanges(file *models.File) error {
|
||||
}
|
||||
|
||||
if file.Added {
|
||||
return c.removeFile(file.Name)
|
||||
return c.OSCommand.RemoveFile(file.Name)
|
||||
}
|
||||
return c.DiscardUnstagedFileChanges(file)
|
||||
}
|
||||
@ -346,381 +340,3 @@ func (c *GitCommand) EditFileCmdStr(filename string) (string, error) {
|
||||
|
||||
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
|
||||
file *models.File
|
||||
command func(string, ...string) *exec.Cmd
|
||||
test func(error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"valid case",
|
||||
&models.File{Name: "test.txt"},
|
||||
test.CreateMockCommand(t, []*test.CommandSwapper{
|
||||
{
|
||||
Expect: `git checkout -- "test.txt"`,
|
||||
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.DiscardUnstagedFileChanges(s.file))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGitCommandDiscardAnyUnstagedFileChanges is a function.
|
||||
func TestGitCommandDiscardAnyUnstagedFileChanges(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
command func(string, ...string) *exec.Cmd
|
||||
test func(error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"valid case",
|
||||
test.CreateMockCommand(t, []*test.CommandSwapper{
|
||||
{
|
||||
Expect: `git checkout -- .`,
|
||||
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.DiscardAnyUnstagedFileChanges())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGitCommandRemoveUntrackedFiles is a function.
|
||||
func TestGitCommandRemoveUntrackedFiles(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
command func(string, ...string) *exec.Cmd
|
||||
test func(error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"valid case",
|
||||
test.CreateMockCommand(t, []*test.CommandSwapper{
|
||||
{
|
||||
Expect: `git clean -fd`,
|
||||
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.RemoveUntrackedFiles())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestEditFileCmdStr is a function.
|
||||
func TestEditFileCmdStr(t *testing.T) {
|
||||
type scenario struct {
|
||||
filename string
|
||||
command func(string, ...string) *exec.Cmd
|
||||
getenv func(string) string
|
||||
getGitConfigValue func(string) (string, error)
|
||||
test func(string, error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"test",
|
||||
func(name string, arg ...string) *exec.Cmd {
|
||||
return secureexec.Command("exit", "1")
|
||||
},
|
||||
func(env string) string {
|
||||
return ""
|
||||
},
|
||||
func(cf string) (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
func(cmdStr string, err error) {
|
||||
assert.EqualError(t, err, "No editor defined in $GIT_EDITOR, $VISUAL, $EDITOR, or git config")
|
||||
},
|
||||
},
|
||||
{
|
||||
"test",
|
||||
func(name string, arg ...string) *exec.Cmd {
|
||||
assert.Equal(t, "which", name)
|
||||
return secureexec.Command("exit", "1")
|
||||
},
|
||||
func(env string) string {
|
||||
return ""
|
||||
},
|
||||
func(cf string) (string, error) {
|
||||
return "nano", nil
|
||||
},
|
||||
func(cmdStr string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "nano \"test\"", cmdStr)
|
||||
},
|
||||
},
|
||||
{
|
||||
"test",
|
||||
func(name string, arg ...string) *exec.Cmd {
|
||||
assert.Equal(t, "which", name)
|
||||
return secureexec.Command("exit", "1")
|
||||
},
|
||||
func(env string) string {
|
||||
if env == "VISUAL" {
|
||||
return "nano"
|
||||
}
|
||||
|
||||
return ""
|
||||
},
|
||||
func(cf string) (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
func(cmdStr string, err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
"test",
|
||||
func(name string, arg ...string) *exec.Cmd {
|
||||
assert.Equal(t, "which", name)
|
||||
return secureexec.Command("exit", "1")
|
||||
},
|
||||
func(env string) string {
|
||||
if env == "EDITOR" {
|
||||
return "emacs"
|
||||
}
|
||||
|
||||
return ""
|
||||
},
|
||||
func(cf string) (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
func(cmdStr string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "emacs \"test\"", cmdStr)
|
||||
},
|
||||
},
|
||||
{
|
||||
"test",
|
||||
func(name string, arg ...string) *exec.Cmd {
|
||||
assert.Equal(t, "which", name)
|
||||
return secureexec.Command("echo")
|
||||
},
|
||||
func(env string) string {
|
||||
return ""
|
||||
},
|
||||
func(cf string) (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
func(cmdStr string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "vi \"test\"", cmdStr)
|
||||
},
|
||||
},
|
||||
{
|
||||
"file/with space",
|
||||
func(name string, args ...string) *exec.Cmd {
|
||||
assert.Equal(t, "which", name)
|
||||
return secureexec.Command("echo")
|
||||
},
|
||||
func(env string) string {
|
||||
return ""
|
||||
},
|
||||
func(cf string) (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
func(cmdStr string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "vi \"file/with space\"", cmdStr)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
gitCmd := NewDummyGitCommand()
|
||||
gitCmd.OSCommand.Command = s.command
|
||||
gitCmd.OSCommand.Getenv = s.getenv
|
||||
gitCmd.getGitConfigValue = s.getGitConfigValue
|
||||
s.test(gitCmd.EditFileCmdStr(s.filename))
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"testing"
|
||||
@ -323,7 +324,7 @@ func TestGitCommandDiscardAllFileChanges(t *testing.T) {
|
||||
var cmdsCalled *[][]string
|
||||
gitCmd := NewDummyGitCommand()
|
||||
gitCmd.OSCommand.Command, cmdsCalled = s.command()
|
||||
gitCmd.removeFile = s.removeFile
|
||||
gitCmd.OSCommand.SetRemoveFile(s.removeFile)
|
||||
s.test(cmdsCalled, gitCmd.DiscardAllFileChanges(s.file))
|
||||
})
|
||||
}
|
||||
@ -465,3 +466,381 @@ func TestGitCommandCheckoutFile(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
file *models.File
|
||||
command func(string, ...string) *exec.Cmd
|
||||
test func(error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"valid case",
|
||||
&models.File{Name: "test.txt"},
|
||||
test.CreateMockCommand(t, []*test.CommandSwapper{
|
||||
{
|
||||
Expect: `git checkout -- "test.txt"`,
|
||||
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.DiscardUnstagedFileChanges(s.file))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGitCommandDiscardAnyUnstagedFileChanges is a function.
|
||||
func TestGitCommandDiscardAnyUnstagedFileChanges(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
command func(string, ...string) *exec.Cmd
|
||||
test func(error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"valid case",
|
||||
test.CreateMockCommand(t, []*test.CommandSwapper{
|
||||
{
|
||||
Expect: `git checkout -- .`,
|
||||
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.DiscardAnyUnstagedFileChanges())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGitCommandRemoveUntrackedFiles is a function.
|
||||
func TestGitCommandRemoveUntrackedFiles(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
command func(string, ...string) *exec.Cmd
|
||||
test func(error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"valid case",
|
||||
test.CreateMockCommand(t, []*test.CommandSwapper{
|
||||
{
|
||||
Expect: `git clean -fd`,
|
||||
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.RemoveUntrackedFiles())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestEditFileCmdStr is a function.
|
||||
func TestEditFileCmdStr(t *testing.T) {
|
||||
type scenario struct {
|
||||
filename string
|
||||
command func(string, ...string) *exec.Cmd
|
||||
getenv func(string) string
|
||||
getGitConfigValue func(string) (string, error)
|
||||
test func(string, error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"test",
|
||||
func(name string, arg ...string) *exec.Cmd {
|
||||
return secureexec.Command("exit", "1")
|
||||
},
|
||||
func(env string) string {
|
||||
return ""
|
||||
},
|
||||
func(cf string) (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
func(cmdStr string, err error) {
|
||||
assert.EqualError(t, err, "No editor defined in $GIT_EDITOR, $VISUAL, $EDITOR, or git config")
|
||||
},
|
||||
},
|
||||
{
|
||||
"test",
|
||||
func(name string, arg ...string) *exec.Cmd {
|
||||
assert.Equal(t, "which", name)
|
||||
return secureexec.Command("exit", "1")
|
||||
},
|
||||
func(env string) string {
|
||||
return ""
|
||||
},
|
||||
func(cf string) (string, error) {
|
||||
return "nano", nil
|
||||
},
|
||||
func(cmdStr string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "nano \"test\"", cmdStr)
|
||||
},
|
||||
},
|
||||
{
|
||||
"test",
|
||||
func(name string, arg ...string) *exec.Cmd {
|
||||
assert.Equal(t, "which", name)
|
||||
return secureexec.Command("exit", "1")
|
||||
},
|
||||
func(env string) string {
|
||||
if env == "VISUAL" {
|
||||
return "nano"
|
||||
}
|
||||
|
||||
return ""
|
||||
},
|
||||
func(cf string) (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
func(cmdStr string, err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
"test",
|
||||
func(name string, arg ...string) *exec.Cmd {
|
||||
assert.Equal(t, "which", name)
|
||||
return secureexec.Command("exit", "1")
|
||||
},
|
||||
func(env string) string {
|
||||
if env == "EDITOR" {
|
||||
return "emacs"
|
||||
}
|
||||
|
||||
return ""
|
||||
},
|
||||
func(cf string) (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
func(cmdStr string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "emacs \"test\"", cmdStr)
|
||||
},
|
||||
},
|
||||
{
|
||||
"test",
|
||||
func(name string, arg ...string) *exec.Cmd {
|
||||
assert.Equal(t, "which", name)
|
||||
return secureexec.Command("echo")
|
||||
},
|
||||
func(env string) string {
|
||||
return ""
|
||||
},
|
||||
func(cf string) (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
func(cmdStr string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "vi \"test\"", cmdStr)
|
||||
},
|
||||
},
|
||||
{
|
||||
"file/with space",
|
||||
func(name string, args ...string) *exec.Cmd {
|
||||
assert.Equal(t, "which", name)
|
||||
return secureexec.Command("echo")
|
||||
},
|
||||
func(env string) string {
|
||||
return ""
|
||||
},
|
||||
func(cf string) (string, error) {
|
||||
return "", nil
|
||||
},
|
||||
func(cmdStr string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "vi \"file/with space\"", cmdStr)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
gitCmd := NewDummyGitCommand()
|
||||
gitCmd.OSCommand.Command = s.command
|
||||
gitCmd.OSCommand.Getenv = s.getenv
|
||||
gitCmd.getGitConfigValue = s.getGitConfigValue
|
||||
s.test(gitCmd.EditFileCmdStr(s.filename))
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,6 @@ type GitCommand struct {
|
||||
Tr *i18n.TranslationSet
|
||||
Config config.AppConfigurer
|
||||
getGitConfigValue func(string) (string, error)
|
||||
removeFile func(string) error
|
||||
DotGitDir string
|
||||
onSuccessfulContinue func() error
|
||||
PatchManager *patch.PatchManager
|
||||
@ -75,7 +74,6 @@ func NewGitCommand(log *logrus.Entry, osCommand *oscommands.OSCommand, tr *i18n.
|
||||
Repo: repo,
|
||||
Config: config,
|
||||
getGitConfigValue: getGitConfigValue,
|
||||
removeFile: os.RemoveAll,
|
||||
DotGitDir: dotGitDir,
|
||||
PushToCurrent: pushToCurrent,
|
||||
}
|
||||
@ -85,6 +83,14 @@ func NewGitCommand(log *logrus.Entry, osCommand *oscommands.OSCommand, tr *i18n.
|
||||
return gitCommand, nil
|
||||
}
|
||||
|
||||
func (c *GitCommand) WithSpan(span string) *GitCommand {
|
||||
newGitCommand := &GitCommand{}
|
||||
*newGitCommand = *c
|
||||
newGitCommand.OSCommand = c.OSCommand.WithSpan(span)
|
||||
newGitCommand.PatchManager.ApplyPatch = newGitCommand.ApplyPatch
|
||||
return newGitCommand
|
||||
}
|
||||
|
||||
func navigateToRepoRootDirectory(stat func(string) (os.FileInfo, error), chdir func(string) error) error {
|
||||
gitDir := env.GetGitDirEnv()
|
||||
if gitDir != "" {
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
// NOTE: If the return data is empty it won't written anything to stdin
|
||||
func RunCommandWithOutputLiveWrapper(c *OSCommand, command string, output func(string) string) error {
|
||||
c.Log.WithField("command", command).Info("RunCommand")
|
||||
c.LogCommand(command, true)
|
||||
cmd := c.ExecutableFromString(command)
|
||||
cmd.Env = append(cmd.Env, "LANG=en_US.UTF-8", "LC_ALL=en_US.UTF-8")
|
||||
|
||||
|
@ -40,7 +40,44 @@ type OSCommand struct {
|
||||
Command func(string, ...string) *exec.Cmd
|
||||
BeforeExecuteCmd func(*exec.Cmd)
|
||||
Getenv func(string) string
|
||||
onRunCommand func(string)
|
||||
|
||||
// callback to run before running a command, i.e. for the purposes of logging
|
||||
onRunCommand func(CmdLogEntry)
|
||||
|
||||
// something like 'Staging File': allows us to group cmd logs under a single title
|
||||
CmdLogSpan string
|
||||
|
||||
removeFile func(string) error
|
||||
}
|
||||
|
||||
// TODO: make these fields private
|
||||
type CmdLogEntry struct {
|
||||
// e.g. 'git commit -m "haha"'
|
||||
cmdStr string
|
||||
// Span is something like 'Staging File'. Multiple commands can be grouped under the same
|
||||
// span
|
||||
span string
|
||||
|
||||
// sometimes our command is direct like 'git commit', and sometimes it's a
|
||||
// command to remove a file but through Go's standard library rather than the
|
||||
// command line
|
||||
commandLine bool
|
||||
}
|
||||
|
||||
func (e CmdLogEntry) GetCmdStr() string {
|
||||
return e.cmdStr
|
||||
}
|
||||
|
||||
func (e CmdLogEntry) GetSpan() string {
|
||||
return e.span
|
||||
}
|
||||
|
||||
func (e CmdLogEntry) GetCommandLine() bool {
|
||||
return e.commandLine
|
||||
}
|
||||
|
||||
func NewCmdLogEntry(cmdStr string, span string, commandLine bool) CmdLogEntry {
|
||||
return CmdLogEntry{cmdStr: cmdStr, span: span, commandLine: commandLine}
|
||||
}
|
||||
|
||||
// NewOSCommand os command runner
|
||||
@ -52,10 +89,30 @@ func NewOSCommand(log *logrus.Entry, config config.AppConfigurer) *OSCommand {
|
||||
Command: secureexec.Command,
|
||||
BeforeExecuteCmd: func(*exec.Cmd) {},
|
||||
Getenv: os.Getenv,
|
||||
removeFile: os.RemoveAll,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *OSCommand) SetOnRunCommand(f func(string)) {
|
||||
func (c *OSCommand) WithSpan(span string) *OSCommand {
|
||||
newOSCommand := &OSCommand{}
|
||||
*newOSCommand = *c
|
||||
newOSCommand.CmdLogSpan = span
|
||||
return newOSCommand
|
||||
}
|
||||
|
||||
func (c *OSCommand) LogExecCmd(cmd *exec.Cmd) {
|
||||
c.LogCommand(strings.Join(cmd.Args, " "), true)
|
||||
}
|
||||
|
||||
func (c *OSCommand) LogCommand(cmdStr string, commandLine bool) {
|
||||
c.Log.WithField("command", cmdStr).Info("RunCommand")
|
||||
|
||||
if c.onRunCommand != nil && c.CmdLogSpan != "" {
|
||||
c.onRunCommand(NewCmdLogEntry(cmdStr, c.CmdLogSpan, commandLine))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *OSCommand) SetOnRunCommand(f func(CmdLogEntry)) {
|
||||
c.onRunCommand = f
|
||||
}
|
||||
|
||||
@ -65,6 +122,11 @@ func (c *OSCommand) SetCommand(cmd func(string, ...string) *exec.Cmd) {
|
||||
c.Command = cmd
|
||||
}
|
||||
|
||||
// To be used for testing only
|
||||
func (c *OSCommand) SetRemoveFile(f func(string) error) {
|
||||
c.removeFile = f
|
||||
}
|
||||
|
||||
func (c *OSCommand) SetBeforeExecuteCmd(cmd func(*exec.Cmd)) {
|
||||
c.BeforeExecuteCmd = cmd
|
||||
}
|
||||
@ -74,10 +136,7 @@ type RunCommandOptions struct {
|
||||
}
|
||||
|
||||
func (c *OSCommand) RunCommandWithOutputWithOptions(command string, options RunCommandOptions) (string, error) {
|
||||
c.Log.WithField("command", command).Info("RunCommand")
|
||||
if c.onRunCommand != nil {
|
||||
c.onRunCommand(command)
|
||||
}
|
||||
c.LogCommand(command, true)
|
||||
cmd := c.ExecutableFromString(command)
|
||||
|
||||
cmd.Env = append(cmd.Env, "GIT_TERMINAL_PROMPT=0") // prevents git from prompting us for input which would freeze the program
|
||||
@ -102,11 +161,8 @@ func (c *OSCommand) RunCommandWithOutput(formatString string, formatArgs ...inte
|
||||
if formatArgs != nil {
|
||||
command = fmt.Sprintf(formatString, formatArgs...)
|
||||
}
|
||||
c.Log.WithField("command", command).Info("RunCommand")
|
||||
if c.onRunCommand != nil {
|
||||
c.onRunCommand(command)
|
||||
}
|
||||
cmd := c.ExecutableFromString(command)
|
||||
c.LogExecCmd(cmd)
|
||||
output, err := sanitisedCommandOutput(cmd.CombinedOutput())
|
||||
if err != nil {
|
||||
c.Log.WithField("command", command).Error(output)
|
||||
@ -114,20 +170,9 @@ func (c *OSCommand) RunCommandWithOutput(formatString string, formatArgs ...inte
|
||||
return output, err
|
||||
}
|
||||
|
||||
func (c *OSCommand) CatFile(filename string) (string, error) {
|
||||
arr := append(c.Platform.CatCmd, filename)
|
||||
cmdStr := strings.Join(arr, " ")
|
||||
c.Log.WithField("command", cmdStr).Info("Cat")
|
||||
cmd := c.Command(arr[0], arr[1:]...)
|
||||
output, err := sanitisedCommandOutput(cmd.CombinedOutput())
|
||||
if err != nil {
|
||||
c.Log.WithField("command", cmdStr).Error(output)
|
||||
}
|
||||
return output, err
|
||||
}
|
||||
|
||||
// RunExecutableWithOutput runs an executable file and returns its output
|
||||
func (c *OSCommand) RunExecutableWithOutput(cmd *exec.Cmd) (string, error) {
|
||||
c.LogExecCmd(cmd)
|
||||
c.BeforeExecuteCmd(cmd)
|
||||
return sanitisedCommandOutput(cmd.CombinedOutput())
|
||||
}
|
||||
@ -165,6 +210,18 @@ func (c *OSCommand) RunCommandWithOutputLive(command string, output func(string)
|
||||
return RunCommandWithOutputLiveWrapper(c, command, output)
|
||||
}
|
||||
|
||||
func (c *OSCommand) CatFile(filename string) (string, error) {
|
||||
arr := append(c.Platform.CatCmd, filename)
|
||||
cmdStr := strings.Join(arr, " ")
|
||||
c.Log.WithField("command", cmdStr).Info("Cat")
|
||||
cmd := c.Command(arr[0], arr[1:]...)
|
||||
output, err := sanitisedCommandOutput(cmd.CombinedOutput())
|
||||
if err != nil {
|
||||
c.Log.WithField("command", cmdStr).Error(output)
|
||||
}
|
||||
return output, err
|
||||
}
|
||||
|
||||
// DetectUnamePass 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
|
||||
@ -201,9 +258,9 @@ func (c *OSCommand) RunCommand(formatString string, formatArgs ...interface{}) e
|
||||
// RunShellCommand runs shell commands i.e. 'sh -c <command>'. Good for when you
|
||||
// need access to the shell
|
||||
func (c *OSCommand) RunShellCommand(command string) error {
|
||||
c.Log.WithField("command", command).Info("RunShellCommand")
|
||||
|
||||
cmd := c.Command(c.Platform.Shell, c.Platform.ShellArg, command)
|
||||
c.LogExecCmd(cmd)
|
||||
|
||||
_, err := sanitisedCommandOutput(cmd.CombinedOutput())
|
||||
|
||||
return err
|
||||
@ -236,6 +293,7 @@ func sanitisedCommandOutput(output []byte, err error) (string, error) {
|
||||
|
||||
// OpenFile opens a file with the given
|
||||
func (c *OSCommand) OpenFile(filename string) error {
|
||||
c.LogCommand(fmt.Sprintf("Opening file '%s'", filename), false)
|
||||
commandTemplate := c.Config.GetUserConfig().OS.OpenCommand
|
||||
templateValues := map[string]string{
|
||||
"filename": c.Quote(filename),
|
||||
@ -248,6 +306,7 @@ func (c *OSCommand) OpenFile(filename string) error {
|
||||
|
||||
// OpenLink opens a file with the given
|
||||
func (c *OSCommand) OpenLink(link string) error {
|
||||
c.LogCommand(fmt.Sprintf("Opening link '%s'", link), false)
|
||||
commandTemplate := c.Config.GetUserConfig().OS.OpenLinkCommand
|
||||
templateValues := map[string]string{
|
||||
"link": c.Quote(link),
|
||||
@ -265,6 +324,7 @@ func (c *OSCommand) PrepareSubProcess(cmdName string, commandArgs ...string) *ex
|
||||
if cmd != nil {
|
||||
cmd.Env = append(os.Environ(), "GIT_OPTIONAL_LOCKS=0")
|
||||
}
|
||||
c.LogExecCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -290,6 +350,7 @@ func (c *OSCommand) Quote(message string) string {
|
||||
|
||||
// AppendLineToFile adds a new line in file
|
||||
func (c *OSCommand) AppendLineToFile(filename, line string) error {
|
||||
c.LogCommand(fmt.Sprintf("Appending '%s' to file '%s'", line, filename), false)
|
||||
f, err := os.OpenFile(filename, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
|
||||
if err != nil {
|
||||
return utils.WrapError(err)
|
||||
@ -310,6 +371,7 @@ func (c *OSCommand) CreateTempFile(filename, content string) (string, error) {
|
||||
c.Log.Error(err)
|
||||
return "", utils.WrapError(err)
|
||||
}
|
||||
c.LogCommand(fmt.Sprintf("Creating temp file '%s'", tmpfile.Name()), false)
|
||||
|
||||
if _, err := tmpfile.WriteString(content); err != nil {
|
||||
c.Log.Error(err)
|
||||
@ -325,6 +387,7 @@ func (c *OSCommand) CreateTempFile(filename, content string) (string, error) {
|
||||
|
||||
// CreateFileWithContent creates a file with the given content
|
||||
func (c *OSCommand) CreateFileWithContent(path string, content string) error {
|
||||
c.LogCommand(fmt.Sprintf("Creating file '%s'", path), false)
|
||||
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
|
||||
c.Log.Error(err)
|
||||
return err
|
||||
@ -340,6 +403,7 @@ func (c *OSCommand) CreateFileWithContent(path string, content string) error {
|
||||
|
||||
// Remove removes a file or directory at the specified path
|
||||
func (c *OSCommand) Remove(filename string) error {
|
||||
c.LogCommand(fmt.Sprintf("Removing '%s'", filename), false)
|
||||
err := os.RemoveAll(filename)
|
||||
return utils.WrapError(err)
|
||||
}
|
||||
@ -360,6 +424,7 @@ func (c *OSCommand) FileExists(path string) (bool, error) {
|
||||
// before running it
|
||||
func (c *OSCommand) RunPreparedCommand(cmd *exec.Cmd) error {
|
||||
c.BeforeExecuteCmd(cmd)
|
||||
c.LogExecCmd(cmd)
|
||||
out, err := cmd.CombinedOutput()
|
||||
outString := string(out)
|
||||
c.Log.Info(outString)
|
||||
@ -383,12 +448,16 @@ func (c *OSCommand) GetLazygitPath() string {
|
||||
|
||||
// PipeCommands runs a heap of commands and pipes their inputs/outputs together like A | B | C
|
||||
func (c *OSCommand) PipeCommands(commandStrings ...string) error {
|
||||
|
||||
cmds := make([]*exec.Cmd, len(commandStrings))
|
||||
|
||||
logCmdStr := ""
|
||||
for i, str := range commandStrings {
|
||||
if i > 0 {
|
||||
logCmdStr += " | "
|
||||
}
|
||||
logCmdStr += str
|
||||
cmds[i] = c.ExecutableFromString(str)
|
||||
}
|
||||
c.LogCommand(logCmdStr, true)
|
||||
|
||||
for i := 0; i < len(cmds)-1; i++ {
|
||||
stdout, err := cmds[i].StdoutPipe()
|
||||
@ -479,5 +548,12 @@ func RunLineOutputCmd(cmd *exec.Cmd, onLine func(line string) (bool, error)) err
|
||||
}
|
||||
|
||||
func (c *OSCommand) CopyToClipboard(str string) error {
|
||||
c.LogCommand(fmt.Sprintf("Copying '%s' to clipboard", utils.TruncateWithEllipsis(str, 40)), false)
|
||||
return clipboard.WriteAll(str)
|
||||
}
|
||||
|
||||
func (c *OSCommand) RemoveFile(path string) error {
|
||||
c.LogCommand(fmt.Sprintf("Deleting path '%s'", path), false)
|
||||
|
||||
return c.removeFile(path)
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ func (c *GitCommand) MovePatchToSelectedCommit(commits []*models.Commit, sourceC
|
||||
return c.GenericMergeOrRebaseAction("rebase", "continue")
|
||||
}
|
||||
|
||||
func (c *GitCommand) PullPatchIntoIndex(commits []*models.Commit, commitIdx int, p *patch.PatchManager, stash bool) error {
|
||||
func (c *GitCommand) MovePatchIntoIndex(commits []*models.Commit, commitIdx int, p *patch.PatchManager, stash bool) error {
|
||||
if stash {
|
||||
if err := c.StashSave(c.Tr.StashPrefix + commits[commitIdx].Sha); err != nil {
|
||||
return err
|
||||
|
@ -21,7 +21,7 @@ func (c *GitCommand) ShowStashEntryCmdStr(index int) string {
|
||||
// StashSaveStagedChanges 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 {
|
||||
|
||||
// wrap in 'writing', which uses a mutex
|
||||
if err := c.RunCommand("git stash --keep-index"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
Reference in New Issue
Block a user