Repository: lindell/multi-gitter
Pull request #26 (2020-11-17 20:17:50)

feat: added close command


Patch:
diff --git a/cmd/close.go b/cmd/close.go
new file mode 100644
index 0000000..9782a1e
--- /dev/null
+++ b/cmd/close.go
@@ -0,0 +1,50 @@
+package cmd
+
+import (
+	"context"
+
+	"github.com/lindell/multi-gitter/internal/multigitter"
+	"github.com/spf13/cobra"
+)
+
+// CloseCmd closes pull requests
+func CloseCmd() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:     "close",
+		Short:   "Close pull requests.",
+		Long:    "Close pull requests with a specified branch name in an organization and with specified conditions.",
+		Args:    cobra.NoArgs,
+		PreRunE: logFlagInit,
+		RunE:    close,
+	}
+
+	cmd.Flags().StringP("branch", "B", "multi-gitter-branch", "The name of the branch where changes are committed.")
+	cmd.Flags().AddFlagSet(platformFlags())
+	cmd.Flags().AddFlagSet(logFlags("-"))
+
+	return cmd
+}
+
+func close(cmd *cobra.Command, args []string) error {
+	flag := cmd.Flags()
+
+	branchName, _ := flag.GetString("branch")
+
+	vc, err := getVersionController(flag)
+	if err != nil {
+		return err
+	}
+
+	statuser := multigitter.Closer{
+		VersionController: vc,
+
+		FeatureBranch: branchName,
+	}
+
+	err = statuser.Close(context.Background())
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/cmd/root.go b/cmd/root.go
index 3be0ee1..2618c3b 100755
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -27,6 +27,7 @@ func RootCmd() *cobra.Command {
 	cmd.AddCommand(RunCmd())
 	cmd.AddCommand(StatusCmd())
 	cmd.AddCommand(MergeCmd())
+	cmd.AddCommand(CloseCmd())
 	cmd.AddCommand(PrintCmd())
 	cmd.AddCommand(VersionCmd())
 
diff --git a/docs/img/fa/times-hexagon.svg b/docs/img/fa/times-hexagon.svg
new file mode 100644
index 0000000..a0e0153
--- /dev/null
+++ b/docs/img/fa/times-hexagon.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 550">    <!-- Font Awesome Pro 5.15.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) -->
+    <defs>
+        <style>
+            .fa{
+                fill: #db4b4b;
+            }
+        </style>
+    </defs>
+    <path class="fa" d="M441.5 39.8C432.9 25.1 417.1 16 400 16H176c-17.1 0-32.9 9.1-41.5 23.8l-112 192c-8.7 14.9-8.7 33.4 0 48.4l112 192c8.6 14.7 24.4 23.8 41.5 23.8h224c17.1 0 32.9-9.1 41.5-23.8l112-192c8.7-14.9 8.7-33.4 0-48.4l-112-192zm84.3 224.3l-112 192c-2.9 4.9-8.2 7.9-13.8 7.9H176c-5.7 0-11-3-13.8-7.9l-112-192c-2.9-5-2.9-11.2 0-16.1l112-192c2.8-5 8.1-8 13.8-8h224c5.7 0 11 3 13.8 7.9l112 192c2.9 5 2.9 11.2 0 16.2zm-143 78.2l-8.5 8.5c-4.7 4.7-12.3 4.7-17 0L288 281.5l-69.3 69.3c-4.7 4.7-12.3 4.7-17 0l-8.5-8.5c-4.7-4.7-4.7-12.3 0-17l69.3-69.3-69.3-69.3c-4.7-4.7-4.7-12.3 0-17l8.5-8.5c4.7-4.7 12.3-4.7 17 0l69.3 69.3 69.3-69.3c4.7-4.7 12.3-4.7 17 0l8.5 8.5c4.7 4.7 4.7 12.3 0 17L313.5 256l69.3 69.3c4.6 4.7 4.6 12.3 0 17z"/>
+</svg>
diff --git a/internal/github/github.go b/internal/github/github.go
index 9cad2bd..1a7cd97 100755
--- a/internal/github/github.go
+++ b/internal/github/github.go
@@ -361,3 +361,18 @@ func (g Github) MergePullRequest(ctx context.Context, pullReq domain.PullRequest
 	_, err = g.ghClient.Git.DeleteRef(ctx, pr.ownerName, pr.repoName, fmt.Sprintf("heads/%s", pr.branchName))
 	return err
 }
+
+// ClosePullRequest closes a pull request
+func (g Github) ClosePullRequest(ctx context.Context, pullReq domain.PullRequest) error {
+	pr := pullReq.(pullRequest)
+
+	_, _, err := g.ghClient.PullRequests.Edit(ctx, pr.ownerName, pr.repoName, pr.number, &github.PullRequest{
+		State: &[]string{"closed"}[0],
+	})
+	if err != nil {
+		return err
+	}
+
+	_, err = g.ghClient.Git.DeleteRef(ctx, pr.ownerName, pr.repoName, fmt.Sprintf("heads/%s", pr.branchName))
+	return err
+}
diff --git a/internal/gitlab/gitlab.go b/internal/gitlab/gitlab.go
index d3b3cdb..a9db863 100644
--- a/internal/gitlab/gitlab.go
+++ b/internal/gitlab/gitlab.go
@@ -347,3 +347,20 @@ func (g *Gitlab) MergePullRequest(ctx context.Context, pullReq domain.PullReques
 
 	return nil
 }
+
+// ClosePullRequest closes a pull request
+func (g *Gitlab) ClosePullRequest(ctx context.Context, pullReq domain.PullRequest) error {
+	pr := pullReq.(pullRequest)
+
+	_, err := g.glClient.MergeRequests.DeleteMergeRequest(pr.pid, pr.iid, gitlab.WithContext(ctx))
+	if err != nil {
+		return err
+	}
+
+	_, err = g.glClient.Branches.DeleteBranch(pr.pid, pr.branchName, gitlab.WithContext(ctx))
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
diff --git a/internal/multigitter/close.go b/internal/multigitter/close.go
new file mode 100644
index 0000000..68c1986
--- /dev/null
+++ b/internal/multigitter/close.go
@@ -0,0 +1,41 @@
+package multigitter
+
+import (
+	"context"
+
+	"github.com/lindell/multi-gitter/internal/domain"
+	log "github.com/sirupsen/logrus"
+)
+
+// Closer closes pull requests
+type Closer struct {
+	VersionController VersionController
+
+	FeatureBranch string
+}
+
+func (s Closer) Close(ctx context.Context) error {
+	prs, err := s.VersionController.GetPullRequestStatuses(ctx, s.FeatureBranch)
+	if err != nil {
+		return err
+	}
+
+	openPRs := make([]domain.PullRequest, 0, len(prs))
+	for _, pr := range prs {
+		if pr.Status() != domain.PullRequestStatusClosed && pr.Status() != domain.PullRequestStatusMerged {
+			openPRs = append(openPRs, pr)
+		}
+	}
+
+	log.Infof("Closing %d pull requests", len(openPRs))
+
+	for _, pr := range openPRs {
+		log.WithField("repo", pr.String()).Infof("Closing")
+		err := s.VersionController.ClosePullRequest(ctx, pr)
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
diff --git a/internal/multigitter/run.go b/internal/multigitter/run.go
index 21b664a..c3b8db9 100755
--- a/internal/multigitter/run.go
+++ b/internal/multigitter/run.go
@@ -24,6 +24,7 @@ type VersionController interface {
 	CreatePullRequest(ctx context.Context, repo domain.Repository, newPR domain.NewPullRequest) error
 	GetPullRequestStatuses(ctx context.Context, branchName string) ([]domain.PullRequest, error)
 	MergePullRequest(ctx context.Context, pr domain.PullRequest) error
+	ClosePullRequest(ctx context.Context, pr domain.PullRequest) error
 }
 
 // Runner contains fields to be able to do the run
diff --git a/tools/readme-docs/main.go b/tools/readme-docs/main.go
index 7aa4b3e..a3b217a 100755
--- a/tools/readme-docs/main.go
+++ b/tools/readme-docs/main.go
@@ -68,6 +68,10 @@ func main() {
 			imgIcon: "docs/img/fa/tasks.svg",
 			cmd:     commandByName(subCommands, "status"),
 		},
+		{
+			imgIcon: "docs/img/fa/times-hexagon.svg",
+			cmd:     commandByName(subCommands, "close"),
+		},
 		{
 			imgIcon: "docs/img/fa/print.svg",
 			cmd:     commandByName(subCommands, "print"),


Test patch:
diff --git a/tests/story_test.go b/tests/story_test.go
index 177a0f0..8f2f390 100644
--- a/tests/story_test.go
+++ b/tests/story_test.go
@@ -127,4 +127,41 @@ Repositories with a successful run:
 	afterMergeStatusLogData, err := ioutil.ReadFile(afterMergeStatusLogFile)
 	require.NoError(t, err)
 	assert.Equal(t, "should-change/XX: Merged\nshould-change-2/XX: Pending\n", string(afterMergeStatusLogData))
+
+	//
+	// Close
+	//
+	closeLogFile := path.Join(tmpDir, "close-log.txt")
+
+	command = cmd.RootCmd()
+	command.SetArgs([]string{"close",
+		"--log-file", closeLogFile,
+		"-B", "custom-branch-name",
+	})
+	err = command.Execute()
+	assert.NoError(t, err)
+
+	// Verify that the output was correct
+	closeLogData, err := ioutil.ReadFile(closeLogFile)
+	require.NoError(t, err)
+	assert.Contains(t, string(closeLogData), "Closing 1 pull request")
+	assert.Contains(t, string(closeLogData), "Closing repo=should-change-2/XX")
+
+	//
+	// After Close Status
+	//
+	afterCloseStatusLogFile := path.Join(tmpDir, "after-close-status-log.txt")
+
+	command = cmd.RootCmd()
+	command.SetArgs([]string{"status",
+		"--log-file", afterCloseStatusLogFile,
+		"-B", "custom-branch-name",
+	})
+	err = command.Execute()
+	assert.NoError(t, err)
+
+	// Verify that the output was correct
+	afterCloseStatusLogData, err := ioutil.ReadFile(afterCloseStatusLogFile)
+	require.NoError(t, err)
+	assert.Equal(t, "should-change/XX: Merged\nshould-change-2/XX: Closed\n", string(afterCloseStatusLogData))
 }
diff --git a/tests/vcmock/vcmock.go b/tests/vcmock/vcmock.go
index 9073b88..e31ce5a 100644
--- a/tests/vcmock/vcmock.go
+++ b/tests/vcmock/vcmock.go
@@ -48,7 +48,7 @@ func (vc *VersionController) GetPullRequestStatuses(ctx context.Context, branchN
 	return ret, nil
 }
 
-// MergePullRequest sets the status of a mock pull requests to merged for
+// MergePullRequest sets the status of a mock pull requests to merged
 func (vc *VersionController) MergePullRequest(ctx context.Context, pr domain.PullRequest) error {
 	pullRequest := pr.(PullRequest)
 	for i := range vc.PullRequests {
@@ -60,6 +60,18 @@ func (vc *VersionController) MergePullRequest(ctx context.Context, pr domain.Pul
 	return errors.New("could not find pull request")
 }
 
+// ClosePullRequest sets the status of a mock pull requests to closed
+func (vc *VersionController) ClosePullRequest(ctx context.Context, pr domain.PullRequest) error {
+	pullRequest := pr.(PullRequest)
+	for i := range vc.PullRequests {
+		if vc.PullRequests[i].Repository.Name == pullRequest.Repository.Name {
+			vc.PullRequests[i].PRStatus = domain.PullRequestStatusClosed
+			return nil
+		}
+	}
+	return errors.New("could not find pull request")
+}
+
 // AddRepository adds a repository to the mock
 func (vc *VersionController) AddRepository(repo Repository) {
 	vc.Repositories = append(vc.Repositories, repo)