diff options
-rw-r--r-- | pkg/commands/commit.go | 6 | ||||
-rw-r--r-- | pkg/commands/git.go | 59 | ||||
-rw-r--r-- | pkg/commands/git_test.go | 182 | ||||
-rw-r--r-- | pkg/gui/commits_panel.go | 10 |
4 files changed, 245 insertions, 12 deletions
diff --git a/pkg/commands/commit.go b/pkg/commands/commit.go index 37c3e9525..54e94ef60 100644 --- a/pkg/commands/commit.go +++ b/pkg/commands/commit.go @@ -9,17 +9,21 @@ type Commit struct { Sha string Name string Pushed bool + Merged bool DisplayString string } func (c *Commit) GetDisplayStrings() []string { red := color.New(color.FgRed) - yellow := color.New(color.FgYellow) + yellow := color.New(color.FgGreen) + green := color.New(color.FgYellow) white := color.New(color.FgWhite) shaColor := yellow if c.Pushed { shaColor = red + } else if !c.Merged { + shaColor = green } return []string{shaColor.Sprint(c.Sha), white.Sprint(c.Name)} diff --git a/pkg/commands/git.go b/pkg/commands/git.go index 87ee69c8c..c7de19b3a 100644 --- a/pkg/commands/git.go +++ b/pkg/commands/git.go @@ -267,6 +267,14 @@ func (c *GitCommand) NewBranch(name string) error { return c.OSCommand.RunCommand(fmt.Sprintf("git checkout -b %s", name)) } +func (c *GitCommand) CurrentBranchName() (string, error) { + output, err := c.OSCommand.RunCommandWithOutput("git symbolic-ref --short HEAD") + if err != nil { + return "", err + } + return utils.TrimTrailingNewline(output), nil +} + // DeleteBranch delete branch func (c *GitCommand) DeleteBranch(branch string, force bool) error { command := "git branch -d" @@ -464,24 +472,63 @@ func (c *GitCommand) GetBranchGraph(branchName string) (string, error) { return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git log --graph --color --abbrev-commit --decorate --date=relative --pretty=medium -100 %s", branchName)) } +func (c *GitCommand) getMergeBase() (string, error) { + currentBranch, err := c.CurrentBranchName() + if err != nil { + return "", err + } + + baseBranch := "master" + if strings.HasPrefix(currentBranch, "feature/") { + baseBranch = "develop" + } + + output, err := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git merge-base HEAD %s", baseBranch)) + if err != nil { + // swallowing error because it's not a big deal; probably because there are no commits yet + c.Log.Error(err) + } + return output, nil +} + // GetCommits obtains the commits of the current branch -func (c *GitCommand) GetCommits() []*Commit { +func (c *GitCommand) GetCommits() ([]*Commit, error) { pushables := c.GetCommitsToPush() log := c.GetLog() - commits := []*Commit{} + + lines := utils.SplitLines(log) + commits := make([]*Commit, len(lines)) // now we can split it up and turn it into commits - for _, line := range utils.SplitLines(log) { + for i, line := range lines { splitLine := strings.Split(line, " ") sha := splitLine[0] _, pushed := pushables[sha] - commits = append(commits, &Commit{ + commits[i] = &Commit{ Sha: sha, Name: strings.Join(splitLine[1:], " "), Pushed: pushed, DisplayString: strings.Join(splitLine, " "), - }) + } + } + return c.setCommitMergedStatuses(commits) +} + +func (c *GitCommand) setCommitMergedStatuses(commits []*Commit) ([]*Commit, error) { + ancestor, err := c.getMergeBase() + if err != nil { + return nil, err + } + if ancestor == "" { + return commits, nil + } + passedAncestor := false + for i, commit := range commits { + if strings.HasPrefix(ancestor, commit.Sha) { + passedAncestor = true + } + commits[i].Merged = passedAncestor } - return commits + return commits, nil } // GetLog gets the git log (currently limited to 30 commits for performance diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go index e5fd539b9..25820558a 100644 --- a/pkg/commands/git_test.go +++ b/pkg/commands/git_test.go @@ -1496,7 +1496,7 @@ func TestGitCommandGetCommits(t *testing.T) { type scenario struct { testName string command func(string, ...string) *exec.Cmd - test func([]*Commit) + test func([]*Commit, error) } scenarios := []scenario{ @@ -1512,11 +1512,18 @@ func TestGitCommandGetCommits(t *testing.T) { case "log": assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args) return exec.Command("echo") + case "merge-base": + assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args) + return exec.Command("test") + case "symbolic-ref": + assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) + return exec.Command("echo", "master") } return nil }, - func(commits []*Commit) { + func(commits []*Commit, err error) { + assert.NoError(t, err) assert.Len(t, commits, 0) }, }, @@ -1532,28 +1539,65 @@ func TestGitCommandGetCommits(t *testing.T) { case "log": assert.EqualValues(t, []string{"log", "--oneline", "-30"}, args) return exec.Command("echo", "8a2bb0e commit 1\n78976bc commit 2") + case "merge-base": + assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args) + return exec.Command("echo", "78976bc") + case "symbolic-ref": + assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) + return exec.Command("echo", "master") } return nil }, - func(commits []*Commit) { + func(commits []*Commit, err error) { + assert.NoError(t, err) assert.Len(t, commits, 2) assert.EqualValues(t, []*Commit{ { Sha: "8a2bb0e", Name: "commit 1", Pushed: true, + Merged: false, DisplayString: "8a2bb0e commit 1", }, { Sha: "78976bc", Name: "commit 2", Pushed: false, + Merged: true, DisplayString: "78976bc commit 2", }, }, commits) }, }, + { + "GetCommits bubbles up an error from setCommitMergedStatuses", + 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") + case "merge-base": + assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args) + return exec.Command("echo", "78976bc") + case "symbolic-ref": + assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) + // here's where we are returning the error + return exec.Command("test") + } + + return nil + }, + func(commits []*Commit, err error) { + assert.Error(t, err) + assert.Len(t, commits, 0) + }, + }, } for _, s := range scenarios { @@ -1668,3 +1712,135 @@ func TestGitCommandDiff(t *testing.T) { }) } } + +func TestGitCommandGetMergeBase(t *testing.T) { + type scenario struct { + testName string + command func(string, ...string) *exec.Cmd + test func(string, error) + } + + scenarios := []scenario{ + { + "swallows an error if the call to merge-base returns an error", + 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 exec.Command("echo", "master") + case "merge-base": + assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args) + return exec.Command("test") + } + return nil + }, + func(output string, err error) { + assert.NoError(t, err) + assert.EqualValues(t, "", output) + }, + }, + { + "returns the commit when master", + 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 exec.Command("echo", "master") + case "merge-base": + assert.EqualValues(t, []string{"merge-base", "HEAD", "master"}, args) + return exec.Command("echo", "blah") + } + return nil + }, + func(output string, err error) { + assert.NoError(t, err) + assert.Equal(t, "blah\n", output) + }, + }, + { + "checks against develop when a feature branch", + 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 exec.Command("echo", "feature/test") + case "merge-base": + assert.EqualValues(t, []string{"merge-base", "HEAD", "develop"}, args) + return exec.Command("echo", "blah") + } + return nil + }, + func(output string, err error) { + assert.NoError(t, err) + assert.Equal(t, "blah\n", output) + }, + }, + { + "bubbles up error if there is one", + func(cmd string, args ...string) *exec.Cmd { + return exec.Command("test") + }, + func(output string, err error) { + assert.Error(t, err) + assert.Equal(t, "", output) + }, + }, + } + + for _, s := range scenarios { + t.Run(s.testName, func(t *testing.T) { + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command = s.command + s.test(gitCmd.getMergeBase()) + }) + } +} + +func TestGitCommandCurrentBranchName(t *testing.T) { + type scenario struct { + testName string + command func(string, ...string) *exec.Cmd + test func(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) + assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) + return exec.Command("echo", "master") + }, + func(output string, err error) { + assert.NoError(t, err) + assert.EqualValues(t, "master", output) + }, + }, + { + "bubbles up error if there is one", + func(cmd string, args ...string) *exec.Cmd { + assert.Equal(t, "git", cmd) + assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args) + return exec.Command("test") + }, + func(output string, err error) { + assert.Error(t, err) + assert.EqualValues(t, "", output) + }, + }, + } + + for _, s := range scenarios { + t.Run(s.testName, func(t *testing.T) { + gitCmd := newDummyGitCommand() + gitCmd.OSCommand.command = s.command + s.test(gitCmd.CurrentBranchName()) + }) + } +} diff --git a/pkg/gui/commits_panel.go b/pkg/gui/commits_panel.go index 91bb334ff..ee7f191a7 100644 --- a/pkg/gui/commits_panel.go +++ b/pkg/gui/commits_panel.go @@ -11,13 +11,19 @@ import ( func (gui *Gui) refreshCommits(g *gocui.Gui) error { g.Update(func(*gocui.Gui) error { - gui.State.Commits = gui.GitCommand.GetCommits() + commits, err := gui.GitCommand.GetCommits() + if err != nil { + return err + } + + gui.State.Commits = commits v, err := g.View("commits") if err != nil { - panic(err) + return err } v.Clear() + list, err := utils.RenderList(gui.State.Commits) if err != nil { return err |