summaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
authorJesse Duffield <jessedduffield@gmail.com>2018-09-25 19:05:24 +1000
committerGitHub <noreply@github.com>2018-09-25 19:05:24 +1000
commit7164f372661794eedd749eb41cc1265c86110913 (patch)
tree39751808681b8908989443b278cd5e90aac97a52 /pkg
parent61f0801bd366e959437676ae72b2d9fe98c4b8af (diff)
parent80d6bbef8661932ee0a665961b44a681c811ac36 (diff)
Merge branch 'master' into feature/commit-amend
Diffstat (limited to 'pkg')
-rw-r--r--pkg/app/app.go4
-rw-r--r--pkg/commands/branch.go6
-rw-r--r--pkg/commands/commit.go26
-rw-r--r--pkg/commands/file.go36
-rw-r--r--pkg/commands/git.go126
-rw-r--r--pkg/commands/git_structs.go27
-rw-r--r--pkg/commands/git_test.go662
-rw-r--r--pkg/commands/stash_entry.go13
-rw-r--r--pkg/git/branch_list_builder.go32
-rw-r--r--pkg/gui/branches_panel.go12
-rw-r--r--pkg/gui/commit_message_panel.go4
-rw-r--r--pkg/gui/commits_panel.go28
-rw-r--r--pkg/gui/files_panel.go45
-rw-r--r--pkg/gui/gui.go23
-rw-r--r--pkg/gui/keybindings.go44
-rw-r--r--pkg/gui/menu_panel.go106
-rw-r--r--pkg/gui/options_menu_panel.go51
-rw-r--r--pkg/gui/recent_repos_panel.go69
-rw-r--r--pkg/gui/stash_panel.go11
-rw-r--r--pkg/gui/status_panel.go19
-rw-r--r--pkg/gui/view_helpers.go48
-rw-r--r--pkg/i18n/english.go3
-rw-r--r--pkg/utils/utils.go115
-rw-r--r--pkg/utils/utils_test.go244
24 files changed, 1434 insertions, 320 deletions
diff --git a/pkg/app/app.go b/pkg/app/app.go
index b03ec5b42..65acd2e35 100644
--- a/pkg/app/app.go
+++ b/pkg/app/app.go
@@ -77,11 +77,11 @@ func Setup(config config.AppConfigurer) (*App, error) {
app.Tr = i18n.NewLocalizer(app.Log)
- app.GitCommand, err = commands.NewGitCommand(app.Log, app.OSCommand, app.Tr)
+ app.Updater, err = updates.NewUpdater(app.Log, config, app.OSCommand, app.Tr)
if err != nil {
return app, err
}
- app.Updater, err = updates.NewUpdater(app.Log, config, app.OSCommand, app.Tr)
+ app.GitCommand, err = commands.NewGitCommand(app.Log, app.OSCommand, app.Tr)
if err != nil {
return app, err
}
diff --git a/pkg/commands/branch.go b/pkg/commands/branch.go
index 13c26e766..19553a26b 100644
--- a/pkg/commands/branch.go
+++ b/pkg/commands/branch.go
@@ -14,9 +14,9 @@ type Branch struct {
Recency string
}
-// GetDisplayString returns the dispaly string of branch
-func (b *Branch) GetDisplayString() string {
- return utils.WithPadding(b.Recency, 4) + utils.ColoredString(b.Name, b.GetColor())
+// GetDisplayStrings returns the dispaly string of branch
+func (b *Branch) GetDisplayStrings() []string {
+ return []string{b.Recency, utils.ColoredString(b.Name, b.GetColor())}
}
// GetColor branch color
diff --git a/pkg/commands/commit.go b/pkg/commands/commit.go
new file mode 100644
index 000000000..37c3e9525
--- /dev/null
+++ b/pkg/commands/commit.go
@@ -0,0 +1,26 @@
+package commands
+
+import (
+ "github.com/fatih/color"
+)
+
+// Commit : A git commit
+type Commit struct {
+ Sha string
+ Name string
+ Pushed bool
+ DisplayString string
+}
+
+func (c *Commit) GetDisplayStrings() []string {
+ red := color.New(color.FgRed)
+ yellow := color.New(color.FgYellow)
+ white := color.New(color.FgWhite)
+
+ shaColor := yellow
+ if c.Pushed {
+ shaColor = red
+ }
+
+ return []string{shaColor.Sprint(c.Sha), white.Sprint(c.Name)}
+}
diff --git a/pkg/commands/file.go b/pkg/commands/file.go
new file mode 100644
index 000000000..8fcd9aff9
--- /dev/null
+++ b/pkg/commands/file.go
@@ -0,0 +1,36 @@
+package commands
+
+import "github.com/fatih/color"
+
+// File : A file from git status
+// duplicating this for now
+type File struct {
+ Name string
+ HasStagedChanges bool
+ HasUnstagedChanges bool
+ Tracked bool
+ Deleted bool
+ HasMergeConflicts bool
+ DisplayString string
+ Type string // one of 'file', 'directory', and 'other'
+}
+
+// GetDisplayStrings returns the display string of a file
+func (f *File) GetDisplayStrings() []string {
+ // potentially inefficient to be instantiating these color
+ // objects with each render
+ red := color.New(color.FgRed)
+ green := color.New(color.FgGreen)
+ if !f.Tracked && !f.HasStagedChanges {
+ return []string{red.Sprint(f.DisplayString)}
+ }
+
+ output := green.Sprint(f.DisplayString[0:1])
+ output += red.Sprint(f.DisplayString[1:3])
+ if f.HasUnstagedChanges {
+ output += red.Sprint(f.Name)
+ } else {
+ output += green.Sprint(f.Name)
+ }
+ return []string{output}
+}
diff --git a/pkg/commands/git.go b/pkg/commands/git.go
index a6f9e2f9c..73b621484 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
@@ -100,21 +101,22 @@ func NewGitCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Localizer)
Repo: repo,
getGlobalGitConfig: gitconfig.Global,
getLocalGitConfig: gitconfig.Local,
+ removeFile: os.RemoveAll,
}, nil
}
// GetStashEntries stash entryies
-func (c *GitCommand) GetStashEntries() []StashEntry {
+func (c *GitCommand) GetStashEntries() []*StashEntry {
rawString, _ := c.OSCommand.RunCommandWithOutput("git stash list --pretty='%gs'")
- stashEntries := []StashEntry{}
+ stashEntries := []*StashEntry{}
for i, line := range utils.SplitLines(rawString) {
stashEntries = append(stashEntries, stashEntryFromLine(line, i))
}
return stashEntries
}
-func stashEntryFromLine(line string, index int) StashEntry {
- return StashEntry{
+func stashEntryFromLine(line string, index int) *StashEntry {
+ return &StashEntry{
Name: line,
Index: index,
DisplayString: line,
@@ -127,10 +129,10 @@ func (c *GitCommand) GetStashEntryDiff(index int) (string, error) {
}
// GetStatusFiles git status files
-func (c *GitCommand) GetStatusFiles() []File {
+func (c *GitCommand) GetStatusFiles() []*File {
statusOutput, _ := c.GitStatus()
statusStrings := utils.SplitLines(statusOutput)
- files := []File{}
+ files := []*File{}
for _, statusString := range statusStrings {
change := statusString[0:2]
@@ -140,7 +142,7 @@ func (c *GitCommand) GetStatusFiles() []File {
_, untracked := map[string]bool{"??": true, "A ": true, "AM": true}[change]
_, hasNoStagedChanges := map[string]bool{" ": true, "U": true, "?": true}[stagedChange]
- file := File{
+ file := &File{
Name: filename,
DisplayString: statusString,
HasStagedChanges: !hasNoStagedChanges,
@@ -168,7 +170,7 @@ func (c *GitCommand) StashSave(message string) error {
}
// MergeStatusFiles merge status files
-func (c *GitCommand) MergeStatusFiles(oldFiles, newFiles []File) []File {
+func (c *GitCommand) MergeStatusFiles(oldFiles, newFiles []*File) []*File {
if len(oldFiles) == 0 {
return newFiles
}
@@ -176,7 +178,7 @@ func (c *GitCommand) MergeStatusFiles(oldFiles, newFiles []File) []File {
appendedIndexes := []int{}
// retain position of files we already could see
- result := []File{}
+ result := []*File{}
for _, oldFile := range oldFiles {
for newIndex, newFile := range newFiles {
if oldFile.Name == newFile.Name {
@@ -231,13 +233,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
@@ -331,56 +338,50 @@ func (c *GitCommand) Push(branchName string, force bool) error {
// retaining the message of the higher commit
func (c *GitCommand) SquashPreviousTwoCommits(message string) error {
// TODO: test this
- err := c.OSCommand.RunCommand("git reset --soft HEAD^")
- if err != nil {
+ if err := c.OSCommand.RunCommand("git reset --soft HEAD^"); err != nil {
return err
}
// TODO: if password is required, we need to return a subprocess
- return c.OSCommand.RunCommand("git commit --amend -m " + c.OSCommand.Quote(message))
+ return c.OSCommand.RunCommand(fmt.Sprintf("git commit --amend -m %s", c.OSCommand.Quote(message)))
}
// SquashFixupCommit squashes a 'FIXUP' commit into the commit beneath it,
// retaining the commit message of the lower commit
func (c *GitCommand) SquashFixupCommit(branchName string, shaValue string) error {
- var err error
commands := []string{
- "git checkout -q " + shaValue,
- "git reset --soft " + shaValue + "^",
- "git commit --amend -C " + shaValue + "^",
- "git rebase --onto HEAD " + shaValue + " " + branchName,
+ fmt.Sprintf("git checkout -q %s", shaValue),
+ fmt.Sprintf("git reset --soft %s^", shaValue),
+ fmt.Sprintf("git commit --amend -C %s^", shaValue),
+ fmt.Sprintf("git rebase --onto HEAD %s %s", shaValue, branchName),
}
- ret := ""
for _, command := range commands {
c.Log.Info(command)
- output, err := c.OSCommand.RunCommandWithOutput(command)
- ret += output
- if err != nil {
+
+ if output, err := c.OSCommand.RunCommandWithOutput(command); err != nil {
+ ret := output
+ // We are already in an error state here so we're just going to append
+ // the output of these commands
+ output, _ := c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git branch -d %s", shaValue))
+ ret += output
+ output, _ = c.OSCommand.RunCommandWithOutput(fmt.Sprintf("git checkout %s", branchName))
+ ret += output
+
c.Log.Info(ret)
- break
+ return errors.New(ret)
}
}
- if err != nil {
- // We are already in an error state here so we're just going to append
- // the output of these commands
- output, _ := c.OSCommand.RunCommandWithOutput("git branch -d " + shaValue)
- ret += output
- output, _ = c.OSCommand.RunCommandWithOutput("git checkout " + branchName)
- ret += output
- }
- if err != nil {
- return errors.New(ret)
- }
+
return nil
}
-// CatFile obtain the contents of a file
+// CatFile obtains the content of a file
func (c *GitCommand) CatFile(fileName string) (string, error) {
- return c.OSCommand.RunCommandWithOutput("cat " + c.OSCommand.Quote(fileName))
+ return c.OSCommand.RunCommandWithOutput(fmt.Sprintf("cat %s", c.OSCommand.Quote(fileName)))
}
// StageFile stages a file
func (c *GitCommand) StageFile(fileName string) error {
- return c.OSCommand.RunCommand("git add " + c.OSCommand.Quote(fileName))
+ return c.OSCommand.RunCommand(fmt.Sprintf("git add %s", c.OSCommand.Quote(fileName)))
}
// StageAll stages all files
@@ -395,13 +396,11 @@ func (c *GitCommand) UnstageAll() error {
// UnStageFile unstages a file
func (c *GitCommand) UnStageFile(fileName string, tracked bool) error {
- var command string
+ command := "git rm --cached %s"
if tracked {
- command = "git reset HEAD "
- } else {
- command = "git rm --cached "
+ command = "git reset HEAD %s"
}
- return c.OSCommand.RunCommand(command + c.OSCommand.Quote(fileName))
+ return c.OSCommand.RunCommand(fmt.Sprintf(command, c.OSCommand.Quote(fileName)))
}
// GitStatus returns the plaintext short status of the repo
@@ -419,18 +418,18 @@ func (c *GitCommand) IsInMergeState() (bool, error) {
}
// RemoveFile directly
-func (c *GitCommand) RemoveFile(file File) 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
@@ -439,7 +438,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
@@ -462,30 +461,20 @@ 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
-func (c *GitCommand) GetCommits() []Commit {
+func (c *GitCommand) GetCommits() []*Commit {
pushables := c.GetCommitsToPush()
log := c.GetLog()
- commits := []Commit{}
+ 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)
- commits = append(commits, Commit{
+ _, pushed := pushables[sha]
+ commits = append(commits, &Commit{
Sha: sha,
Name: strings.Join(splitLine[1:], " "),
Pushed: pushed,
@@ -505,6 +494,7 @@ func (c *GitCommand) GetLog() string {
// assume if there is an error there are no commits yet for this branch
return ""
}
+
return result
}
@@ -523,7 +513,7 @@ func (c *GitCommand) Show(sha string) string {
}
// Diff returns the diff of a file
-func (c *GitCommand) Diff(file File) string {
+func (c *GitCommand) Diff(file *File) string {
cachedArg := ""
fileName := c.OSCommand.Quote(file.Name)
if file.HasStagedChanges && !file.HasUnstagedChanges {
diff --git a/pkg/commands/git_structs.go b/pkg/commands/git_structs.go
index 2b3b3f2db..1590ce32e 100644
--- a/pkg/commands/git_structs.go
+++ b/pkg/commands/git_structs.go
@@ -1,32 +1,5 @@
package commands
-// File : A staged/unstaged file
-type File struct {
- Name string
- HasStagedChanges bool
- HasUnstagedChanges bool
- Tracked bool
- Deleted bool
- HasMergeConflicts bool
- DisplayString string
- Type string // one of 'file', 'directory', and 'other'
-}
-
-// Commit : A git commit
-type Commit struct {
- Sha string
- Name string
- Pushed bool
- DisplayString string
-}
-
-// StashEntry : A git stash entry
-type StashEntry struct {
- Index int
- Name string
- DisplayString string
-}
-
// Conflict : A git conflict with a start middle and end corresponding to line
// numbers in the file where the conflict bars appear
type Conflict struct {
diff --git a/pkg/commands/git_test.go b/pkg/commands/git_test.go
index bda3ea225..790a34690 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 },
}
}
@@ -275,7 +276,7 @@ func TestGitCommandGetStashEntries(t *testing.T) {
type scenario struct {
testName string
command func(string, ...string) *exec.Cmd
- test func([]StashEntry)
+ test func([]*StashEntry)
}
scenarios := []scenario{
@@ -284,7 +285,7 @@ func TestGitCommandGetStashEntries(t *testing.T) {
func(string, ...string) *exec.Cmd {
return exec.Command("echo")
},
- func(entries []StashEntry) {
+ func(entries []*StashEntry) {
assert.Len(t, entries, 0)
},
},
@@ -293,8 +294,8 @@ func TestGitCommandGetStashEntries(t *testing.T) {
func(string, ...string) *exec.Cmd {
return exec.Command("echo", "WIP on add-pkg-commands-test: 55c6af2 increase parallel build\nWIP on master: bb86a3f update github template")
},
- func(entries []StashEntry) {
- expected := []StashEntry{
+ func(entries []*StashEntry) {
+ expected := []*StashEntry{
{
0,
"WIP on add-pkg-commands-test: 55c6af2 increase parallel build",
@@ -341,7 +342,7 @@ func TestGitCommandGetStatusFiles(t *testing.T) {
type scenario struct {
testName string
command func(string, ...string) *exec.Cmd
- test func([]File)
+ test func([]*File)
}
scenarios := []scenario{
@@ -350,7 +351,7 @@ func TestGitCommandGetStatusFiles(t *testing.T) {
func(cmd string, args ...string) *exec.Cmd {
return exec.Command("echo")
},
- func(files []File) {
+ func(files []*File) {
assert.Len(t, files, 0)
},
},
@@ -362,10 +363,10 @@ func TestGitCommandGetStatusFiles(t *testing.T) {
"MM file1.txt\nA file3.txt\nAM file2.txt\n?? file4.txt",
)
},
- func(files []File) {
+ func(files []*File) {
assert.Len(t, files, 4)
- expected := []File{
+ expected := []*File{
{
Name: "file1.txt",
HasStagedChanges: true,
@@ -463,22 +464,22 @@ func TestGitCommandCommitAmend(t *testing.T) {
func TestGitCommandMergeStatusFiles(t *testing.T) {
type scenario struct {
testName string
- oldFiles []File
- newFiles []File
- test func([]File)
+ oldFiles []*File
+ newFiles []*File
+ test func([]*File)
}
scenarios := []scenario{
{
"Old file and new file are the same",
- []File{},
- []File{
+ []*File{},
+ []*File{
{
Name: "new_file.txt",
},
},
- func(files []File) {
- expected := []File{
+ func(files []*File) {
+ expected := []*File{
{
Name: "new_file.txt",
},
@@ -490,7 +491,7 @@ func TestGitCommandMergeStatusFiles(t *testing.T) {
},
{
"Several files to merge, with some identical",
- []File{
+ []*File{
{
Name: "new_file1.txt",
},
@@ -501,7 +502,7 @@ func TestGitCommandMergeStatusFiles(t *testing.T) {
Name: "new_file3.txt",
},
},
- []File{
+ []*File{
{
Name: "new_file4.txt",
},
@@ -512,8 +513,8 @@ func TestGitCommandMergeStatusFiles(t *testing.T) {
Name: "new_file1.txt",
},
},
- func(files []File) {
- expected := []File{
+ func(files []*File) {
+ expected := []*File{
{
Name: "new_file1.txt",
},
@@ -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
@@ -917,7 +918,7 @@ func TestGitCommandPush(t *testing.T) {
},
},
{
- "Push with force enable",
+ "Push with force enabled",
func(cmd string, args ...string) *exec.Cmd {
assert.EqualValues(t, "git", cmd)
assert.EqualValues(t, []string{"push", "--force-with-lease", "-u", "origin", "test"}, args)
@@ -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) {
@@ -953,11 +954,610 @@ func TestGitCommandPush(t *testing.T) {
}
}
+func TestGitCommandSquashPreviousTwoCommits(t *testing.T) {
+ type scenario struct {
+ testName string
+ command func(string, ...string) *exec.Cmd
+ test func(error)
+ }
+
+ scenarios := []scenario{
+ {
+ "Git reset triggers an error",
+ func(cmd string, args ...string) *exec.Cmd {
+ assert.EqualValues(t, "git", cmd)
+ assert.EqualValues(t, []string{"reset", "--soft", "HEAD^"}, args)
+
+ return exec.Command("test")
+ },
+ func(err error) {
+ assert.NotNil(t, err)
+ },
+ },
+ {
+ "Git commit triggers an error",
+ func(cmd string, args ...string) *exec.Cmd {
+ if len(args) > 0 && args[0] == "reset" {
+ return exec.Command("echo")
+ }
+
+ assert.EqualValues(t, "git", cmd)
+ assert.EqualValues(t, []string{"commit", "--amend", "-m", "test"}, args)
+
+ return exec.Command("test")
+ },
+ func(err error) {
+ assert.NotNil(t, err)
+ },
+ },
+ {
+ "Stash succeeded",
+ func(cmd string, args ...string) *exec.Cmd {
+ if len(args) > 0 && args[0] == "reset" {
+ return exec.Command("echo")
+ }
+
+ assert.EqualValues(t, "git", cmd)
+ assert.EqualValues(t, []string{"commit", "--amend", "-m", "test"}, args)
+
+ return exec.Command("echo")
+ },
+ func(err error) {
+ assert.Nil(t, err)
+ },
+ },
+ }
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ gitCmd := newDummyGitCommand()
+ gitCmd.OSCommand.command = s.command
+ s.test(gitCmd.SquashPreviousTwoCommits("test"))
+ })
+ }
+}
+
+func TestGitCommandSquashFixupCommit(t *testing.T) {
+ type scenario struct {
+ testName string
+ command func() (func(string, ...string) *exec.Cmd, *[][]string)
+ test func(*[][]string, error)
+ }
+
+ scenarios := []scenario{
+ {
+ "An error occurred with one of the sub git command",
+ func() (func(string, ...string) *exec.Cmd, *[][]string) {
+ cmdsCalled := [][]string{}
+ return func(cmd string, args ...string) *exec.Cmd {
+ cmdsCalled = append(cmdsCalled, args)
+ if len(args) > 0 && args[0] == "checkout" {
+ return exec.Command("test")
+ }
+
+ return exec.Command("echo")
+ }, &cmdsCalled
+ },
+ func(cmdsCalled *[][]string, err error) {
+ assert.NotNil(t, err)
+ assert.Len(t, *cmdsCalled, 3)
+ assert.EqualValues(t, *cmdsCalled, [][]string{
+ {"checkout", "-q", "6789abcd"},
+ {"branch", "-d", "6789abcd"},
+ {"checkout", "test"},
+ })
+ },
+ },
+ {
+ "Squash fixup succeeded",
+ 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.Nil(t, err)
+ assert.Len(t, *cmdsCalled, 4)
+ assert.EqualValues(t, *cmdsCalled, [][]string{
+ {"checkout", "-q", "6789abcd"},
+ {"reset", "--soft", "6789abcd^"},
+ {"commit", "--amend", "-C", "6789abcd^"},
+ {"rebase", "--onto", "HEAD", "6789abcd", "test"},
+ })
+ },
+ },
+ }
+
+ for _, s := range scenarios {
+ t.Run(s.testName, func(t *testing.T) {
+ var cmdsCalled *[][]string
+ gitCmd := newDummyGitCommand()
+ gitCmd.OSCommand.command, cmdsCalled = s.command()
+ s.test(cmdsCalled, gitCmd.SquashFixupCommit("test", "6789abcd"))
+ })
+ }
+}
+
+func TestGitCommandCatFile(t *testing.T) {
+ gitCmd := newDummyGitCommand()
+ gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
+ assert.EqualValues(t, "cat", cmd)
+ assert.EqualValues(t, []string{"test.txt"}, args)
+
+ return exec.Command("echo", "-n", "test")
+ }
+
+ o, err := gitCmd.CatFile("test.txt")
+ assert.NoError(t, err)
+ assert.Equal(t, "test", o)
+}
+
+func TestGitCommandStageFile(t *testing.T) {
+ gitCmd := newDummyGitCommand()
+ gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
+ assert.EqualValues(t, "git", cmd)
+ assert.EqualValues(t, []string{"add", "test.txt"}, args)
+
+ return exec.Command("echo")
+ }
+
+ assert.NoError(t, gitCmd.StageFile("test.txt"))
+}
+
+func TestGitCommandUnstageFile(t *testing.T) {
+ type scenario struct {
+ testName string
+ command func(string, ...string) *exec.Cmd
+ test func(error)
+ tracked bool
+ }
+
+ scenarios := []scenario{
+ {
+ "Remove an untracked file from staging",
+ func(cmd string, args ...string) *exec.Cmd {
+ assert.EqualValues(t, "git", cmd)
+ assert.EqualValues(t, []string{"rm", "--cached", "test.txt"}, args)
+
+ return exec.Command("echo")
+ },
+ func(err error) {
+ assert.NoError(t, err)
+ },
+ false,
+ },
+ {
+ "Remove a tracked file from staging",
+ func(cmd string, args ...string) *exec.Cmd {
+ assert.EqualValues(t, "git", cmd)
+ assert.EqualValues(t, []string{"reset", "HEAD