diff options
author | Jesse Duffield <jessedduffield@gmail.com> | 2018-09-25 19:05:24 +1000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-09-25 19:05:24 +1000 |
commit | 7164f372661794eedd749eb41cc1265c86110913 (patch) | |
tree | 39751808681b8908989443b278cd5e90aac97a52 /pkg | |
parent | 61f0801bd366e959437676ae72b2d9fe98c4b8af (diff) | |
parent | 80d6bbef8661932ee0a665961b44a681c811ac36 (diff) |
Merge branch 'master' into feature/commit-amend
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/app/app.go | 4 | ||||
-rw-r--r-- | pkg/commands/branch.go | 6 | ||||
-rw-r--r-- | pkg/commands/commit.go | 26 | ||||
-rw-r--r-- | pkg/commands/file.go | 36 | ||||
-rw-r--r-- | pkg/commands/git.go | 126 | ||||
-rw-r--r-- | pkg/commands/git_structs.go | 27 | ||||
-rw-r--r-- | pkg/commands/git_test.go | 662 | ||||
-rw-r--r-- | pkg/commands/stash_entry.go | 13 | ||||
-rw-r--r-- | pkg/git/branch_list_builder.go | 32 | ||||
-rw-r--r-- | pkg/gui/branches_panel.go | 12 | ||||
-rw-r--r-- | pkg/gui/commit_message_panel.go | 4 | ||||
-rw-r--r-- | pkg/gui/commits_panel.go | 28 | ||||
-rw-r--r-- | pkg/gui/files_panel.go | 45 | ||||
-rw-r--r-- | pkg/gui/gui.go | 23 | ||||
-rw-r--r-- | pkg/gui/keybindings.go | 44 | ||||
-rw-r--r-- | pkg/gui/menu_panel.go | 106 | ||||
-rw-r--r-- | pkg/gui/options_menu_panel.go | 51 | ||||
-rw-r--r-- | pkg/gui/recent_repos_panel.go | 69 | ||||
-rw-r--r-- | pkg/gui/stash_panel.go | 11 | ||||
-rw-r--r-- | pkg/gui/status_panel.go | 19 | ||||
-rw-r--r-- | pkg/gui/view_helpers.go | 48 | ||||
-rw-r--r-- | pkg/i18n/english.go | 3 | ||||
-rw-r--r-- | pkg/utils/utils.go | 115 | ||||
-rw-r--r-- | pkg/utils/utils_test.go | 244 |
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 |