github.com/fluffynuts/lazygit@v0.8.1/pkg/commands/git_test.go (about)

     1  package commands
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"os"
     7  	"os/exec"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/go-errors/errors"
    12  	"github.com/jesseduffield/lazygit/pkg/i18n"
    13  	"github.com/jesseduffield/lazygit/pkg/test"
    14  	"github.com/stretchr/testify/assert"
    15  	gogit "gopkg.in/src-d/go-git.v4"
    16  )
    17  
    18  type fileInfoMock struct {
    19  	name        string
    20  	size        int64
    21  	fileMode    os.FileMode
    22  	fileModTime time.Time
    23  	isDir       bool
    24  	sys         interface{}
    25  }
    26  
    27  // Name is a function.
    28  func (f fileInfoMock) Name() string {
    29  	return f.name
    30  }
    31  
    32  // Size is a function.
    33  func (f fileInfoMock) Size() int64 {
    34  	return f.size
    35  }
    36  
    37  // Mode is a function.
    38  func (f fileInfoMock) Mode() os.FileMode {
    39  	return f.fileMode
    40  }
    41  
    42  // ModTime is a function.
    43  func (f fileInfoMock) ModTime() time.Time {
    44  	return f.fileModTime
    45  }
    46  
    47  // IsDir is a function.
    48  func (f fileInfoMock) IsDir() bool {
    49  	return f.isDir
    50  }
    51  
    52  // Sys is a function.
    53  func (f fileInfoMock) Sys() interface{} {
    54  	return f.sys
    55  }
    56  
    57  // TestVerifyInGitRepo is a function.
    58  func TestVerifyInGitRepo(t *testing.T) {
    59  	type scenario struct {
    60  		testName string
    61  		runCmd   func(string) error
    62  		test     func(error)
    63  	}
    64  
    65  	scenarios := []scenario{
    66  		{
    67  			"Valid git repository",
    68  			func(string) error {
    69  				return nil
    70  			},
    71  			func(err error) {
    72  				assert.NoError(t, err)
    73  			},
    74  		},
    75  		{
    76  			"Not a valid git repository",
    77  			func(string) error {
    78  				return fmt.Errorf("fatal: Not a git repository (or any of the parent directories): .git")
    79  			},
    80  			func(err error) {
    81  				assert.Error(t, err)
    82  				assert.Regexp(t, `fatal: .ot a git repository \(or any of the parent directories\s?\/?\): \.git`, err.Error())
    83  			},
    84  		},
    85  	}
    86  
    87  	for _, s := range scenarios {
    88  		t.Run(s.testName, func(t *testing.T) {
    89  			s.test(verifyInGitRepo(s.runCmd))
    90  		})
    91  	}
    92  }
    93  
    94  // TestNavigateToRepoRootDirectory is a function.
    95  func TestNavigateToRepoRootDirectory(t *testing.T) {
    96  	type scenario struct {
    97  		testName string
    98  		stat     func(string) (os.FileInfo, error)
    99  		chdir    func(string) error
   100  		test     func(error)
   101  	}
   102  
   103  	scenarios := []scenario{
   104  		{
   105  			"Navigate to git repository",
   106  			func(string) (os.FileInfo, error) {
   107  				return fileInfoMock{isDir: true}, nil
   108  			},
   109  			func(string) error {
   110  				return nil
   111  			},
   112  			func(err error) {
   113  				assert.NoError(t, err)
   114  			},
   115  		},
   116  		{
   117  			"An error occurred when getting path informations",
   118  			func(string) (os.FileInfo, error) {
   119  				return nil, fmt.Errorf("An error occurred")
   120  			},
   121  			func(string) error {
   122  				return nil
   123  			},
   124  			func(err error) {
   125  				assert.Error(t, err)
   126  				assert.EqualError(t, err, "An error occurred")
   127  			},
   128  		},
   129  		{
   130  			"An error occurred when trying to move one path backward",
   131  			func(string) (os.FileInfo, error) {
   132  				return nil, os.ErrNotExist
   133  			},
   134  			func(string) error {
   135  				return fmt.Errorf("An error occurred")
   136  			},
   137  			func(err error) {
   138  				assert.Error(t, err)
   139  				assert.EqualError(t, err, "An error occurred")
   140  			},
   141  		},
   142  	}
   143  
   144  	for _, s := range scenarios {
   145  		t.Run(s.testName, func(t *testing.T) {
   146  			s.test(navigateToRepoRootDirectory(s.stat, s.chdir))
   147  		})
   148  	}
   149  }
   150  
   151  // TestSetupRepositoryAndWorktree is a function.
   152  func TestSetupRepositoryAndWorktree(t *testing.T) {
   153  	type scenario struct {
   154  		testName          string
   155  		openGitRepository func(string) (*gogit.Repository, error)
   156  		sLocalize         func(string) string
   157  		test              func(*gogit.Repository, *gogit.Worktree, error)
   158  	}
   159  
   160  	scenarios := []scenario{
   161  		{
   162  			"A gitconfig parsing error occurred",
   163  			func(string) (*gogit.Repository, error) {
   164  				return nil, fmt.Errorf(`unquoted '\' must be followed by new line`)
   165  			},
   166  			func(string) string {
   167  				return "error translated"
   168  			},
   169  			func(r *gogit.Repository, w *gogit.Worktree, err error) {
   170  				assert.Error(t, err)
   171  				assert.EqualError(t, err, "error translated")
   172  			},
   173  		},
   174  		{
   175  			"A gogit error occurred",
   176  			func(string) (*gogit.Repository, error) {
   177  				return nil, fmt.Errorf("Error from inside gogit")
   178  			},
   179  			func(string) string { return "" },
   180  			func(r *gogit.Repository, w *gogit.Worktree, err error) {
   181  				assert.Error(t, err)
   182  				assert.EqualError(t, err, "Error from inside gogit")
   183  			},
   184  		},
   185  		{
   186  			"An error occurred cause git repository is a bare repository",
   187  			func(string) (*gogit.Repository, error) {
   188  				return &gogit.Repository{}, nil
   189  			},
   190  			func(string) string { return "" },
   191  			func(r *gogit.Repository, w *gogit.Worktree, err error) {
   192  				assert.Error(t, err)
   193  				assert.Equal(t, gogit.ErrIsBareRepository, err)
   194  			},
   195  		},
   196  		{
   197  			"Setup done properly",
   198  			func(string) (*gogit.Repository, error) {
   199  				assert.NoError(t, os.RemoveAll("/tmp/lazygit-test"))
   200  				r, err := gogit.PlainInit("/tmp/lazygit-test", false)
   201  				assert.NoError(t, err)
   202  				return r, nil
   203  			},
   204  			func(string) string { return "" },
   205  			func(r *gogit.Repository, w *gogit.Worktree, err error) {
   206  				assert.NoError(t, err)
   207  				assert.NotNil(t, w)
   208  				assert.NotNil(t, r)
   209  			},
   210  		},
   211  	}
   212  
   213  	for _, s := range scenarios {
   214  		t.Run(s.testName, func(t *testing.T) {
   215  			s.test(setupRepositoryAndWorktree(s.openGitRepository, s.sLocalize))
   216  		})
   217  	}
   218  }
   219  
   220  // TestNewGitCommand is a function.
   221  func TestNewGitCommand(t *testing.T) {
   222  	actual, err := os.Getwd()
   223  	assert.NoError(t, err)
   224  
   225  	defer func() {
   226  		assert.NoError(t, os.Chdir(actual))
   227  	}()
   228  
   229  	type scenario struct {
   230  		testName string
   231  		setup    func()
   232  		test     func(*GitCommand, error)
   233  	}
   234  
   235  	scenarios := []scenario{
   236  		{
   237  			"An error occurred, folder doesn't contains a git repository",
   238  			func() {
   239  				assert.NoError(t, os.Chdir("/tmp"))
   240  			},
   241  			func(gitCmd *GitCommand, err error) {
   242  				assert.Error(t, err)
   243  				assert.Regexp(t, `fatal: .ot a git repository ((\(or any of the parent directories\): \.git)|(\(or any parent up to mount point \/\)))`, err.Error())
   244  			},
   245  		},
   246  		{
   247  			"New GitCommand object created",
   248  			func() {
   249  				assert.NoError(t, os.RemoveAll("/tmp/lazygit-test"))
   250  				_, err := gogit.PlainInit("/tmp/lazygit-test", false)
   251  				assert.NoError(t, err)
   252  				assert.NoError(t, os.Chdir("/tmp/lazygit-test"))
   253  			},
   254  			func(gitCmd *GitCommand, err error) {
   255  				assert.NoError(t, err)
   256  			},
   257  		},
   258  	}
   259  
   260  	for _, s := range scenarios {
   261  		t.Run(s.testName, func(t *testing.T) {
   262  			s.setup()
   263  			s.test(NewGitCommand(NewDummyLog(), NewDummyOSCommand(), i18n.NewLocalizer(NewDummyLog()), NewDummyAppConfig()))
   264  		})
   265  	}
   266  }
   267  
   268  // TestGitCommandGetStashEntries is a function.
   269  func TestGitCommandGetStashEntries(t *testing.T) {
   270  	type scenario struct {
   271  		testName string
   272  		command  func(string, ...string) *exec.Cmd
   273  		test     func([]*StashEntry)
   274  	}
   275  
   276  	scenarios := []scenario{
   277  		{
   278  			"No stash entries found",
   279  			func(string, ...string) *exec.Cmd {
   280  				return exec.Command("echo")
   281  			},
   282  			func(entries []*StashEntry) {
   283  				assert.Len(t, entries, 0)
   284  			},
   285  		},
   286  		{
   287  			"Several stash entries found",
   288  			func(string, ...string) *exec.Cmd {
   289  				return exec.Command("echo", "WIP on add-pkg-commands-test: 55c6af2 increase parallel build\nWIP on master: bb86a3f update github template")
   290  			},
   291  			func(entries []*StashEntry) {
   292  				expected := []*StashEntry{
   293  					{
   294  						0,
   295  						"WIP on add-pkg-commands-test: 55c6af2 increase parallel build",
   296  						"WIP on add-pkg-commands-test: 55c6af2 increase parallel build",
   297  					},
   298  					{
   299  						1,
   300  						"WIP on master: bb86a3f update github template",
   301  						"WIP on master: bb86a3f update github template",
   302  					},
   303  				}
   304  
   305  				assert.Len(t, entries, 2)
   306  				assert.EqualValues(t, expected, entries)
   307  			},
   308  		},
   309  	}
   310  
   311  	for _, s := range scenarios {
   312  		t.Run(s.testName, func(t *testing.T) {
   313  			gitCmd := NewDummyGitCommand()
   314  			gitCmd.OSCommand.command = s.command
   315  
   316  			s.test(gitCmd.GetStashEntries())
   317  		})
   318  	}
   319  }
   320  
   321  // TestGitCommandGetStashEntryDiff is a function.
   322  func TestGitCommandGetStashEntryDiff(t *testing.T) {
   323  	gitCmd := NewDummyGitCommand()
   324  	gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
   325  		assert.EqualValues(t, "git", cmd)
   326  		assert.EqualValues(t, []string{"stash", "show", "-p", "--color", "stash@{1}"}, args)
   327  
   328  		return exec.Command("echo")
   329  	}
   330  
   331  	_, err := gitCmd.GetStashEntryDiff(1)
   332  
   333  	assert.NoError(t, err)
   334  }
   335  
   336  // TestGitCommandGetStatusFiles is a function.
   337  func TestGitCommandGetStatusFiles(t *testing.T) {
   338  	type scenario struct {
   339  		testName string
   340  		command  func(string, ...string) *exec.Cmd
   341  		test     func([]*File)
   342  	}
   343  
   344  	scenarios := []scenario{
   345  		{
   346  			"No files found",
   347  			func(cmd string, args ...string) *exec.Cmd {
   348  				return exec.Command("echo")
   349  			},
   350  			func(files []*File) {
   351  				assert.Len(t, files, 0)
   352  			},
   353  		},
   354  		{
   355  			"Several files found",
   356  			func(cmd string, args ...string) *exec.Cmd {
   357  				return exec.Command(
   358  					"echo",
   359  					"MM file1.txt\nA  file3.txt\nAM file2.txt\n?? file4.txt",
   360  				)
   361  			},
   362  			func(files []*File) {
   363  				assert.Len(t, files, 4)
   364  
   365  				expected := []*File{
   366  					{
   367  						Name:               "file1.txt",
   368  						HasStagedChanges:   true,
   369  						HasUnstagedChanges: true,
   370  						Tracked:            true,
   371  						Deleted:            false,
   372  						HasMergeConflicts:  false,
   373  						DisplayString:      "MM file1.txt",
   374  						Type:               "other",
   375  						ShortStatus:        "MM",
   376  					},
   377  					{
   378  						Name:               "file3.txt",
   379  						HasStagedChanges:   true,
   380  						HasUnstagedChanges: false,
   381  						Tracked:            false,
   382  						Deleted:            false,
   383  						HasMergeConflicts:  false,
   384  						DisplayString:      "A  file3.txt",
   385  						Type:               "other",
   386  						ShortStatus:        "A ",
   387  					},
   388  					{
   389  						Name:               "file2.txt",
   390  						HasStagedChanges:   true,
   391  						HasUnstagedChanges: true,
   392  						Tracked:            false,
   393  						Deleted:            false,
   394  						HasMergeConflicts:  false,
   395  						DisplayString:      "AM file2.txt",
   396  						Type:               "other",
   397  						ShortStatus:        "AM",
   398  					},
   399  					{
   400  						Name:               "file4.txt",
   401  						HasStagedChanges:   false,
   402  						HasUnstagedChanges: true,
   403  						Tracked:            false,
   404  						Deleted:            false,
   405  						HasMergeConflicts:  false,
   406  						DisplayString:      "?? file4.txt",
   407  						Type:               "other",
   408  						ShortStatus:        "??",
   409  					},
   410  				}
   411  
   412  				assert.EqualValues(t, expected, files)
   413  			},
   414  		},
   415  	}
   416  
   417  	for _, s := range scenarios {
   418  		t.Run(s.testName, func(t *testing.T) {
   419  			gitCmd := NewDummyGitCommand()
   420  			gitCmd.OSCommand.command = s.command
   421  
   422  			s.test(gitCmd.GetStatusFiles())
   423  		})
   424  	}
   425  }
   426  
   427  // TestGitCommandStashDo is a function.
   428  func TestGitCommandStashDo(t *testing.T) {
   429  	gitCmd := NewDummyGitCommand()
   430  	gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
   431  		assert.EqualValues(t, "git", cmd)
   432  		assert.EqualValues(t, []string{"stash", "drop", "stash@{1}"}, args)
   433  
   434  		return exec.Command("echo")
   435  	}
   436  
   437  	assert.NoError(t, gitCmd.StashDo(1, "drop"))
   438  }
   439  
   440  // TestGitCommandStashSave is a function.
   441  func TestGitCommandStashSave(t *testing.T) {
   442  	gitCmd := NewDummyGitCommand()
   443  	gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
   444  		assert.EqualValues(t, "git", cmd)
   445  		assert.EqualValues(t, []string{"stash", "save", "A stash message"}, args)
   446  
   447  		return exec.Command("echo")
   448  	}
   449  
   450  	assert.NoError(t, gitCmd.StashSave("A stash message"))
   451  }
   452  
   453  // TestGitCommandCommitAmend is a function.
   454  func TestGitCommandCommitAmend(t *testing.T) {
   455  	gitCmd := NewDummyGitCommand()
   456  	gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
   457  		assert.EqualValues(t, "git", cmd)
   458  		assert.EqualValues(t, []string{"commit", "--amend", "--allow-empty"}, args)
   459  
   460  		return exec.Command("echo")
   461  	}
   462  
   463  	_, err := gitCmd.PrepareCommitAmendSubProcess().CombinedOutput()
   464  	assert.NoError(t, err)
   465  }
   466  
   467  // TestGitCommandMergeStatusFiles is a function.
   468  func TestGitCommandMergeStatusFiles(t *testing.T) {
   469  	type scenario struct {
   470  		testName string
   471  		oldFiles []*File
   472  		newFiles []*File
   473  		test     func([]*File)
   474  	}
   475  
   476  	scenarios := []scenario{
   477  		{
   478  			"Old file and new file are the same",
   479  			[]*File{},
   480  			[]*File{
   481  				{
   482  					Name: "new_file.txt",
   483  				},
   484  			},
   485  			func(files []*File) {
   486  				expected := []*File{
   487  					{
   488  						Name: "new_file.txt",
   489  					},
   490  				}
   491  
   492  				assert.Len(t, files, 1)
   493  				assert.EqualValues(t, expected, files)
   494  			},
   495  		},
   496  		{
   497  			"Several files to merge, with some identical",
   498  			[]*File{
   499  				{
   500  					Name: "new_file1.txt",
   501  				},
   502  				{
   503  					Name: "new_file2.txt",
   504  				},
   505  				{
   506  					Name: "new_file3.txt",
   507  				},
   508  			},
   509  			[]*File{
   510  				{
   511  					Name: "new_file4.txt",
   512  				},
   513  				{
   514  					Name: "new_file5.txt",
   515  				},
   516  				{
   517  					Name: "new_file1.txt",
   518  				},
   519  			},
   520  			func(files []*File) {
   521  				expected := []*File{
   522  					{
   523  						Name: "new_file1.txt",
   524  					},
   525  					{
   526  						Name: "new_file4.txt",
   527  					},
   528  					{
   529  						Name: "new_file5.txt",
   530  					},
   531  				}
   532  
   533  				assert.Len(t, files, 3)
   534  				assert.EqualValues(t, expected, files)
   535  			},
   536  		},
   537  	}
   538  
   539  	for _, s := range scenarios {
   540  		t.Run(s.testName, func(t *testing.T) {
   541  			gitCmd := NewDummyGitCommand()
   542  
   543  			s.test(gitCmd.MergeStatusFiles(s.oldFiles, s.newFiles))
   544  		})
   545  	}
   546  }
   547  
   548  // TestGitCommandGetCommitDifferences is a function.
   549  func TestGitCommandGetCommitDifferences(t *testing.T) {
   550  	type scenario struct {
   551  		testName string
   552  		command  func(string, ...string) *exec.Cmd
   553  		test     func(string, string)
   554  	}
   555  
   556  	scenarios := []scenario{
   557  		{
   558  			"Can't retrieve pushable count",
   559  			func(string, ...string) *exec.Cmd {
   560  				return exec.Command("test")
   561  			},
   562  			func(pushableCount string, pullableCount string) {
   563  				assert.EqualValues(t, "?", pushableCount)
   564  				assert.EqualValues(t, "?", pullableCount)
   565  			},
   566  		},
   567  		{
   568  			"Can't retrieve pullable count",
   569  			func(cmd string, args ...string) *exec.Cmd {
   570  				if args[1] == "HEAD..@{u}" {
   571  					return exec.Command("test")
   572  				}
   573  
   574  				return exec.Command("echo")
   575  			},
   576  			func(pushableCount string, pullableCount string) {
   577  				assert.EqualValues(t, "?", pushableCount)
   578  				assert.EqualValues(t, "?", pullableCount)
   579  			},
   580  		},
   581  		{
   582  			"Retrieve pullable and pushable count",
   583  			func(cmd string, args ...string) *exec.Cmd {
   584  				if args[1] == "HEAD..@{u}" {
   585  					return exec.Command("echo", "10")
   586  				}
   587  
   588  				return exec.Command("echo", "11")
   589  			},
   590  			func(pushableCount string, pullableCount string) {
   591  				assert.EqualValues(t, "11", pushableCount)
   592  				assert.EqualValues(t, "10", pullableCount)
   593  			},
   594  		},
   595  	}
   596  
   597  	for _, s := range scenarios {
   598  		t.Run(s.testName, func(t *testing.T) {
   599  			gitCmd := NewDummyGitCommand()
   600  			gitCmd.OSCommand.command = s.command
   601  			s.test(gitCmd.GetCommitDifferences("HEAD", "@{u}"))
   602  		})
   603  	}
   604  }
   605  
   606  // TestGitCommandRenameCommit is a function.
   607  func TestGitCommandRenameCommit(t *testing.T) {
   608  	gitCmd := NewDummyGitCommand()
   609  	gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
   610  		assert.EqualValues(t, "git", cmd)
   611  		assert.EqualValues(t, []string{"commit", "--allow-empty", "--amend", "-m", "test"}, args)
   612  
   613  		return exec.Command("echo")
   614  	}
   615  
   616  	assert.NoError(t, gitCmd.RenameCommit("test"))
   617  }
   618  
   619  // TestGitCommandResetToCommit is a function.
   620  func TestGitCommandResetToCommit(t *testing.T) {
   621  	gitCmd := NewDummyGitCommand()
   622  	gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
   623  		assert.EqualValues(t, "git", cmd)
   624  		assert.EqualValues(t, []string{"reset", "--hard", "78976bc"}, args)
   625  
   626  		return exec.Command("echo")
   627  	}
   628  
   629  	assert.NoError(t, gitCmd.ResetToCommit("78976bc", "hard"))
   630  }
   631  
   632  // TestGitCommandNewBranch is a function.
   633  func TestGitCommandNewBranch(t *testing.T) {
   634  	gitCmd := NewDummyGitCommand()
   635  	gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
   636  		assert.EqualValues(t, "git", cmd)
   637  		assert.EqualValues(t, []string{"checkout", "-b", "test"}, args)
   638  
   639  		return exec.Command("echo")
   640  	}
   641  
   642  	assert.NoError(t, gitCmd.NewBranch("test"))
   643  }
   644  
   645  // TestGitCommandDeleteBranch is a function.
   646  func TestGitCommandDeleteBranch(t *testing.T) {
   647  	type scenario struct {
   648  		testName string
   649  		branch   string
   650  		force    bool
   651  		command  func(string, ...string) *exec.Cmd
   652  		test     func(error)
   653  	}
   654  
   655  	scenarios := []scenario{
   656  		{
   657  			"Delete a branch",
   658  			"test",
   659  			false,
   660  			func(cmd string, args ...string) *exec.Cmd {
   661  				assert.EqualValues(t, "git", cmd)
   662  				assert.EqualValues(t, []string{"branch", "-d", "test"}, args)
   663  
   664  				return exec.Command("echo")
   665  			},
   666  			func(err error) {
   667  				assert.NoError(t, err)
   668  			},
   669  		},
   670  		{
   671  			"Force delete a branch",
   672  			"test",
   673  			true,
   674  			func(cmd string, args ...string) *exec.Cmd {
   675  				assert.EqualValues(t, "git", cmd)
   676  				assert.EqualValues(t, []string{"branch", "-D", "test"}, args)
   677  
   678  				return exec.Command("echo")
   679  			},
   680  			func(err error) {
   681  				assert.NoError(t, err)
   682  			},
   683  		},
   684  	}
   685  
   686  	for _, s := range scenarios {
   687  		t.Run(s.testName, func(t *testing.T) {
   688  			gitCmd := NewDummyGitCommand()
   689  			gitCmd.OSCommand.command = s.command
   690  			s.test(gitCmd.DeleteBranch(s.branch, s.force))
   691  		})
   692  	}
   693  }
   694  
   695  // TestGitCommandMerge is a function.
   696  func TestGitCommandMerge(t *testing.T) {
   697  	gitCmd := NewDummyGitCommand()
   698  	gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
   699  		assert.EqualValues(t, "git", cmd)
   700  		assert.EqualValues(t, []string{"merge", "--no-edit", "test"}, args)
   701  
   702  		return exec.Command("echo")
   703  	}
   704  
   705  	assert.NoError(t, gitCmd.Merge("test"))
   706  }
   707  
   708  // TestGitCommandUsingGpg is a function.
   709  func TestGitCommandUsingGpg(t *testing.T) {
   710  	type scenario struct {
   711  		testName           string
   712  		getLocalGitConfig  func(string) (string, error)
   713  		getGlobalGitConfig func(string) (string, error)
   714  		test               func(bool)
   715  	}
   716  
   717  	scenarios := []scenario{
   718  		{
   719  			"Option global and local config commit.gpgsign is not set",
   720  			func(string) (string, error) {
   721  				return "", nil
   722  			},
   723  			func(string) (string, error) {
   724  				return "", nil
   725  			},
   726  			func(gpgEnabled bool) {
   727  				assert.False(t, gpgEnabled)
   728  			},
   729  		},
   730  		{
   731  			"Option global config commit.gpgsign is not set, fallback on local config",
   732  			func(string) (string, error) {
   733  				return "", nil
   734  			},
   735  			func(string) (string, error) {
   736  				return "true", nil
   737  			},
   738  			func(gpgEnabled bool) {
   739  				assert.True(t, gpgEnabled)
   740  			},
   741  		},
   742  		{
   743  			"Option commit.gpgsign is true",
   744  			func(string) (string, error) {
   745  				return "True", nil
   746  			},
   747  			func(string) (string, error) {
   748  				return "", nil
   749  			},
   750  			func(gpgEnabled bool) {
   751  				assert.True(t, gpgEnabled)
   752  			},
   753  		},
   754  		{
   755  			"Option commit.gpgsign is on",
   756  			func(string) (string, error) {
   757  				return "ON", nil
   758  			},
   759  			func(string) (string, error) {
   760  				return "", nil
   761  			},
   762  			func(gpgEnabled bool) {
   763  				assert.True(t, gpgEnabled)
   764  			},
   765  		},
   766  		{
   767  			"Option commit.gpgsign is yes",
   768  			func(string) (string, error) {
   769  				return "YeS", nil
   770  			},
   771  			func(string) (string, error) {
   772  				return "", nil
   773  			},
   774  			func(gpgEnabled bool) {
   775  				assert.True(t, gpgEnabled)
   776  			},
   777  		},
   778  		{
   779  			"Option commit.gpgsign is 1",
   780  			func(string) (string, error) {
   781  				return "1", nil
   782  			},
   783  			func(string) (string, error) {
   784  				return "", nil
   785  			},
   786  			func(gpgEnabled bool) {
   787  				assert.True(t, gpgEnabled)
   788  			},
   789  		},
   790  	}
   791  
   792  	for _, s := range scenarios {
   793  		t.Run(s.testName, func(t *testing.T) {
   794  			gitCmd := NewDummyGitCommand()
   795  			gitCmd.getGlobalGitConfig = s.getGlobalGitConfig
   796  			gitCmd.getLocalGitConfig = s.getLocalGitConfig
   797  			s.test(gitCmd.usingGpg())
   798  		})
   799  	}
   800  }
   801  
   802  // TestGitCommandCommit is a function.
   803  func TestGitCommandCommit(t *testing.T) {
   804  	type scenario struct {
   805  		testName           string
   806  		command            func(string, ...string) *exec.Cmd
   807  		getGlobalGitConfig func(string) (string, error)
   808  		test               func(*exec.Cmd, error)
   809  		flags              string
   810  	}
   811  
   812  	scenarios := []scenario{
   813  		{
   814  			"Commit using gpg",
   815  			func(cmd string, args ...string) *exec.Cmd {
   816  				assert.EqualValues(t, "bash", cmd)
   817  				assert.EqualValues(t, []string{"-c", `git commit  -m 'test'`}, args)
   818  
   819  				return exec.Command("echo")
   820  			},
   821  			func(string) (string, error) {
   822  				return "true", nil
   823  			},
   824  			func(cmd *exec.Cmd, err error) {
   825  				assert.NotNil(t, cmd)
   826  				assert.Nil(t, err)
   827  			},
   828  			"",
   829  		},
   830  		{
   831  			"Commit without using gpg",
   832  			func(cmd string, args ...string) *exec.Cmd {
   833  				assert.EqualValues(t, "git", cmd)
   834  				assert.EqualValues(t, []string{"commit", "-m", "test"}, args)
   835  
   836  				return exec.Command("echo")
   837  			},
   838  			func(string) (string, error) {
   839  				return "false", nil
   840  			},
   841  			func(cmd *exec.Cmd, err error) {
   842  				assert.Nil(t, cmd)
   843  				assert.Nil(t, err)
   844  			},
   845  			"",
   846  		},
   847  		{
   848  			"Commit with --no-verify flag",
   849  			func(cmd string, args ...string) *exec.Cmd {
   850  				assert.EqualValues(t, "git", cmd)
   851  				assert.EqualValues(t, []string{"commit", "--no-verify", "-m", "test"}, args)
   852  
   853  				return exec.Command("echo")
   854  			},
   855  			func(string) (string, error) {
   856  				return "false", nil
   857  			},
   858  			func(cmd *exec.Cmd, err error) {
   859  				assert.Nil(t, cmd)
   860  				assert.Nil(t, err)
   861  			},
   862  			"--no-verify",
   863  		},
   864  		{
   865  			"Commit without using gpg with an error",
   866  			func(cmd string, args ...string) *exec.Cmd {
   867  				assert.EqualValues(t, "git", cmd)
   868  				assert.EqualValues(t, []string{"commit", "-m", "test"}, args)
   869  
   870  				return exec.Command("test")
   871  			},
   872  			func(string) (string, error) {
   873  				return "false", nil
   874  			},
   875  			func(cmd *exec.Cmd, err error) {
   876  				assert.Nil(t, cmd)
   877  				assert.Error(t, err)
   878  			},
   879  			"",
   880  		},
   881  	}
   882  
   883  	for _, s := range scenarios {
   884  		t.Run(s.testName, func(t *testing.T) {
   885  			gitCmd := NewDummyGitCommand()
   886  			gitCmd.getGlobalGitConfig = s.getGlobalGitConfig
   887  			gitCmd.OSCommand.command = s.command
   888  			s.test(gitCmd.Commit("test", s.flags))
   889  		})
   890  	}
   891  }
   892  
   893  // TestGitCommandAmendHead is a function.
   894  func TestGitCommandAmendHead(t *testing.T) {
   895  	type scenario struct {
   896  		testName           string
   897  		command            func(string, ...string) *exec.Cmd
   898  		getGlobalGitConfig func(string) (string, error)
   899  		test               func(*exec.Cmd, error)
   900  	}
   901  
   902  	scenarios := []scenario{
   903  		{
   904  			"Amend commit using gpg",
   905  			func(cmd string, args ...string) *exec.Cmd {
   906  				assert.EqualValues(t, "bash", cmd)
   907  				assert.EqualValues(t, []string{"-c", "git commit --amend --no-edit"}, args)
   908  
   909  				return exec.Command("echo")
   910  			},
   911  			func(string) (string, error) {
   912  				return "true", nil
   913  			},
   914  			func(cmd *exec.Cmd, err error) {
   915  				assert.NotNil(t, cmd)
   916  				assert.Nil(t, err)
   917  			},
   918  		},
   919  		{
   920  			"Amend commit without using gpg",
   921  			func(cmd string, args ...string) *exec.Cmd {
   922  				assert.EqualValues(t, "git", cmd)
   923  				assert.EqualValues(t, []string{"commit", "--amend", "--no-edit"}, args)
   924  
   925  				return exec.Command("echo")
   926  			},
   927  			func(string) (string, error) {
   928  				return "false", nil
   929  			},
   930  			func(cmd *exec.Cmd, err error) {
   931  				assert.Nil(t, cmd)
   932  				assert.Nil(t, err)
   933  			},
   934  		},
   935  		{
   936  			"Amend commit without using gpg with an error",
   937  			func(cmd string, args ...string) *exec.Cmd {
   938  				assert.EqualValues(t, "git", cmd)
   939  				assert.EqualValues(t, []string{"commit", "--amend", "--no-edit"}, args)
   940  
   941  				return exec.Command("test")
   942  			},
   943  			func(string) (string, error) {
   944  				return "false", nil
   945  			},
   946  			func(cmd *exec.Cmd, err error) {
   947  				assert.Nil(t, cmd)
   948  				assert.Error(t, err)
   949  			},
   950  		},
   951  	}
   952  
   953  	for _, s := range scenarios {
   954  		t.Run(s.testName, func(t *testing.T) {
   955  			gitCmd := NewDummyGitCommand()
   956  			gitCmd.getGlobalGitConfig = s.getGlobalGitConfig
   957  			gitCmd.OSCommand.command = s.command
   958  			s.test(gitCmd.AmendHead())
   959  		})
   960  	}
   961  }
   962  
   963  // TestGitCommandPush is a function.
   964  func TestGitCommandPush(t *testing.T) {
   965  	type scenario struct {
   966  		testName  string
   967  		command   func(string, ...string) *exec.Cmd
   968  		forcePush bool
   969  		test      func(error)
   970  	}
   971  
   972  	scenarios := []scenario{
   973  		{
   974  			"Push with force disabled",
   975  			func(cmd string, args ...string) *exec.Cmd {
   976  				assert.EqualValues(t, "git", cmd)
   977  				assert.EqualValues(t, []string{"push", "-u", "origin", "test"}, args)
   978  
   979  				return exec.Command("echo")
   980  			},
   981  			false,
   982  			func(err error) {
   983  				assert.NoError(t, err)
   984  			},
   985  		},
   986  		{
   987  			"Push with force enabled",
   988  			func(cmd string, args ...string) *exec.Cmd {
   989  				assert.EqualValues(t, "git", cmd)
   990  				assert.EqualValues(t, []string{"push", "--force-with-lease", "-u", "origin", "test"}, args)
   991  
   992  				return exec.Command("echo")
   993  			},
   994  			true,
   995  			func(err error) {
   996  				assert.NoError(t, err)
   997  			},
   998  		},
   999  		{
  1000  			"Push with an error occurring",
  1001  			func(cmd string, args ...string) *exec.Cmd {
  1002  				assert.EqualValues(t, "git", cmd)
  1003  				assert.EqualValues(t, []string{"push", "-u", "origin", "test"}, args)
  1004  				return exec.Command("test")
  1005  			},
  1006  			false,
  1007  			func(err error) {
  1008  				assert.Error(t, err)
  1009  			},
  1010  		},
  1011  	}
  1012  
  1013  	for _, s := range scenarios {
  1014  		t.Run(s.testName, func(t *testing.T) {
  1015  			gitCmd := NewDummyGitCommand()
  1016  			gitCmd.OSCommand.command = s.command
  1017  			err := gitCmd.Push("test", s.forcePush, func(passOrUname string) string {
  1018  				return "\n"
  1019  			})
  1020  			s.test(err)
  1021  		})
  1022  	}
  1023  }
  1024  
  1025  // TestGitCommandCatFile is a function.
  1026  func TestGitCommandCatFile(t *testing.T) {
  1027  	gitCmd := NewDummyGitCommand()
  1028  	gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
  1029  		assert.EqualValues(t, "cat", cmd)
  1030  		assert.EqualValues(t, []string{"test.txt"}, args)
  1031  
  1032  		return exec.Command("echo", "-n", "test")
  1033  	}
  1034  
  1035  	o, err := gitCmd.CatFile("test.txt")
  1036  	assert.NoError(t, err)
  1037  	assert.Equal(t, "test", o)
  1038  }
  1039  
  1040  // TestGitCommandStageFile is a function.
  1041  func TestGitCommandStageFile(t *testing.T) {
  1042  	gitCmd := NewDummyGitCommand()
  1043  	gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
  1044  		assert.EqualValues(t, "git", cmd)
  1045  		assert.EqualValues(t, []string{"add", "test.txt"}, args)
  1046  
  1047  		return exec.Command("echo")
  1048  	}
  1049  
  1050  	assert.NoError(t, gitCmd.StageFile("test.txt"))
  1051  }
  1052  
  1053  // TestGitCommandUnstageFile is a function.
  1054  func TestGitCommandUnstageFile(t *testing.T) {
  1055  	type scenario struct {
  1056  		testName string
  1057  		command  func(string, ...string) *exec.Cmd
  1058  		test     func(error)
  1059  		tracked  bool
  1060  	}
  1061  
  1062  	scenarios := []scenario{
  1063  		{
  1064  			"Remove an untracked file from staging",
  1065  			func(cmd string, args ...string) *exec.Cmd {
  1066  				assert.EqualValues(t, "git", cmd)
  1067  				assert.EqualValues(t, []string{"rm", "--cached", "test.txt"}, args)
  1068  
  1069  				return exec.Command("echo")
  1070  			},
  1071  			func(err error) {
  1072  				assert.NoError(t, err)
  1073  			},
  1074  			false,
  1075  		},
  1076  		{
  1077  			"Remove a tracked file from staging",
  1078  			func(cmd string, args ...string) *exec.Cmd {
  1079  				assert.EqualValues(t, "git", cmd)
  1080  				assert.EqualValues(t, []string{"reset", "HEAD", "test.txt"}, args)
  1081  
  1082  				return exec.Command("echo")
  1083  			},
  1084  			func(err error) {
  1085  				assert.NoError(t, err)
  1086  			},
  1087  			true,
  1088  		},
  1089  	}
  1090  
  1091  	for _, s := range scenarios {
  1092  		t.Run(s.testName, func(t *testing.T) {
  1093  			gitCmd := NewDummyGitCommand()
  1094  			gitCmd.OSCommand.command = s.command
  1095  			s.test(gitCmd.UnStageFile("test.txt", s.tracked))
  1096  		})
  1097  	}
  1098  }
  1099  
  1100  // TestGitCommandIsInMergeState is a function.
  1101  func TestGitCommandIsInMergeState(t *testing.T) {
  1102  	type scenario struct {
  1103  		testName string
  1104  		command  func(string, ...string) *exec.Cmd
  1105  		test     func(bool, error)
  1106  	}
  1107  
  1108  	scenarios := []scenario{
  1109  		{
  1110  			"An error occurred when running status command",
  1111  			func(cmd string, args ...string) *exec.Cmd {
  1112  				assert.EqualValues(t, "git", cmd)
  1113  				assert.EqualValues(t, []string{"status", "--untracked-files=all"}, args)
  1114  
  1115  				return exec.Command("test")
  1116  			},
  1117  			func(isInMergeState bool, err error) {
  1118  				assert.Error(t, err)
  1119  				assert.False(t, isInMergeState)
  1120  			},
  1121  		},
  1122  		{
  1123  			"Is not in merge state",
  1124  			func(cmd string, args ...string) *exec.Cmd {
  1125  				assert.EqualValues(t, "git", cmd)
  1126  				assert.EqualValues(t, []string{"status", "--untracked-files=all"}, args)
  1127  				return exec.Command("echo")
  1128  			},
  1129  			func(isInMergeState bool, err error) {
  1130  				assert.False(t, isInMergeState)
  1131  				assert.NoError(t, err)
  1132  			},
  1133  		},
  1134  		{
  1135  			"Command output contains conclude merge",
  1136  			func(cmd string, args ...string) *exec.Cmd {
  1137  				assert.EqualValues(t, "git", cmd)
  1138  				assert.EqualValues(t, []string{"status", "--untracked-files=all"}, args)
  1139  				return exec.Command("echo", "'conclude merge'")
  1140  			},
  1141  			func(isInMergeState bool, err error) {
  1142  				assert.True(t, isInMergeState)
  1143  				assert.NoError(t, err)
  1144  			},
  1145  		},
  1146  		{
  1147  			"Command output contains unmerged paths",
  1148  			func(cmd string, args ...string) *exec.Cmd {
  1149  				assert.EqualValues(t, "git", cmd)
  1150  				assert.EqualValues(t, []string{"status", "--untracked-files=all"}, args)
  1151  				return exec.Command("echo", "'unmerged paths'")
  1152  			},
  1153  			func(isInMergeState bool, err error) {
  1154  				assert.True(t, isInMergeState)
  1155  				assert.NoError(t, err)
  1156  			},
  1157  		},
  1158  	}
  1159  
  1160  	for _, s := range scenarios {
  1161  		t.Run(s.testName, func(t *testing.T) {
  1162  			gitCmd := NewDummyGitCommand()
  1163  			gitCmd.OSCommand.command = s.command
  1164  			s.test(gitCmd.IsInMergeState())
  1165  		})
  1166  	}
  1167  }
  1168  
  1169  // TestGitCommandDiscardAllFileChanges is a function.
  1170  func TestGitCommandDiscardAllFileChanges(t *testing.T) {
  1171  	type scenario struct {
  1172  		testName   string
  1173  		command    func() (func(string, ...string) *exec.Cmd, *[][]string)
  1174  		test       func(*[][]string, error)
  1175  		file       *File
  1176  		removeFile func(string) error
  1177  	}
  1178  
  1179  	scenarios := []scenario{
  1180  		{
  1181  			"An error occurred when resetting",
  1182  			func() (func(string, ...string) *exec.Cmd, *[][]string) {
  1183  				cmdsCalled := [][]string{}
  1184  				return func(cmd string, args ...string) *exec.Cmd {
  1185  					cmdsCalled = append(cmdsCalled, args)
  1186  
  1187  					return exec.Command("test")
  1188  				}, &cmdsCalled
  1189  			},
  1190  			func(cmdsCalled *[][]string, err error) {
  1191  				assert.Error(t, err)
  1192  				assert.Len(t, *cmdsCalled, 1)
  1193  				assert.EqualValues(t, *cmdsCalled, [][]string{
  1194  					{"reset", "--", "test"},
  1195  				})
  1196  			},
  1197  			&File{
  1198  				Name:             "test",
  1199  				HasStagedChanges: true,
  1200  			},
  1201  			func(string) error {
  1202  				return nil
  1203  			},
  1204  		},
  1205  		{
  1206  			"An error occurred when removing file",
  1207  			func() (func(string, ...string) *exec.Cmd, *[][]string) {
  1208  				cmdsCalled := [][]string{}
  1209  				return func(cmd string, args ...string) *exec.Cmd {
  1210  					cmdsCalled = append(cmdsCalled, args)
  1211  
  1212  					return exec.Command("test")
  1213  				}, &cmdsCalled
  1214  			},
  1215  			func(cmdsCalled *[][]string, err error) {
  1216  				assert.Error(t, err)
  1217  				assert.EqualError(t, err, "an error occurred when removing file")
  1218  				assert.Len(t, *cmdsCalled, 0)
  1219  			},
  1220  			&File{
  1221  				Name:    "test",
  1222  				Tracked: false,
  1223  			},
  1224  			func(string) error {
  1225  				return fmt.Errorf("an error occurred when removing file")
  1226  			},
  1227  		},
  1228  		{
  1229  			"An error occurred with checkout",
  1230  			func() (func(string, ...string) *exec.Cmd, *[][]string) {
  1231  				cmdsCalled := [][]string{}
  1232  				return func(cmd string, args ...string) *exec.Cmd {
  1233  					cmdsCalled = append(cmdsCalled, args)
  1234  
  1235  					return exec.Command("test")
  1236  				}, &cmdsCalled
  1237  			},
  1238  			func(cmdsCalled *[][]string, err error) {
  1239  				assert.Error(t, err)
  1240  				assert.Len(t, *cmdsCalled, 1)
  1241  				assert.EqualValues(t, *cmdsCalled, [][]string{
  1242  					{"checkout", "--", "test"},
  1243  				})
  1244  			},
  1245  			&File{
  1246  				Name:             "test",
  1247  				Tracked:          true,
  1248  				HasStagedChanges: false,
  1249  			},
  1250  			func(string) error {
  1251  				return nil
  1252  			},
  1253  		},
  1254  		{
  1255  			"Checkout only",
  1256  			func() (func(string, ...string) *exec.Cmd, *[][]string) {
  1257  				cmdsCalled := [][]string{}
  1258  				return func(cmd string, args ...string) *exec.Cmd {
  1259  					cmdsCalled = append(cmdsCalled, args)
  1260  
  1261  					return exec.Command("echo")
  1262  				}, &cmdsCalled
  1263  			},
  1264  			func(cmdsCalled *[][]string, err error) {
  1265  				assert.NoError(t, err)
  1266  				assert.Len(t, *cmdsCalled, 1)
  1267  				assert.EqualValues(t, *cmdsCalled, [][]string{
  1268  					{"checkout", "--", "test"},
  1269  				})
  1270  			},
  1271  			&File{
  1272  				Name:             "test",
  1273  				Tracked:          true,
  1274  				HasStagedChanges: false,
  1275  			},
  1276  			func(string) error {
  1277  				return nil
  1278  			},
  1279  		},
  1280  		{
  1281  			"Reset and checkout",
  1282  			func() (func(string, ...string) *exec.Cmd, *[][]string) {
  1283  				cmdsCalled := [][]string{}
  1284  				return func(cmd string, args ...string) *exec.Cmd {
  1285  					cmdsCalled = append(cmdsCalled, args)
  1286  
  1287  					return exec.Command("echo")
  1288  				}, &cmdsCalled
  1289  			},
  1290  			func(cmdsCalled *[][]string, err error) {
  1291  				assert.NoError(t, err)
  1292  				assert.Len(t, *cmdsCalled, 2)
  1293  				assert.EqualValues(t, *cmdsCalled, [][]string{
  1294  					{"reset", "--", "test"},
  1295  					{"checkout", "--", "test"},
  1296  				})
  1297  			},
  1298  			&File{
  1299  				Name:             "test",
  1300  				Tracked:          true,
  1301  				HasStagedChanges: true,
  1302  			},
  1303  			func(string) error {
  1304  				return nil
  1305  			},
  1306  		},
  1307  		{
  1308  			"Reset and remove",
  1309  			func() (func(string, ...string) *exec.Cmd, *[][]string) {
  1310  				cmdsCalled := [][]string{}
  1311  				return func(cmd string, args ...string) *exec.Cmd {
  1312  					cmdsCalled = append(cmdsCalled, args)
  1313  
  1314  					return exec.Command("echo")
  1315  				}, &cmdsCalled
  1316  			},
  1317  			func(cmdsCalled *[][]string, err error) {
  1318  				assert.NoError(t, err)
  1319  				assert.Len(t, *cmdsCalled, 1)
  1320  				assert.EqualValues(t, *cmdsCalled, [][]string{
  1321  					{"reset", "--", "test"},
  1322  				})
  1323  			},
  1324  			&File{
  1325  				Name:             "test",
  1326  				Tracked:          false,
  1327  				HasStagedChanges: true,
  1328  			},
  1329  			func(filename string) error {
  1330  				assert.Equal(t, "test", filename)
  1331  				return nil
  1332  			},
  1333  		},
  1334  		{
  1335  			"Remove only",
  1336  			func() (func(string, ...string) *exec.Cmd, *[][]string) {
  1337  				cmdsCalled := [][]string{}
  1338  				return func(cmd string, args ...string) *exec.Cmd {
  1339  					cmdsCalled = append(cmdsCalled, args)
  1340  
  1341  					return exec.Command("echo")
  1342  				}, &cmdsCalled
  1343  			},
  1344  			func(cmdsCalled *[][]string, err error) {
  1345  				assert.NoError(t, err)
  1346  				assert.Len(t, *cmdsCalled, 0)
  1347  			},
  1348  			&File{
  1349  				Name:             "test",
  1350  				Tracked:          false,
  1351  				HasStagedChanges: false,
  1352  			},
  1353  			func(filename string) error {
  1354  				assert.Equal(t, "test", filename)
  1355  				return nil
  1356  			},
  1357  		},
  1358  	}
  1359  
  1360  	for _, s := range scenarios {
  1361  		t.Run(s.testName, func(t *testing.T) {
  1362  			var cmdsCalled *[][]string
  1363  			gitCmd := NewDummyGitCommand()
  1364  			gitCmd.OSCommand.command, cmdsCalled = s.command()
  1365  			gitCmd.removeFile = s.removeFile
  1366  			s.test(cmdsCalled, gitCmd.DiscardAllFileChanges(s.file))
  1367  		})
  1368  	}
  1369  }
  1370  
  1371  // TestGitCommandShow is a function.
  1372  func TestGitCommandShow(t *testing.T) {
  1373  	type scenario struct {
  1374  		testName string
  1375  		arg      string
  1376  		command  func(string, ...string) *exec.Cmd
  1377  		test     func(string, error)
  1378  	}
  1379  
  1380  	scenarios := []scenario{
  1381  		{
  1382  			"regular commit",
  1383  			"456abcde",
  1384  			test.CreateMockCommand(t, []*test.CommandSwapper{
  1385  				{
  1386  					Expect:  "git show --color 456abcde",
  1387  					Replace: "echo \"commit ccc771d8b13d5b0d4635db4463556366470fd4f6\nblah\"",
  1388  				},
  1389  				{
  1390  					Expect:  "git rev-list -1 --merges 456abcde^...456abcde",
  1391  					Replace: "echo",
  1392  				},
  1393  			}),
  1394  			func(result string, err error) {
  1395  				assert.NoError(t, err)
  1396  				assert.Equal(t, "commit ccc771d8b13d5b0d4635db4463556366470fd4f6\nblah\n", result)
  1397  			},
  1398  		},
  1399  		{
  1400  			"merge commit",
  1401  			"456abcde",
  1402  			test.CreateMockCommand(t, []*test.CommandSwapper{
  1403  				{
  1404  					Expect:  "git show --color 456abcde",
  1405  					Replace: "echo \"commit ccc771d8b13d5b0d4635db4463556366470fd4f6\nMerge: 1a6a69a 3b51d7c\"",
  1406  				},
  1407  				{
  1408  					Expect:  "git rev-list -1 --merges 456abcde^...456abcde",
  1409  					Replace: "echo aa30e006433628ba9281652952b34d8aacda9c01",
  1410  				},
  1411  				{
  1412  					Expect:  "git diff --color 1a6a69a...3b51d7c",
  1413  					Replace: "echo blah",
  1414  				},
  1415  			}),
  1416  			func(result string, err error) {
  1417  				assert.NoError(t, err)
  1418  				assert.Equal(t, "commit ccc771d8b13d5b0d4635db4463556366470fd4f6\nMerge: 1a6a69a 3b51d7c\nblah\n", result)
  1419  			},
  1420  		},
  1421  	}
  1422  
  1423  	gitCmd := NewDummyGitCommand()
  1424  
  1425  	for _, s := range scenarios {
  1426  		gitCmd.OSCommand.command = s.command
  1427  		s.test(gitCmd.Show(s.arg))
  1428  	}
  1429  }
  1430  
  1431  // TestGitCommandCheckout is a function.
  1432  func TestGitCommandCheckout(t *testing.T) {
  1433  	type scenario struct {
  1434  		testName string
  1435  		command  func(string, ...string) *exec.Cmd
  1436  		test     func(error)
  1437  		force    bool
  1438  	}
  1439  
  1440  	scenarios := []scenario{
  1441  		{
  1442  			"Checkout",
  1443  			func(cmd string, args ...string) *exec.Cmd {
  1444  				assert.EqualValues(t, "git", cmd)
  1445  				assert.EqualValues(t, []string{"checkout", "test"}, args)
  1446  
  1447  				return exec.Command("echo")
  1448  			},
  1449  			func(err error) {
  1450  				assert.NoError(t, err)
  1451  			},
  1452  			false,
  1453  		},
  1454  		{
  1455  			"Checkout forced",
  1456  			func(cmd string, args ...string) *exec.Cmd {
  1457  				assert.EqualValues(t, "git", cmd)
  1458  				assert.EqualValues(t, []string{"checkout", "--force", "test"}, args)
  1459  
  1460  				return exec.Command("echo")
  1461  			},
  1462  			func(err error) {
  1463  				assert.NoError(t, err)
  1464  			},
  1465  			true,
  1466  		},
  1467  	}
  1468  
  1469  	for _, s := range scenarios {
  1470  		t.Run(s.testName, func(t *testing.T) {
  1471  			gitCmd := NewDummyGitCommand()
  1472  			gitCmd.OSCommand.command = s.command
  1473  			s.test(gitCmd.Checkout("test", s.force))
  1474  		})
  1475  	}
  1476  }
  1477  
  1478  // TestGitCommandGetBranchGraph is a function.
  1479  func TestGitCommandGetBranchGraph(t *testing.T) {
  1480  	gitCmd := NewDummyGitCommand()
  1481  	gitCmd.OSCommand.command = func(cmd string, args ...string) *exec.Cmd {
  1482  		assert.EqualValues(t, "git", cmd)
  1483  		assert.EqualValues(t, []string{"log", "--graph", "--color", "--abbrev-commit", "--decorate", "--date=relative", "--pretty=medium", "-100", "test"}, args)
  1484  
  1485  		return exec.Command("echo")
  1486  	}
  1487  
  1488  	_, err := gitCmd.GetBranchGraph("test")
  1489  	assert.NoError(t, err)
  1490  }
  1491  
  1492  // TestGitCommandDiff is a function.
  1493  func TestGitCommandDiff(t *testing.T) {
  1494  	type scenario struct {
  1495  		testName string
  1496  		command  func(string, ...string) *exec.Cmd
  1497  		file     *File
  1498  		plain    bool
  1499  	}
  1500  
  1501  	scenarios := []scenario{
  1502  		{
  1503  			"Default case",
  1504  			func(cmd string, args ...string) *exec.Cmd {
  1505  				assert.EqualValues(t, "git", cmd)
  1506  				assert.EqualValues(t, []string{"diff", "--color", "--", "test.txt"}, args)
  1507  
  1508  				return exec.Command("echo")
  1509  			},
  1510  			&File{
  1511  				Name:             "test.txt",
  1512  				HasStagedChanges: false,
  1513  				Tracked:          true,
  1514  			},
  1515  			false,
  1516  		},
  1517  		{
  1518  			"Default case",
  1519  			func(cmd string, args ...string) *exec.Cmd {
  1520  				assert.EqualValues(t, "git", cmd)
  1521  				assert.EqualValues(t, []string{"diff", "--", "test.txt"}, args)
  1522  
  1523  				return exec.Command("echo")
  1524  			},
  1525  			&File{
  1526  				Name:             "test.txt",
  1527  				HasStagedChanges: false,
  1528  				Tracked:          true,
  1529  			},
  1530  			true,
  1531  		},
  1532  		{
  1533  			"All changes staged",
  1534  			func(cmd string, args ...string) *exec.Cmd {
  1535  				assert.EqualValues(t, "git", cmd)
  1536  				assert.EqualValues(t, []string{"diff", "--color", "--cached", "--", "test.txt"}, args)
  1537  
  1538  				return exec.Command("echo")
  1539  			},
  1540  			&File{
  1541  				Name:               "test.txt",
  1542  				HasStagedChanges:   true,
  1543  				HasUnstagedChanges: false,
  1544  				Tracked:            true,
  1545  			},
  1546  			false,
  1547  		},
  1548  		{
  1549  			"File not tracked and file has no staged changes",
  1550  			func(cmd string, args ...string) *exec.Cmd {
  1551  				assert.EqualValues(t, "git", cmd)
  1552  				assert.EqualValues(t, []string{"diff", "--color", "--no-index", "/dev/null", "test.txt"}, args)
  1553  
  1554  				return exec.Command("echo")
  1555  			},
  1556  			&File{
  1557  				Name:             "test.txt",
  1558  				HasStagedChanges: false,
  1559  				Tracked:          false,
  1560  			},
  1561  			false,
  1562  		},
  1563  	}
  1564  
  1565  	for _, s := range scenarios {
  1566  		t.Run(s.testName, func(t *testing.T) {
  1567  			gitCmd := NewDummyGitCommand()
  1568  			gitCmd.OSCommand.command = s.command
  1569  			gitCmd.Diff(s.file, s.plain)
  1570  		})
  1571  	}
  1572  }
  1573  
  1574  // TestGitCommandCurrentBranchName is a function.
  1575  func TestGitCommandCurrentBranchName(t *testing.T) {
  1576  	type scenario struct {
  1577  		testName string
  1578  		command  func(string, ...string) *exec.Cmd
  1579  		test     func(string, error)
  1580  	}
  1581  
  1582  	scenarios := []scenario{
  1583  		{
  1584  			"says we are on the master branch if we are",
  1585  			func(cmd string, args ...string) *exec.Cmd {
  1586  				assert.Equal(t, "git", cmd)
  1587  				return exec.Command("echo", "master")
  1588  			},
  1589  			func(output string, err error) {
  1590  				assert.NoError(t, err)
  1591  				assert.EqualValues(t, "master", output)
  1592  			},
  1593  		},
  1594  		{
  1595  			"falls back to git rev-parse if symbolic-ref fails",
  1596  			func(cmd string, args ...string) *exec.Cmd {
  1597  				assert.EqualValues(t, "git", cmd)
  1598  
  1599  				switch args[0] {
  1600  				case "symbolic-ref":
  1601  					assert.EqualValues(t, []string{"symbolic-ref", "--short", "HEAD"}, args)
  1602  					return exec.Command("test")
  1603  				case "rev-parse":
  1604  					assert.EqualValues(t, []string{"rev-parse", "--short", "HEAD"}, args)
  1605  					return exec.Command("echo", "master")
  1606  				}
  1607  
  1608  				return nil
  1609  			},
  1610  			func(output string, err error) {
  1611  				assert.NoError(t, err)
  1612  				assert.EqualValues(t, "master", output)
  1613  			},
  1614  		},
  1615  		{
  1616  			"bubbles up error if there is one",
  1617  			func(cmd string, args ...string) *exec.Cmd {
  1618  				assert.Equal(t, "git", cmd)
  1619  				return exec.Command("test")
  1620  			},
  1621  			func(output string, err error) {
  1622  				assert.Error(t, err)
  1623  				assert.EqualValues(t, "", output)
  1624  			},
  1625  		},
  1626  	}
  1627  
  1628  	for _, s := range scenarios {
  1629  		t.Run(s.testName, func(t *testing.T) {
  1630  			gitCmd := NewDummyGitCommand()
  1631  			gitCmd.OSCommand.command = s.command
  1632  			s.test(gitCmd.CurrentBranchName())
  1633  		})
  1634  	}
  1635  }
  1636  
  1637  func TestGitCommandApplyPatch(t *testing.T) {
  1638  	type scenario struct {
  1639  		testName string
  1640  		command  func(string, ...string) *exec.Cmd
  1641  		test     func(string, error)
  1642  	}
  1643  
  1644  	scenarios := []scenario{
  1645  		{
  1646  			"valid case",
  1647  			func(cmd string, args ...string) *exec.Cmd {
  1648  				assert.Equal(t, "git", cmd)
  1649  				assert.EqualValues(t, []string{"apply", "--cached"}, args[0:2])
  1650  				filename := args[2]
  1651  				content, err := ioutil.ReadFile(filename)
  1652  				assert.NoError(t, err)
  1653  
  1654  				assert.Equal(t, "test", string(content))
  1655  
  1656  				return exec.Command("echo", "done")
  1657  			},
  1658  			func(output string, err error) {
  1659  				assert.NoError(t, err)
  1660  				assert.EqualValues(t, "done\n", output)
  1661  			},
  1662  		},
  1663  		{
  1664  			"command returns error",
  1665  			func(cmd string, args ...string) *exec.Cmd {
  1666  				assert.Equal(t, "git", cmd)
  1667  				assert.EqualValues(t, []string{"apply", "--cached"}, args[0:2])
  1668  				filename := args[2]
  1669  				// TODO: Ideally we want to mock out OSCommand here so that we're not
  1670  				// double handling testing it's CreateTempFile functionality,
  1671  				// but it is going to take a bit of work to make a proper mock for it
  1672  				// so I'm leaving it for another PR
  1673  				content, err := ioutil.ReadFile(filename)
  1674  				assert.NoError(t, err)
  1675  
  1676  				assert.Equal(t, "test", string(content))
  1677  
  1678  				return exec.Command("test")
  1679  			},
  1680  			func(output string, err error) {
  1681  				assert.Error(t, err)
  1682  			},
  1683  		},
  1684  	}
  1685  
  1686  	for _, s := range scenarios {
  1687  		t.Run(s.testName, func(t *testing.T) {
  1688  			gitCmd := NewDummyGitCommand()
  1689  			gitCmd.OSCommand.command = s.command
  1690  			s.test(gitCmd.ApplyPatch("test"))
  1691  		})
  1692  	}
  1693  }
  1694  
  1695  // TestGitCommandRebaseBranch is a function.
  1696  func TestGitCommandRebaseBranch(t *testing.T) {
  1697  	type scenario struct {
  1698  		testName string
  1699  		arg      string
  1700  		command  func(string, ...string) *exec.Cmd
  1701  		test     func(error)
  1702  	}
  1703  
  1704  	scenarios := []scenario{
  1705  		{
  1706  			"successful rebase",
  1707  			"master",
  1708  			test.CreateMockCommand(t, []*test.CommandSwapper{
  1709  				{
  1710  					Expect:  "git rebase --interactive --autostash master",
  1711  					Replace: "echo",
  1712  				},
  1713  			}),
  1714  			func(err error) {
  1715  				assert.NoError(t, err)
  1716  			},
  1717  		},
  1718  		{
  1719  			"unsuccessful rebase",
  1720  			"master",
  1721  			test.CreateMockCommand(t, []*test.CommandSwapper{
  1722  				{
  1723  					Expect:  "git rebase --interactive --autostash master",
  1724  					Replace: "test",
  1725  				},
  1726  			}),
  1727  			func(err error) {
  1728  				assert.Error(t, err)
  1729  			},
  1730  		},
  1731  	}
  1732  
  1733  	gitCmd := NewDummyGitCommand()
  1734  
  1735  	for _, s := range scenarios {
  1736  		t.Run(s.testName, func(t *testing.T) {
  1737  			gitCmd.OSCommand.command = s.command
  1738  			s.test(gitCmd.RebaseBranch(s.arg))
  1739  		})
  1740  	}
  1741  }
  1742  
  1743  // TestGitCommandCheckoutFile is a function.
  1744  func TestGitCommandCheckoutFile(t *testing.T) {
  1745  	type scenario struct {
  1746  		testName  string
  1747  		commitSha string
  1748  		fileName  string
  1749  		command   func(string, ...string) *exec.Cmd
  1750  		test      func(error)
  1751  	}
  1752  
  1753  	scenarios := []scenario{
  1754  		{
  1755  			"typical case",
  1756  			"11af912",
  1757  			"test999.txt",
  1758  			test.CreateMockCommand(t, []*test.CommandSwapper{
  1759  				{
  1760  					Expect:  "git checkout 11af912 test999.txt",
  1761  					Replace: "echo",
  1762  				},
  1763  			}),
  1764  			func(err error) {
  1765  				assert.NoError(t, err)
  1766  			},
  1767  		},
  1768  		{
  1769  			"returns error if there is one",
  1770  			"11af912",
  1771  			"test999.txt",
  1772  			test.CreateMockCommand(t, []*test.CommandSwapper{
  1773  				{
  1774  					Expect:  "git checkout 11af912 test999.txt",
  1775  					Replace: "test",
  1776  				},
  1777  			}),
  1778  			func(err error) {
  1779  				assert.Error(t, err)
  1780  			},
  1781  		},
  1782  	}
  1783  
  1784  	gitCmd := NewDummyGitCommand()
  1785  
  1786  	for _, s := range scenarios {
  1787  		t.Run(s.testName, func(t *testing.T) {
  1788  			gitCmd.OSCommand.command = s.command
  1789  			s.test(gitCmd.CheckoutFile(s.commitSha, s.fileName))
  1790  		})
  1791  	}
  1792  }
  1793  
  1794  // TestGitCommandDiscardOldFileChanges is a function.
  1795  func TestGitCommandDiscardOldFileChanges(t *testing.T) {
  1796  	type scenario struct {
  1797  		testName          string
  1798  		getLocalGitConfig func(string) (string, error)
  1799  		commits           []*Commit
  1800  		commitIndex       int
  1801  		fileName          string
  1802  		command           func(string, ...string) *exec.Cmd
  1803  		test              func(error)
  1804  	}
  1805  
  1806  	scenarios := []scenario{
  1807  		{
  1808  			"returns error when index outside of range of commits",
  1809  			func(string) (string, error) {
  1810  				return "", nil
  1811  			},
  1812  			[]*Commit{},
  1813  			0,
  1814  			"test999.txt",
  1815  			nil,
  1816  			func(err error) {
  1817  				assert.Error(t, err)
  1818  			},
  1819  		},
  1820  		{
  1821  			"returns error when using gpg",
  1822  			func(string) (string, error) {
  1823  				return "true", nil
  1824  			},
  1825  			[]*Commit{{Name: "commit", Sha: "123456"}},
  1826  			0,
  1827  			"test999.txt",
  1828  			nil,
  1829  			func(err error) {
  1830  				assert.Error(t, err)
  1831  			},
  1832  		},
  1833  		{
  1834  			"checks out file if it already existed",
  1835  			func(string) (string, error) {
  1836  				return "", nil
  1837  			},
  1838  			[]*Commit{
  1839  				{Name: "commit", Sha: "123456"},
  1840  				{Name: "commit2", Sha: "abcdef"},
  1841  			},
  1842  			0,
  1843  			"test999.txt",
  1844  			test.CreateMockCommand(t, []*test.CommandSwapper{
  1845  				{
  1846  					Expect:  "git rebase --interactive --autostash abcdef",
  1847  					Replace: "echo",
  1848  				},
  1849  				{
  1850  					Expect:  "git cat-file -e HEAD^:test999.txt",
  1851  					Replace: "echo",
  1852  				},
  1853  				{
  1854  					Expect:  "git checkout HEAD^ test999.txt",
  1855  					Replace: "echo",
  1856  				},
  1857  				{
  1858  					Expect:  "git commit --amend --no-edit",
  1859  					Replace: "echo",
  1860  				},
  1861  				{
  1862  					Expect:  "git rebase --continue",
  1863  					Replace: "echo",
  1864  				},
  1865  			}),
  1866  			func(err error) {
  1867  				assert.NoError(t, err)
  1868  			},
  1869  		},
  1870  		// test for when the file was created within the commit requires a refactor to support proper mocks
  1871  		// currently we'd need to mock out the os.Remove function and that's gonna introduce tech debt
  1872  	}
  1873  
  1874  	gitCmd := NewDummyGitCommand()
  1875  
  1876  	for _, s := range scenarios {
  1877  		t.Run(s.testName, func(t *testing.T) {
  1878  			gitCmd.OSCommand.command = s.command
  1879  			gitCmd.getLocalGitConfig = s.getLocalGitConfig
  1880  			s.test(gitCmd.DiscardOldFileChanges(s.commits, s.commitIndex, s.fileName))
  1881  		})
  1882  	}
  1883  }
  1884  
  1885  // TestGitCommandShowCommitFile is a function.
  1886  func TestGitCommandShowCommitFile(t *testing.T) {
  1887  	type scenario struct {
  1888  		testName  string
  1889  		commitSha string
  1890  		fileName  string
  1891  		command   func(string, ...string) *exec.Cmd
  1892  		test      func(string, error)
  1893  	}
  1894  
  1895  	scenarios := []scenario{
  1896  		{
  1897  			"valid case",
  1898  			"123456",
  1899  			"hello.txt",
  1900  			test.CreateMockCommand(t, []*test.CommandSwapper{
  1901  				{
  1902  					Expect:  "git show --color 123456 -- hello.txt",
  1903  					Replace: "echo -n hello",
  1904  				},
  1905  			}),
  1906  			func(str string, err error) {
  1907  				assert.NoError(t, err)
  1908  				assert.Equal(t, "hello", str)
  1909  			},
  1910  		},
  1911  	}
  1912  
  1913  	gitCmd := NewDummyGitCommand()
  1914  
  1915  	for _, s := range scenarios {
  1916  		t.Run(s.testName, func(t *testing.T) {
  1917  			gitCmd.OSCommand.command = s.command
  1918  			s.test(gitCmd.ShowCommitFile(s.commitSha, s.fileName))
  1919  		})
  1920  	}
  1921  }
  1922  
  1923  // TestGitCommandGetCommitFiles is a function.
  1924  func TestGitCommandGetCommitFiles(t *testing.T) {
  1925  	type scenario struct {
  1926  		testName  string
  1927  		commitSha string
  1928  		command   func(string, ...string) *exec.Cmd
  1929  		test      func([]*CommitFile, error)
  1930  	}
  1931  
  1932  	scenarios := []scenario{
  1933  		{
  1934  			"valid case",
  1935  			"123456",
  1936  			test.CreateMockCommand(t, []*test.CommandSwapper{
  1937  				{
  1938  					Expect:  "git show --pretty= --name-only 123456",
  1939  					Replace: "echo 'hello\nworld'",
  1940  				},
  1941  			}),
  1942  			func(commitFiles []*CommitFile, err error) {
  1943  				assert.NoError(t, err)
  1944  				assert.Equal(t, []*CommitFile{
  1945  					{Sha: "123456", Name: "hello", DisplayString: "hello"},
  1946  					{Sha: "123456", Name: "world", DisplayString: "world"},
  1947  				}, commitFiles)
  1948  			},
  1949  		},
  1950  	}
  1951  
  1952  	gitCmd := NewDummyGitCommand()
  1953  
  1954  	for _, s := range scenarios {
  1955  		t.Run(s.testName, func(t *testing.T) {
  1956  			gitCmd.OSCommand.command = s.command
  1957  			s.test(gitCmd.GetCommitFiles(s.commitSha))
  1958  		})
  1959  	}
  1960  }
  1961  
  1962  // TestGitCommandDiscardUnstagedFileChanges is a function.
  1963  func TestGitCommandDiscardUnstagedFileChanges(t *testing.T) {
  1964  	type scenario struct {
  1965  		testName string
  1966  		file     *File
  1967  		command  func(string, ...string) *exec.Cmd
  1968  		test     func(error)
  1969  	}
  1970  
  1971  	scenarios := []scenario{
  1972  		{
  1973  			"valid case",
  1974  			&File{Name: "test.txt"},
  1975  			test.CreateMockCommand(t, []*test.CommandSwapper{
  1976  				{
  1977  					Expect:  `git checkout -- "test.txt"`,
  1978  					Replace: "echo",
  1979  				},
  1980  			}),
  1981  			func(err error) {
  1982  				assert.NoError(t, err)
  1983  			},
  1984  		},
  1985  	}
  1986  
  1987  	gitCmd := NewDummyGitCommand()
  1988  
  1989  	for _, s := range scenarios {
  1990  		t.Run(s.testName, func(t *testing.T) {
  1991  			gitCmd.OSCommand.command = s.command
  1992  			s.test(gitCmd.DiscardUnstagedFileChanges(s.file))
  1993  		})
  1994  	}
  1995  }
  1996  
  1997  // TestGitCommandDiscardAnyUnstagedFileChanges is a function.
  1998  func TestGitCommandDiscardAnyUnstagedFileChanges(t *testing.T) {
  1999  	type scenario struct {
  2000  		testName string
  2001  		command  func(string, ...string) *exec.Cmd
  2002  		test     func(error)
  2003  	}
  2004  
  2005  	scenarios := []scenario{
  2006  		{
  2007  			"valid case",
  2008  			test.CreateMockCommand(t, []*test.CommandSwapper{
  2009  				{
  2010  					Expect:  `git checkout -- .`,
  2011  					Replace: "echo",
  2012  				},
  2013  			}),
  2014  			func(err error) {
  2015  				assert.NoError(t, err)
  2016  			},
  2017  		},
  2018  	}
  2019  
  2020  	gitCmd := NewDummyGitCommand()
  2021  
  2022  	for _, s := range scenarios {
  2023  		t.Run(s.testName, func(t *testing.T) {
  2024  			gitCmd.OSCommand.command = s.command
  2025  			s.test(gitCmd.DiscardAnyUnstagedFileChanges())
  2026  		})
  2027  	}
  2028  }
  2029  
  2030  // TestGitCommandRemoveUntrackedFiles is a function.
  2031  func TestGitCommandRemoveUntrackedFiles(t *testing.T) {
  2032  	type scenario struct {
  2033  		testName string
  2034  		command  func(string, ...string) *exec.Cmd
  2035  		test     func(error)
  2036  	}
  2037  
  2038  	scenarios := []scenario{
  2039  		{
  2040  			"valid case",
  2041  			test.CreateMockCommand(t, []*test.CommandSwapper{
  2042  				{
  2043  					Expect:  `git clean -fd`,
  2044  					Replace: "echo",
  2045  				},
  2046  			}),
  2047  			func(err error) {
  2048  				assert.NoError(t, err)
  2049  			},
  2050  		},
  2051  	}
  2052  
  2053  	gitCmd := NewDummyGitCommand()
  2054  
  2055  	for _, s := range scenarios {
  2056  		t.Run(s.testName, func(t *testing.T) {
  2057  			gitCmd.OSCommand.command = s.command
  2058  			s.test(gitCmd.RemoveUntrackedFiles())
  2059  		})
  2060  	}
  2061  }
  2062  
  2063  // TestGitCommandResetHardHead is a function.
  2064  func TestGitCommandResetHardHead(t *testing.T) {
  2065  	type scenario struct {
  2066  		testName string
  2067  		command  func(string, ...string) *exec.Cmd
  2068  		test     func(error)
  2069  	}
  2070  
  2071  	scenarios := []scenario{
  2072  		{
  2073  			"valid case",
  2074  			test.CreateMockCommand(t, []*test.CommandSwapper{
  2075  				{
  2076  					Expect:  `git reset --hard HEAD`,
  2077  					Replace: "echo",
  2078  				},
  2079  			}),
  2080  			func(err error) {
  2081  				assert.NoError(t, err)
  2082  			},
  2083  		},
  2084  	}
  2085  
  2086  	gitCmd := NewDummyGitCommand()
  2087  
  2088  	for _, s := range scenarios {
  2089  		t.Run(s.testName, func(t *testing.T) {
  2090  			gitCmd.OSCommand.command = s.command
  2091  			s.test(gitCmd.ResetHardHead())
  2092  		})
  2093  	}
  2094  }
  2095  
  2096  // TestGitCommandCreateFixupCommit is a function.
  2097  func TestGitCommandCreateFixupCommit(t *testing.T) {
  2098  	type scenario struct {
  2099  		testName string
  2100  		sha      string
  2101  		command  func(string, ...string) *exec.Cmd
  2102  		test     func(error)
  2103  	}
  2104  
  2105  	scenarios := []scenario{
  2106  		{
  2107  			"valid case",
  2108  			"12345",
  2109  			test.CreateMockCommand(t, []*test.CommandSwapper{
  2110  				{
  2111  					Expect:  `git commit --fixup=12345`,
  2112  					Replace: "echo",
  2113  				},
  2114  			}),
  2115  			func(err error) {
  2116  				assert.NoError(t, err)
  2117  			},
  2118  		},
  2119  	}
  2120  
  2121  	gitCmd := NewDummyGitCommand()
  2122  
  2123  	for _, s := range scenarios {
  2124  		t.Run(s.testName, func(t *testing.T) {
  2125  			gitCmd.OSCommand.command = s.command
  2126  			s.test(gitCmd.CreateFixupCommit(s.sha))
  2127  		})
  2128  	}
  2129  }
  2130  
  2131  func TestFindDotGitDir(t *testing.T) {
  2132  	type scenario struct {
  2133  		testName string
  2134  		stat     func(string) (os.FileInfo, error)
  2135  		readFile func(filename string) ([]byte, error)
  2136  		test     func(string, error)
  2137  	}
  2138  
  2139  	scenarios := []scenario{
  2140  		{
  2141  			".git is a directory",
  2142  			func(dotGit string) (os.FileInfo, error) {
  2143  				assert.Equal(t, ".git", dotGit)
  2144  				return os.Stat("testdata/a_dir")
  2145  			},
  2146  			func(dotGit string) ([]byte, error) {
  2147  				assert.Fail(t, "readFile should not be called if .git is a directory")
  2148  				return nil, nil
  2149  			},
  2150  			func(gitDir string, err error) {
  2151  				assert.NoError(t, err)
  2152  				assert.Equal(t, ".git", gitDir)
  2153  			},
  2154  		},
  2155  		{
  2156  			".git is a file",
  2157  			func(dotGit string) (os.FileInfo, error) {
  2158  				assert.Equal(t, ".git", dotGit)
  2159  				return os.Stat("testdata/a_file")
  2160  			},
  2161  			func(dotGit string) ([]byte, error) {
  2162  				assert.Equal(t, ".git", dotGit)
  2163  				return []byte("gitdir: blah\n"), nil
  2164  			},
  2165  			func(gitDir string, err error) {
  2166  				assert.NoError(t, err)
  2167  				assert.Equal(t, "blah", gitDir)
  2168  			},
  2169  		},
  2170  		{
  2171  			"os.Stat returns an error",
  2172  			func(dotGit string) (os.FileInfo, error) {
  2173  				assert.Equal(t, ".git", dotGit)
  2174  				return nil, errors.New("error")
  2175  			},
  2176  			func(dotGit string) ([]byte, error) {
  2177  				assert.Fail(t, "readFile should not be called os.Stat returns an error")
  2178  				return nil, nil
  2179  			},
  2180  			func(gitDir string, err error) {
  2181  				assert.Error(t, err)
  2182  			},
  2183  		},
  2184  		{
  2185  			"readFile returns an error",
  2186  			func(dotGit string) (os.FileInfo, error) {
  2187  				assert.Equal(t, ".git", dotGit)
  2188  				return os.Stat("testdata/a_file")
  2189  			},
  2190  			func(dotGit string) ([]byte, error) {
  2191  				return nil, errors.New("error")
  2192  			},
  2193  			func(gitDir string, err error) {
  2194  				assert.Error(t, err)
  2195  			},
  2196  		},
  2197  	}
  2198  
  2199  	for _, s := range scenarios {
  2200  		t.Run(s.testName, func(t *testing.T) {
  2201  			s.test(findDotGitDir(s.stat, s.readFile))
  2202  		})
  2203  	}
  2204  }