Convert the git package into functions that can be mocked with go-mock

Idea:

To make git related things easier to add and test, the GitLab CLI is going to use gomock for our git library functions.

We'll do this in 3 steps:

  1. Create all new functions that can be mocked, in a separate library file
  2. Replace functions in the git library with the new [mockable] functions.
    • This is a good opportunity to clean these up. There could be some functions that can be refactored to be cleaner/simpler.
    • We should make sure there are adequate tests for all these as well.
  3. When one function is completely replaced, remove the old one and move it to the git module.

Examples:

We have a function called git.Backflip.

  1. Create a new function git_mock.Backflip that is part of the StandardGitRunner interface:
func (g *StandardGitRunner) Backflip(branch string) error {
	_, stderr, err := g.runGitCommand("do-a-backflip", branch)
	if err != nil {
		return fmt.Errorf("could not sufficiently backflip: %v - %s", err, stderr)
	}
	return nil
}
  1. Create a test for git_mock.Backflip:
func TestBackflip(t *testing.T) {
	tests := []struct {
		name          string
		branch        string
		setupMock     func(*MockGitInterface, string)
		expectedError error
	}{
		{
			name:   "successful backflip",
			branch: "backflipbranch",
			setupMock: func(m *MockGitInterface, branch string) {
				m.EXPECT().Backflip(branch).Return(nil)
			},
			expectedError: nil,
		},
		{
			name:   "branch checkout fails",
			branch: "faklsdjflkawjef",
			setupMock: func(m *MockGitInterface, branch string) {
				m.EXPECT().CheckoutBranch(branch).Return(errors.New("could not checkout branch"))
			},
			expectedError: errors.New("could not checkout branch"),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctrl := gomock.NewController(t)
			defer ctrl.Finish()

			mockGit := NewMockGitInterface(ctrl)

			tt.setupMock(mockGit, tt.branch)

			err := mockGit.CheckoutBranch(tt.branch)

			if tt.expectedError != nil {
				require.Error(t, err)
				require.Equal(t, tt.expectedError.Error(), err.Error())
			} else {
				require.NoError(t, err)
			}
		})
	}
}
  1. Replace git.Backflip with git_interface.Backflip in the code. (and make sure everything is working!)
diff --git a/backfip_cmd.go b/backfip_cmd.go
index 3ac17f4c32c2..2c46ee8fc772 100644
--- a/backfip_cmd.go
+++ b/backfip_cmd.go
@@ -1,7 +1,7 @@
 import "fmt"

 func DoABackflip(branch string) error {
-	err := git.Backflip(branch)
+	err := git_interface.Backflip(branch)
 	if err != nil {
 		return fmt.Errorf("something went wrong with the backflip: %v", stderr)
 	}

Notes:

Doesn't this make git based commands subject to errors if git changes syntax?

It does, good thinking! That's why we'll include an integration test layer that actually tests these commands are getting (something close to) the output and results we expect.

Where was this discussed?

Please check the thread in !1945 (comment 2389898291)

To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information