Repository: lindell/multi-gitter
Pull request #41 (2020-12-09 21:16:57)

feat: added base-branch flag


Patch:
diff --git a/cmd/run.go b/cmd/run.go
index f1cef8e..8568dfb 100755
--- a/cmd/run.go
+++ b/cmd/run.go
@@ -35,6 +35,7 @@ func RunCmd() *cobra.Command {
 	}
 
 	cmd.Flags().StringP("branch", "B", "multi-gitter-branch", "The name of the branch where changes are committed.")
+	cmd.Flags().StringP("base-branch", "", "", "The branch which the changes will be based on.")
 	cmd.Flags().StringP("pr-title", "t", "", "The title of the PR. Will default to the first line of the commit message if none is set.")
 	cmd.Flags().StringP("pr-body", "b", "", "The body of the commit message. Will default to everything but the first line of the commit message if none is set.")
 	cmd.Flags().StringP("commit-message", "m", "", "The commit message. Will default to title + body if none is set.")
@@ -55,6 +56,7 @@ func run(cmd *cobra.Command, args []string) error {
 	flag := cmd.Flags()
 
 	branchName, _ := flag.GetString("branch")
+	baseBranchName, _ := flag.GetString("base-branch")
 	prTitle, _ := flag.GetString("pr-title")
 	prBody, _ := flag.GetString("pr-body")
 	commitMessage, _ := flag.GetString("commit-message")
@@ -162,6 +164,7 @@ func run(cmd *cobra.Command, args []string) error {
 		MaxReviewers:     maxReviewers,
 		DryRun:           dryRun,
 		CommitAuthor:     commitAuthor,
+		BaseBranch:       baseBranchName,
 
 		Concurrent: concurrent,
 	}
diff --git a/internal/git/git.go b/internal/git/git.go
index 32e54de..c9f4fca 100755
--- a/internal/git/git.go
+++ b/internal/git/git.go
@@ -24,16 +24,18 @@ type Git struct {
 }
 
 // Clone a repository
-func (g *Git) Clone() error {
+func (g *Git) Clone(branchName string) error {
 	u, err := url.Parse(g.Repo)
 	if err != nil {
 		return err
 	}
 
 	r, err := git.PlainClone(g.Directory, false, &git.CloneOptions{
-		URL:        u.String(),
-		RemoteName: "origin",
-		Depth:      10,
+		URL:           u.String(),
+		RemoteName:    "origin",
+		Depth:         10,
+		ReferenceName: plumbing.NewBranchReferenceName(branchName),
+		SingleBranch:  true,
 	})
 	if err != nil {
 		return err
diff --git a/internal/multigitter/print.go b/internal/multigitter/print.go
index 03e51ac..5739dd7 100755
--- a/internal/multigitter/print.go
+++ b/internal/multigitter/print.go
@@ -81,7 +81,7 @@ func (r Printer) runSingleRepo(ctx context.Context, repo domain.Repository) erro
 		Repo:      repo.URL(r.Token),
 	}
 
-	err = sourceController.Clone()
+	err = sourceController.Clone(repo.DefaultBranch())
 	if err != nil {
 		return err
 	}
diff --git a/internal/multigitter/run.go b/internal/multigitter/run.go
index 481eaae..8febe42 100755
--- a/internal/multigitter/run.go
+++ b/internal/multigitter/run.go
@@ -2,7 +2,6 @@ package multigitter
 
 import (
 	"context"
-	"errors"
 	"fmt"
 	"io"
 	"io/ioutil"
@@ -11,6 +10,7 @@ import (
 	"os/exec"
 	"sync"
 
+	"github.com/pkg/errors"
 	log "github.com/sirupsen/logrus"
 
 	"github.com/lindell/multi-gitter/internal/domain"
@@ -46,6 +46,7 @@ type Runner struct {
 	MaxReviewers     int // If set to zero, all reviewers will be used
 	DryRun           bool
 	CommitAuthor     *domain.CommitAuthor
+	BaseBranch       string // The base branch of the PR, use default branch if not set
 
 	Concurrent int
 }
@@ -129,15 +130,20 @@ func (r Runner) runSingleRepo(ctx context.Context, repo domain.Repository) error
 		Repo:      repo.URL(r.Token),
 	}
 
-	err = sourceController.Clone()
+	baseBranch := r.BaseBranch
+	if baseBranch == "" {
+		baseBranch = repo.DefaultBranch()
+	}
+
+	err = sourceController.Clone(baseBranch)
 	if err != nil {
 		return err
 	}
 
-	branchExist, err := sourceController.BranchExist(r.FeatureBranch)
+	featureBranchExist, err := sourceController.BranchExist(r.FeatureBranch)
 	if err != nil {
 		return err
-	} else if branchExist {
+	} else if featureBranchExist {
 		return domain.BranchExistError
 	}
 
@@ -190,7 +196,7 @@ func (r Runner) runSingleRepo(ctx context.Context, repo domain.Repository) error
 		Title:     r.PullRequestTitle,
 		Body:      r.PullRequestBody,
 		Head:      r.FeatureBranch,
-		Base:      repo.DefaultBranch(),
+		Base:      r.BaseBranch,
 		Reviewers: getReviewers(r.Reviewers, r.MaxReviewers),
 	})
 	if err != nil {


Test patch:
diff --git a/tests/repo_helper_test.go b/tests/repo_helper_test.go
index 88948dd..4e50f5f 100644
--- a/tests/repo_helper_test.go
+++ b/tests/repo_helper_test.go
@@ -15,6 +15,8 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
+const fileName = "test.txt"
+
 func createRepo(t *testing.T, name, dataInFile string) vcmock.Repository {
 	tmpDir, err := createDummyRepo(dataInFile)
 	require.NoError(t, err)
@@ -66,7 +68,7 @@ func createDummyRepo(dataInFile string) (string, error) {
 	return tmpDir, nil
 }
 
-func changeBranch(t *testing.T, path string, branchName string) {
+func changeBranch(t *testing.T, path string, branchName string, create bool) {
 	repo, err := git.PlainOpen(path)
 	assert.NoError(t, err)
 
@@ -75,6 +77,41 @@ func changeBranch(t *testing.T, path string, branchName string) {
 
 	err = wt.Checkout(&git.CheckoutOptions{
 		Branch: plumbing.NewBranchReferenceName(branchName),
+		Create: create,
 	})
 	assert.NoError(t, err)
 }
+
+func changeTestFile(t *testing.T, basePath string, content string, commitMessage string) {
+	repo, err := git.PlainOpen(basePath)
+	require.NoError(t, err)
+
+	testFilePath := path.Join(basePath, fileName)
+
+	err = ioutil.WriteFile(testFilePath, []byte(content), 0600)
+	require.NoError(t, err)
+
+	wt, err := repo.Worktree()
+	require.NoError(t, err)
+
+	_, err = wt.Add(".")
+	require.NoError(t, err)
+
+	_, err = wt.Commit(commitMessage, &git.CommitOptions{
+		Author: &object.Signature{
+			Name:  "test",
+			Email: "test@example.com",
+			When:  time.Now(),
+		},
+	})
+	require.NoError(t, err)
+}
+
+func readTestFile(t *testing.T, basePath string) string {
+	testFilePath := path.Join(basePath, fileName)
+
+	b, err := ioutil.ReadFile(testFilePath)
+	require.NoError(t, err)
+
+	return string(b)
+}
diff --git a/tests/story_test.go b/tests/story_test.go
index c0b2aec..cde2a51 100644
--- a/tests/story_test.go
+++ b/tests/story_test.go
@@ -15,8 +15,6 @@ import (
 	"github.com/stretchr/testify/require"
 )
 
-const fileName = "test.txt"
-
 // TestStory tests the common usecase: run, status, merge, status
 func TestStory(t *testing.T) {
 	vcMock := &vcmock.VersionController{}
@@ -56,7 +54,7 @@ func TestStory(t *testing.T) {
 	assert.Equal(t, []byte("i like apples"), data)
 
 	// Verify that the new branch is changed
-	changeBranch(t, changeRepo.Path, "custom-branch-name")
+	changeBranch(t, changeRepo.Path, "custom-branch-name", false)
 	data, err = ioutil.ReadFile(path.Join(changeRepo.Path, fileName))
 	assert.NoError(t, err)
 	assert.Equal(t, []byte("i like bananas"), data)
diff --git a/tests/table_test.go b/tests/table_test.go
index 5722eb9..02d084d 100644
--- a/tests/table_test.go
+++ b/tests/table_test.go
@@ -23,8 +23,10 @@ func TestTable(t *testing.T) {
 	assert.NoError(t, err)
 
 	tests := []struct {
-		name   string
-		vc     *vcmock.VersionController
+		name     string
+		vc       *vcmock.VersionController
+		vcCreate func(t *testing.T) *vcmock.VersionController // Can be used if advanced setup is needed for the vc
+
 		args   []string
 		verify func(t *testing.T, vcMock *vcmock.VersionController, outputs outputs)
 
@@ -59,6 +61,61 @@ func TestTable(t *testing.T) {
 `, outputs.out)
 			},
 		},
+
+		{
+			name: "failing base-branch",
+			vc: &vcmock.VersionController{
+				Repositories: []vcmock.Repository{
+					createRepo(t, "should-change", "i like apples"),
+				},
+			},
+			args: []string{
+				"run",
+				"--author-name", "Test Author",
+				"--author-email", "test@example.com",
+				"-B", "custom-branch-name",
+				"--base-branch", "custom-base-branch",
+				"-m", "custom message",
+				fmt.Sprintf(`go run %s`, path.Join(workingDir, "scripts/changer/main.go")),
+			},
+			verify: func(t *testing.T, vcMock *vcmock.VersionController, outputs outputs) {
+				require.Len(t, vcMock.PullRequests, 0)
+				assert.Contains(t, outputs.logOut, `msg="couldn't find remote ref \"refs/heads/custom-base-branch\""`)
+			},
+		},
+
+		{
+			name: "success base-branch",
+			vcCreate: func(t *testing.T) *vcmock.VersionController {
+				repo := createRepo(t, "should-change", "i like apples")
+				changeBranch(t, repo.Path, "custom-base-branch", true)
+				changeTestFile(t, repo.Path, "i like apple", "test change")
+				changeBranch(t, repo.Path, "master", false)
+				return &vcmock.VersionController{
+					Repositories: []vcmock.Repository{
+						repo,
+					},
+				}
+			},
+			args: []string{
+				"run",
+				"--author-name", "Test Author",
+				"--author-email", "test@example.com",
+				"-B", "custom-branch-name",
+				"--base-branch", "custom-base-branch",
+				"-m", "custom message",
+				fmt.Sprintf(`go run %s`, path.Join(workingDir, "scripts/changer/main.go")),
+			},
+			verify: func(t *testing.T, vcMock *vcmock.VersionController, outputs outputs) {
+				require.Len(t, vcMock.PullRequests, 1)
+				assert.Equal(t, "custom-base-branch", vcMock.PullRequests[0].Base)
+				assert.Equal(t, "custom-branch-name", vcMock.PullRequests[0].Head)
+				assert.Equal(t, "custom message", vcMock.PullRequests[0].Title)
+
+				changeBranch(t, vcMock.Repositories[0].Path, "custom-branch-name", false)
+				assert.Equal(t, "i like banana", readTestFile(t, vcMock.Repositories[0].Path))
+			},
+		},
 	}
 
 	for _, test := range tests {
@@ -71,7 +128,13 @@ func TestTable(t *testing.T) {
 			require.NoError(t, err)
 			defer os.Remove(outFile.Name())
 
-			cmd.OverrideVersionController = test.vc
+			var vc *vcmock.VersionController
+			if test.vcCreate != nil {
+				vc = test.vcCreate(t)
+			} else {
+				vc = test.vc
+			}
+			cmd.OverrideVersionController = vc
 
 			command := cmd.RootCmd()
 			command.SetArgs(append(
@@ -92,7 +155,7 @@ func TestTable(t *testing.T) {
 			outData, err := ioutil.ReadAll(outFile)
 			assert.NoError(t, err)
 
-			test.verify(t, test.vc, outputs{
+			test.verify(t, vc, outputs{
 				logOut: string(logData),
 				out:    string(outData),
 			})