summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2018-09-19 19:17:05 +1000
committerJesse Duffield <jessedduffield@gmail.com>2018-09-19 19:17:05 +1000
commitbaa9eff318bf7ac7f3971491c7bbed1657488fdd (patch)
tree45b1d9082ed672fc41b5295a36ebae9c59dbe912
parente91fb21233f1c725c34e03b43ac763d8b4c61889 (diff)
parentfcaf4e339c4e76aafb9e89114a7eabe1bcbe0362 (diff)
Merge branch 'hotfix/cursor-positioning' into feature/recent-repos
-rw-r--r--.circleci/config.yml5
-rw-r--r--pkg/commands/git.go41
-rw-r--r--pkg/commands/git_test.go362
-rw-r--r--pkg/gui/commits_panel.go1
4 files changed, 369 insertions, 40 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml
index ddfd88844..14c848257 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -3,7 +3,6 @@ jobs:
build:
docker:
- image: circleci/golang:1.11
-
working_directory: /go/src/github.com/jesseduffield/lazygit
steps:
- checkout
@@ -29,7 +28,7 @@ jobs:
fi
- restore_cache:
keys:
- - pkg-cache-{{ checksum "Gopkg.lock" }}-v2
+ - pkg-cache-{{ checksum "Gopkg.lock" }}-v3
- run:
name: Run tests
command: |
@@ -44,7 +43,7 @@ jobs:
command: |
bash <(curl -s https://codecov.io/bash)
- save_cache:
- key: pkg-cache-{{ checksum "Gopkg.lock" }}-v2
+ key: pkg-cache-{{ checksum "Gopkg.lock" }}-v3
paths:
- ~/.cache/go-build
diff --git a/pkg/commands/git.go b/pkg/commands/git.go
index 017bd815f..c37ca9b47 100644
--- a/pkg/commands/git.go
+++ b/pkg/commands/git.go
@@ -65,6 +65,7 @@ type GitCommand struct {
Tr *i18n.Localizer
getGlobalGitConfig func(string) (string, error)
getLocalGitConfig func(string) (string, error)
+ removeFile func(string) error
}
// NewGitCommand it runs git commands
@@ -231,13 +232,18 @@ func (c *GitCommand) UpstreamDifferenceCount() (string, string) {
}
// GetCommitsToPush Returns the sha's of the commits that have not yet been pushed
-// to the remote branch of the current branch
-func (c *GitCommand) GetCommitsToPush() []string {
- pushables, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..head --abbrev-commit")
+// to the remote branch of the current branch, a map is returned to ease look up
+func (c *GitCommand) GetCommitsToPush() map[string]bool {
+ pushables := map[string]bool{}
+ o, err := c.OSCommand.RunCommandWithOutput("git rev-list @{u}..head --abbrev-commit")
if err != nil {
- return []string{}
+ return pushables
}
- return utils.SplitLines(pushables)
+ for _, p := range utils.SplitLines(o) {
+ pushables[p] = true
+ }
+
+ return pushables
}
// RenameCommit renames the topmost commit with the given name
@@ -410,15 +416,15 @@ func (c *GitCommand) IsInMergeState() (bool, error) {
func (c *GitCommand) RemoveFile(file *File) error {
// if the file isn't tracked, we assume you want to delete it
if file.HasStagedChanges {
- if err := c.OSCommand.RunCommand("git reset -- " + file.Name); err != nil {
+ if err := c.OSCommand.RunCommand(fmt.Sprintf("git reset -- %s", file.Name)); err != nil {
return err
}
}
if !file.Tracked {
- return os.RemoveAll(file.Name)
+ return c.removeFile(file.Name)
}
// if the file is tracked, we assume you want to just check it out
- return c.OSCommand.RunCommand("git checkout -- " + file.Name)
+ return c.OSCommand.RunCommand(fmt.Sprintf("git checkout -- %s", file.Name))
}
// Checkout checks out a branch, with --force if you set the force arg to true
@@ -427,7 +433,7 @@ func (c *GitCommand) Checkout(branch string, force bool) error {
if force {
forceArg = "--force "
}
- return c.OSCommand.RunCommand("git checkout " + forceArg + branch)
+ return c.OSCommand.RunCommand(fmt.Sprintf("git checkout %s %s", forceArg, branch))
}
// AddPatch prepares a subprocess for adding a patch by patch
@@ -450,16 +456,7 @@ func (c *GitCommand) PrepareCommitAmendSubProcess() *exec.Cmd {
// Currently it limits the result to 100 commits, but when we get async stuff
// working we can do lazy loading
func (c *GitCommand) GetBranchGraph(branchName string) (string, error) {
- return c.OSCommand.RunCommandWithOutput("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 " + branchName)
-}
-
-func includesString(list []string, a string) bool {
- for _, b := range list {
- if b == a {
- return true
- }
- }
- return false
+ return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 %s", branchName))
}
// GetCommits obtains the commits of the current branch
@@ -468,11 +465,10 @@ func (c *GitCommand) GetCommits() []*Commit {
log := c.GetLog()
commits := []*Commit{}
// now we can split it up and turn it into commits
- lines := utils.SplitLines(log)
- for _, line := range lines {
+ for _, line := range utils.SplitLines(log) {
splitLine := strings.Split(line, " ")
sha := splitLine[0]
- pushed := includesString(pushables, sha)
+ _, pushed := pushables[sha]
commits = append(commits, &Commit{
Sha: sha,
Name: strings.Join(splitLine[1:], " "),
@@ -493,6 +489,7 @@ func (c *GitCommand) GetLog() string {
// assume if there is an error there are no commits yet for this branch
return ""
}
+
return result
}
diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go
index d1c27cd30..6e3b81bf5 100644
--- a/pkg/commands/git_test.go
+++ b/pkg/commands/git_test.go
@@ -61,6 +61,7 @@ func newDummyGitCommand() *GitCommand {
Tr: i18n.NewLocalizer(newDummyLog()),
getGlobalGitConfig: func(string) (string, error) { return "", nil },
getLocalGitConfig: func(string) (string, error) { return "", nil },
+ removeFile: func(string) error { return nil },
}
}
@@ -551,7 +552,7 @@ func TestGitCommandUpstreamDifferentCount(t *testing.T) {
{
"Can't retrieve pushable count",
func(string, ...string) *exec.Cmd {
- return exec.Command("exit", "1")
+ return exec.Command("test")
},
func(pushableCount string, pullableCount string) {
assert.EqualValues(t, "?", pushableCount)
@@ -562,7 +563,7 @@ func TestGitCommandUpstreamDifferentCount(t *testing.T) {
"Can't retrieve pullable count",
func(cmd string, args ...string) *exec.Cmd {
if args[1] == "head..@{u}" {
- return exec.Command("exit", "1")
+ return exec.Command("test")
}
return exec.Command("echo")
@@ -601,17 +602,17 @@ func TestGitCommandGetCommitsToPush(t *testing.T) {
type scenario struct {
testName string
command func(string, ...string) *exec.Cmd
- test func([]string)
+ test func(map[string]bool)
}
scenarios := []scenario{
{
"Can't retrieve pushable commits",
func(string, ...string) *exec.Cmd {
- return exec.Command("exit", "1")
+ return exec.Command("test")
},
- func(pushables []string) {
- assert.EqualValues(t, []string{}, pushables)
+ func(pushables map[string]bool) {
+ assert.EqualValues(t, map[string]bool{}, pushables)
},
},
{
@@ -619,9 +620,9 @@ func TestGitCommandGetCommitsToPush(t *testing.T) {
func(cmd string, args ...string) *exec.Cmd {
return exec.Command("echo", "8a2bb0e\n78976bc")
},
- func(pushables []string) {
+ func(pushables map[string]bool) {
assert.Len(t, pushables, 2)
- assert.EqualValues(t, []string{"8a2bb0e", "78976bc"}, pushables)
+ assert.EqualValues(t, map[string]bool{"8a2bb0e": true, "78976bc": true}, pushables)
},
},
}
@@ -872,7 +873,7 @@ func TestGitCommandCommit(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"commit", "-m", "test"}, args)
- return exec.Command("exit", "1")
+ return exec.Command("test")
},
func(string) (string, error) {
return "false", nil
@@ -935,7 +936,7 @@ func TestGitCommandPush(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"push", "-u", "origin", "test"}, args)
- return exec.Command("exit", "1")
+ return exec.Command("test")
},
false,
func(err error) {
@@ -967,7 +968,7 @@ func TestGitCommandSquashPreviousTwoCommits(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"reset", "--soft", "HEAD^"}, args)
- return exec.Command("exit", "1")
+ return exec.Command("test")
},
func(err error) {
assert.NotNil(t, err)
@@ -983,7 +984,7 @@ func TestGitCommandSquashPreviousTwoCommits(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"commit", "--amend", "-m", "test"}, args)
- return exec.Command("exit", "1")
+ return exec.Command("test")
},
func(err error) {
assert.NotNil(t, err)
@@ -1031,7 +1032,7 @@ func TestGitCommandSquashFixupCommit(t *testing.T) {
return func(cmd string, args ...string) *exec.Cmd {
cmdsCalled = append(cmdsCalled, args)
if len(args) > 0 && args[0] == "checkout" {
- return exec.Command("exit", "1")
+ return exec.Command("test")
}
return exec.Command("echo")
@@ -1165,7 +1166,7 @@ func TestGitCommandIsInMergeState(t *testing.T) {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"status", "--untracked-files=all"}, args)
- return exec.Command("exit", "1")
+ return exec.Command("test")
},
func(isInMergeState bool, err error) {
assert.Error(t, err)
@@ -1219,6 +1220,339 @@ func TestGitCommandIsInMergeState(t *testing.T) {
}
}
+func TestGitCommandRemoveFile(t *testing.T) {
+ type scenario struct {
+ testName string
+ command func() (func(string, ...string) *exec.Cmd, *[][]string)
+ test func(*[][]string, error)
+ file File
+ removeFile func(string) error
+ }
+
+ scenarios := []scenario{
+ {
+ "An error occurred when resetting",
+ func() (func(string, ...string) *exec.Cmd, *[][]string) {
+ cmdsCalled := [][]string{}
+ return func(cmd string, args ...string) *exec.Cmd {
+ cmdsCalled = append(cmdsCalled, args)
+
+ return exec.Command("test")
+ }, &cmdsCalled
+ },
+ func(cmdsCalled *[][]string, err error) {
+ assert.Error(t, err)
+ assert.Len(t, *cmdsCalled, 1)
+ assert.EqualValues(t, *cmdsCalled, [][]string{
+ {"reset", "--", "test"},
+ })
+ },
+ File{
+ Name: "test",
+ HasStagedChanges: true,
+ },
+ func(string) error {
+ return nil
+ },
+ },
+ {
+ "An error occurred when removing file",
+ func() (func(string, ...string) *exec.Cmd, *[][]string) {
+ cmdsCalled := [][]string{}
+ return func(cmd string, args ...string) *exec.Cmd {
+ cmdsCalled = append(cmdsCalled, args)
+
+ return exec.Command("test")
+ }, &cmdsCalled
+ },
+ func(cmdsCalled *[][]string, err error) {
+ assert.Error(t, err)
+ assert.EqualError(t, err, "an error occurred when removing file")
+ assert.Len(t, *cmdsCalled, 0)
+ },
+ File{
+ Name: "test",
+ Tracked: false,
+ },
+ func(string) error {
+ return fmt.Errorf("an error occurred when removing file")
+ },
+ },
+ {
+ "An error occurred with checkout",
+ func() (func(string, ...string) *exec.Cmd, *[][]string) {
+ cmdsCalled := [][]string{}
+ return func(cmd string, args ...string) *exec.Cmd {
+ cmdsCalled = append(cmdsCalled, args)
+
+ return exec.Command("test")
+ }, &cmdsCalled
+ },
+ func(cmdsCalled *[][]string, err error) {
+ assert.Error(t, err)
+ assert.Len(t, *cmdsCalled, 1)
+ assert.EqualValues(t, *cmdsCalled, [][]string{
+ {"checkout", "--", "test"},
+ })
+ },
+ File{
+ Name: "test",
+ Tracked: true,
+ HasStagedChanges: false,
+ },
+ func(string) error {
+ return nil
+ },
+ },
+ {
+ "Checkout only",
+ func() (func(string, ...string) *exec.Cmd, *[][]string) {
+ cmdsCalled := [][]string{}
+ return func(cmd string, args ...string) *exec.Cmd {
+ cmdsCalled = append(cmdsCalled, args)
+
+ return exec.Command("echo")
+ }, &cmdsCalled
+ },
+ func(cmdsCalled *[][]string, err error) {
+ assert.NoError(t, err)
+ assert.Len(t, *cmdsCalled, 1)
+ assert.EqualValues(t, *cmdsCalled, [][]string{
+ {"checkout", "--", "test"},
+ })
+ },
+ File{
+ Name: "test",
+ Tracked: true,
+ HasStagedChanges: false,
+ },
+ func(string) error {
+ return nil
+ },
+ },
+ {
+ "Reset and checkout",
+ func() (func(string, ...string) *exec.Cmd, *[][]string) {
+ cmdsCalled := [][]string{}
+ return func(cmd string, args ...string) *exec.Cmd {
+ cmdsCalled = append(cmdsCalled, args)
+
+ return exec.Command("echo")
+ }, &cmdsCalled
+ },
+ func(cmdsCalled *[][]string, err error) {
+ assert.NoError(t, err)
+ assert.Len(t, *cmdsCalled, 2)
+ assert.EqualValues(t, *cmdsCalled, [][]string{
+ {"reset", "--", "test"},
+ {"checkout", "--", "test"},
+ })
+ },
+ File{
+ Name: "test",
+ Tracked: true,
+ HasStagedChanges: true,
+ },
+ func(string) error {
+ return nil
+ },
+ },
+ {
+ "Reset and remove",
+ func() (func(string, ...string) *exec.Cmd, *[][]string) {
+ cmdsCalled := [][]string{}
+ return func(cmd string, args ...string) *exec.Cmd {
+ cmdsCalled = append(cmdsCalled, args)
+
+ return exec.Command("echo")
+ }, &cmdsCalled
+ },
+ func(cmdsCalled *[][]string, err error) {
+ assert.NoError(t, err)
+ assert.Len(t, *cmdsCalled, 1)
+ assert.EqualValues(t, *cmdsCalled, [][]string{
+ {"reset", "--", "test"},
+ })
+ },
+ File{
+ Name: "test",
+ Tracked: false,
+ HasStagedChanges: true,
+ },
+ func(filename string) error {
+ assert.Equal(t, "test", filename)
+ return nil
+ },
+ },
+ {
+ "Remove only",
+ func() (func(string, ...string) *exec.Cmd, *[][]string) {
+ cmdsCalled := [][]string{}
+ return func(cmd string, args ...string) *exec.Cmd {
+ cmdsCalled = append(cmdsCalled, args)
+
+ return exec.Command("echo")
+ }, &cmdsCalled
+ },
+ func(cmdsCalled *[][]string, err error) {
+ assert.NoError(t, err)
+ assert.Len(t, *cmdsCalled, 0)
+ },
+ File{
+ Name: "test",
+ Tracked: false,
+ HasStagedChanges: false,
+ },
+ func(filename string) error {
+ assert.Equal(t, "test", filename)
+ return nil
+ },
+ },
+ }
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ var cmdsCalled *[][]string
+ gitCmd := newDummyGitCommand()
+ gitCmd.OSCommand.command, cmdsCalled = s.command()
+ gitCmd.removeFile = s.removeFile
+ s.test(cmdsCalled, gitCmd.RemoveFile(s.file))
+ })
+ }
+}
+
+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 exec.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 exec.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", s.force))
+ })
+ }
+}
+
+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", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium", "-100", "test"}, args)
+
+ return exec.Command("echo")
+ }
+
+ _, err := gitCmd.GetBranchGraph("test")
+ assert.NoError(t, err)
+}
+
+func TestGitCommandGetCommits(t *testing.T) {
+ type scenario struct {
+ testName string
+ command func(string, ...string) *exec.Cmd
+ test func([]Commit)
+ }
+
+ scenarios := []scenario{
+ {
+ "No data found",
+ func(cmd string, args ...string) *exec.Cmd {
+ assert.EqualValues(t, "git", cmd)
+
+ switch args[0] {
+ case "rev-list":
+ assert.EqualValues(t, []string{"rev-list", "@{u}..head", "--abbrev-commit"}, args)
+ return exec.Command("echo")
+ case "log":
+ assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args)
+ return exec.Command("echo")
+ }
+
+ return nil
+ },
+ func(commits []Commit) {
+ assert.Len(t, commits, 0)
+ },
+ },
+ {
+ "GetCommits returns 2 commits, 1 pushed the other not",
+ func(cmd string, args ...string) *exec.Cmd {
+ assert.EqualValues(t, "git", cmd)
+
+ switch args[0] {
+ case "rev-list":
+ assert.EqualValues(t, []string{"rev-list", "@{u}..head", "--abbrev-commit"}, args)
+ return exec.Command("echo", "8a2bb0e")
+ case "log":
+ assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args)
+ return exec.Command("echo", "8a2bb0e commit 1\n78976bc commit 2")
+ }
+
+ return nil
+ },
+ func(commits []Commit) {
+ assert.Len(t, commits, 2)
+ assert.EqualValues(t, []Commit{
+ {
+ Sha: "8a2bb0e",
+ Name: "commit 1",
+ Pushed: true,
+ DisplayString: "8a2bb0e commit 1",
+ },
+ {
+ Sha: "78976bc",
+ Name: "commit 2",
+ Pushed: false,
+ DisplayString: "78976bc commit 2",
+ },
+ }, commits)
+ },
+ },
+ }
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ gitCmd := newDummyGitCommand()
+ gitCmd.OSCommand.command = s.command
+ s.test(gitCmd.GetCommits())
+ })
+ }
+}
+
func TestGitCommandDiff(t *testing.T) {
gitCommand := newDummyGitCommand()
assert.NoError(t, test.GenerateRepo("lots_of_diffs.sh"))
diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go
index f0b96586a..7c09559ff 100644
--- a/pkg/gui/commits_panel.go
+++ b/pkg/gui/commits_panel.go
@@ -141,7 +141,6 @@ func (gui *Gui) handleRenameCommit(g *gocui.Gui, v *gocui.View) error {
}
return gui.handleCommitSelect(g, v)
})
- return nil
}
func (gui *Gui) handleRenameCommitEditor(g *gocui.Gui, v *gocui.View) error {