-
Notifications
You must be signed in to change notification settings - Fork 45
/
merge.go
123 lines (106 loc) · 3.65 KB
/
merge.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package merge
import (
"context"
"fmt"
"time"
"github.com/Clever/microplane/lib"
"github.com/google/go-github/v35/github"
)
// Input to Push()
type Input struct {
// Repo is the git Repo
Repo lib.Repo
// PRNumber of Github, e.g. for https://github.com/Clever/microplane/pull/123, the PRNumber is 123
PRNumber int
// CommitSHA for the commit which opened the above PR. Used to look up Commit status.
CommitSHA string
// RequireReviewApproval specifies if the PR must be approved before merging
// - must have at least 1 reviewer
// - all reviewers must have explicitly approved
RequireReviewApproval bool
// RequireBuildSuccess specifies if the PR must have a successful build before merging
RequireBuildSuccess bool
// Merge method to use. Possible values include: "merge", "squash", and "rebase"
MergeMethod string
}
// Output from Push()
type Output struct {
Success bool
MergeCommitSHA string
}
// Error and details from Push()
type Error struct {
error
Details string
}
// Merge an open PR in Github
// - repoLimiter rate limits the # of calls to Github
// - mergeLimiter rate limits # of merges, to prevent load when submitting builds to CI system
func GitHubMerge(ctx context.Context, input Input, repoLimiter *time.Ticker, mergeLimiter *time.Ticker) (Output, error) {
// Create Github Client
p := lib.NewProviderFromConfig(input.Repo.ProviderConfig)
client, err := p.GithubClient(ctx)
if err != nil {
return Output{}, err
}
// OK to merge?
// (1) Check if the PR is mergeable
<-repoLimiter.C
pr, _, err := client.PullRequests.Get(ctx, input.Repo.Owner, input.Repo.Name, input.PRNumber)
if err != nil {
return Output{Success: false}, err
}
if pr.GetMerged() {
// Success! already merged
return Output{Success: true, MergeCommitSHA: pr.GetMergeCommitSHA()}, nil
}
if !pr.GetMergeable() {
return Output{Success: false}, fmt.Errorf("PR is not mergeable")
}
// (2) Check commit status
<-repoLimiter.C
status, _, err := client.Repositories.GetCombinedStatus(ctx, input.Repo.Owner, input.Repo.Name, input.CommitSHA, &github.ListOptions{})
if err != nil {
return Output{Success: false}, err
}
if input.RequireBuildSuccess {
state := status.GetState()
if state != "success" {
return Output{Success: false}, fmt.Errorf("Build status was not 'success', instead was '%s'. Use --ignore-build-status to override this check.", state)
}
}
// (3) check if PR has been approved by a reviewer
<-repoLimiter.C
reviews, _, err := client.PullRequests.ListReviews(ctx, input.Repo.Owner, input.Repo.Name, input.PRNumber, &github.ListOptions{})
if input.RequireReviewApproval {
if len(reviews) == 0 {
return Output{Success: false}, fmt.Errorf("PR awaiting review. Use --ignore-review-approval to override this check.")
}
for _, r := range reviews {
if r.GetState() != "APPROVED" {
return Output{Success: false}, fmt.Errorf("PR is not approved. Review state is %s. Use --ignore-review-approval to override this check.", r.GetState())
}
}
}
// Merge the PR
options := &github.PullRequestOptions{
MergeMethod: input.MergeMethod,
}
commitMsg := ""
<-mergeLimiter.C
<-repoLimiter.C
result, _, err := client.PullRequests.Merge(ctx, input.Repo.Owner, input.Repo.Name, input.PRNumber, commitMsg, options)
if err != nil {
return Output{Success: false}, err
}
if !result.GetMerged() {
return Output{Success: false}, fmt.Errorf("failed to merge: %s", result.GetMessage())
}
// Delete the branch
<-repoLimiter.C
_, err = client.Git.DeleteRef(ctx, input.Repo.Owner, input.Repo.Name, "heads/"+*pr.Head.Ref)
if err != nil {
return Output{Success: false}, err
}
return Output{Success: true, MergeCommitSHA: result.GetSHA()}, nil
}