github.com/yrvsyh/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 }