mirror of
https://github.com/jesseduffield/lazygit.git
synced 2024-12-14 11:23:09 +02:00
601 lines
16 KiB
Go
601 lines
16 KiB
Go
package git_commands
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"regexp"
|
|
"testing"
|
|
|
|
"github.com/go-errors/errors"
|
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
|
"github.com/jesseduffield/lazygit/pkg/config"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestWorkingTreeStageFile(t *testing.T) {
|
|
runner := oscommands.NewFakeRunner(t).
|
|
Expect(`git add -- "test.txt"`, "", nil)
|
|
|
|
instance := buildWorkingTreeCommands(commonDeps{runner: runner})
|
|
|
|
assert.NoError(t, instance.StageFile("test.txt"))
|
|
runner.CheckForMissingCalls()
|
|
}
|
|
|
|
func TestWorkingTreeStageFiles(t *testing.T) {
|
|
runner := oscommands.NewFakeRunner(t).
|
|
Expect(`git add -- "test.txt" "test2.txt"`, "", nil)
|
|
|
|
instance := buildWorkingTreeCommands(commonDeps{runner: runner})
|
|
|
|
assert.NoError(t, instance.StageFiles([]string{"test.txt", "test2.txt"}))
|
|
runner.CheckForMissingCalls()
|
|
}
|
|
|
|
func TestWorkingTreeUnstageFile(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).
|
|
Expect(`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).
|
|
Expect(`git reset HEAD -- "test.txt"`, "", nil),
|
|
test: func(err error) {
|
|
assert.NoError(t, err)
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
s := s
|
|
t.Run(s.testName, func(t *testing.T) {
|
|
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
|
|
s.test(instance.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 TestWorkingTreeDiscardAllFileChanges(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).
|
|
Expect(`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).
|
|
Expect(`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).
|
|
Expect(`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).
|
|
Expect(`git reset -- "test"`, "", nil).
|
|
Expect(`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).
|
|
Expect(`git reset -- "test"`, "", nil).
|
|
Expect(`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).
|
|
Expect(`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 {
|
|
s := s
|
|
t.Run(s.testName, func(t *testing.T) {
|
|
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, removeFile: s.removeFile})
|
|
err := instance.DiscardAllFileChanges(s.file)
|
|
|
|
if s.expectedError == "" {
|
|
assert.Nil(t, err)
|
|
} else {
|
|
assert.Equal(t, s.expectedError, err.Error())
|
|
}
|
|
s.runner.CheckForMissingCalls()
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWorkingTreeDiff(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).
|
|
Expect(`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).
|
|
Expect(`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).
|
|
Expect(`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).
|
|
Expect(`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).
|
|
Expect(`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).
|
|
Expect(`git diff --submodule --no-ext-diff --unified=17 --color=always -- "test.txt"`, expectedResult, nil),
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
s := s
|
|
t.Run(s.testName, func(t *testing.T) {
|
|
userConfig := config.GetDefaultConfig()
|
|
userConfig.Git.DiffContextSize = s.contextSize
|
|
|
|
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, userConfig: userConfig})
|
|
result := instance.WorktreeFileDiff(s.file, s.plain, s.cached, s.ignoreWhitespace)
|
|
assert.Equal(t, expectedResult, result)
|
|
s.runner.CheckForMissingCalls()
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWorkingTreeShowFileDiff(t *testing.T) {
|
|
type scenario struct {
|
|
testName string
|
|
from string
|
|
to string
|
|
reverse bool
|
|
plain bool
|
|
ignoreWhitespace 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,
|
|
ignoreWhitespace: false,
|
|
contextSize: 3,
|
|
runner: oscommands.NewFakeRunner(t).
|
|
Expect(`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,
|
|
ignoreWhitespace: false,
|
|
contextSize: 123,
|
|
runner: oscommands.NewFakeRunner(t).
|
|
Expect(`git diff --submodule --no-ext-diff --unified=123 --no-renames --color=always 1234567890 0987654321 -- "test.txt"`, expectedResult, nil),
|
|
},
|
|
{
|
|
testName: "Default case (ignore whitespace)",
|
|
from: "1234567890",
|
|
to: "0987654321",
|
|
reverse: false,
|
|
plain: false,
|
|
ignoreWhitespace: true,
|
|
contextSize: 3,
|
|
runner: oscommands.NewFakeRunner(t).
|
|
Expect(`git diff --submodule --no-ext-diff --unified=3 --no-renames --color=always 1234567890 0987654321 --ignore-all-space -- "test.txt"`, expectedResult, nil),
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
s := s
|
|
t.Run(s.testName, func(t *testing.T) {
|
|
userConfig := config.GetDefaultConfig()
|
|
userConfig.Git.DiffContextSize = s.contextSize
|
|
|
|
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, userConfig: userConfig})
|
|
|
|
result, err := instance.ShowFileDiff(s.from, s.to, s.reverse, "test.txt", s.plain, s.ignoreWhitespace)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, expectedResult, result)
|
|
s.runner.CheckForMissingCalls()
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWorkingTreeCheckoutFile(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 {
|
|
s := s
|
|
t.Run(s.testName, func(t *testing.T) {
|
|
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
|
|
|
|
s.test(instance.CheckoutFile(s.commitSha, s.fileName))
|
|
s.runner.CheckForMissingCalls()
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWorkingTreeApplyPatch(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)
|
|
cmdStr := cmdObj.ToString()
|
|
matches := re.FindStringSubmatch(cmdStr)
|
|
assert.Equal(t, 2, len(matches), fmt.Sprintf("unexpected command: %s", cmdStr))
|
|
|
|
filename := matches[1]
|
|
|
|
content, err := os.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 {
|
|
s := s
|
|
t.Run(s.testName, func(t *testing.T) {
|
|
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
|
|
s.test(instance.ApplyPatch("test", "cached"))
|
|
s.runner.CheckForMissingCalls()
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWorkingTreeDiscardUnstagedFileChanges(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 {
|
|
s := s
|
|
t.Run(s.testName, func(t *testing.T) {
|
|
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
|
|
s.test(instance.DiscardUnstagedFileChanges(s.file))
|
|
s.runner.CheckForMissingCalls()
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWorkingTreeDiscardAnyUnstagedFileChanges(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 {
|
|
s := s
|
|
t.Run(s.testName, func(t *testing.T) {
|
|
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
|
|
s.test(instance.DiscardAnyUnstagedFileChanges())
|
|
s.runner.CheckForMissingCalls()
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWorkingTreeRemoveUntrackedFiles(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 {
|
|
s := s
|
|
t.Run(s.testName, func(t *testing.T) {
|
|
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
|
|
s.test(instance.RemoveUntrackedFiles())
|
|
s.runner.CheckForMissingCalls()
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestWorkingTreeResetHard(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 {
|
|
s := s
|
|
t.Run(s.testName, func(t *testing.T) {
|
|
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
|
|
s.test(instance.ResetHard(s.ref))
|
|
})
|
|
}
|
|
}
|