-
-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
307 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io/ioutil" | ||
"log" | ||
"os" | ||
"strings" | ||
|
||
"github.com/google/go-github/v24/github" | ||
"golang.org/x/oauth2" | ||
) | ||
|
||
const ( | ||
// https://developer.github.com/v3/activity/events/types/#pullrequestevent | ||
pullRequestEventName = "pull_request" | ||
) | ||
|
||
func action(dryRun bool) error { | ||
gitHubToken := os.Getenv("GITHUB_TOKEN") | ||
eventName := os.Getenv("GITHUB_EVENT_NAME") | ||
eventPath := os.Getenv("GITHUB_EVENT_PATH") | ||
|
||
if len(gitHubToken) == 0 { | ||
return errors.New("GITHUB_TOKEN is required") | ||
} | ||
|
||
if eventName != pullRequestEventName { | ||
return fmt.Errorf("invalid event type: %q", eventName) | ||
} | ||
|
||
event := &github.PullRequestEvent{} | ||
err := readEvent(eventPath, event) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
action := event.GetAction() | ||
|
||
if action != "closed" || !event.PullRequest.GetMerged() { | ||
log.Printf("skip: %q merge %v", action, event.PullRequest.GetMerged()) | ||
return nil | ||
} | ||
|
||
ctx := context.Background() | ||
client := newGitHubClient(ctx, gitHubToken) | ||
|
||
owner, repoName := getRepoInfo() | ||
|
||
return closeRelatedIssues(ctx, client, owner, repoName, event.PullRequest, dryRun) | ||
} | ||
|
||
func newGitHubClient(ctx context.Context, token string) *github.Client { | ||
var client *github.Client | ||
if len(token) == 0 { | ||
client = github.NewClient(nil) | ||
} else { | ||
ts := oauth2.StaticTokenSource( | ||
&oauth2.Token{AccessToken: token}, | ||
) | ||
tc := oauth2.NewClient(ctx, ts) | ||
client = github.NewClient(tc) | ||
} | ||
return client | ||
} | ||
|
||
func readEvent(eventPath string, event interface{}) error { | ||
content, err := ioutil.ReadFile(eventPath) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return json.Unmarshal(content, event) | ||
} | ||
|
||
func getRepoInfo() (string, string) { | ||
githubRepository := os.Getenv("GITHUB_REPOSITORY") | ||
|
||
parts := strings.SplitN(githubRepository, "/", 2) | ||
|
||
return parts[0], parts[1] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module github.com/ldez/gha-mjolnir | ||
|
||
require ( | ||
github.com/google/go-github v17.0.0+incompatible | ||
github.com/google/go-github/v24 v24.0.0 | ||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= | ||
github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= | ||
github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= | ||
github.com/google/go-github/v24 v24.0.0 h1:11gKs9XbwcUO5yEgg+GAbtg6z6CH7tK77wDM3mnAnXc= | ||
github.com/google/go-github/v24 v24.0.0/go.mod h1:CRqaW1Uns1TCkP0wqTpxYyRxRjxwvKU/XSS44u6X74M= | ||
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= | ||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= | ||
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I= | ||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= | ||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= | ||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||
golang.org/x/sys v0.0.0-20180824143301-4910a1d54f87/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package main | ||
|
||
import "log" | ||
|
||
func main() { | ||
displayVersion() | ||
|
||
err := action(true) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
package main | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"log" | ||
"regexp" | ||
"strconv" | ||
"strings" | ||
|
||
"github.com/google/go-github/v24/github" | ||
) | ||
|
||
var ( | ||
globalFixesIssueRE = regexp.MustCompile(`(?i)(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved)((?:[\s]+#[\d]+)(?:[\s,]+#[\d]+)*(?:[\n\r\s,]|$))`) | ||
fixesIssueRE = regexp.MustCompile(`[\s,]+#`) | ||
cleanNumberRE = regexp.MustCompile(`[\n\r\s,]`) | ||
) | ||
|
||
// closeRelatedIssues Closes issues listed in the PR description. | ||
func closeRelatedIssues(ctx context.Context, client *github.Client, owner string, repositoryName string, pr *github.PullRequest, dryRun bool) error { | ||
issueNumbers := parseIssueFixes(pr.GetBody()) | ||
|
||
for _, issueNumber := range issueNumbers { | ||
log.Printf("PR #%d: closes issue #%d, add milestones %s", pr.GetNumber(), issueNumber, pr.Milestone.GetTitle()) | ||
if !dryRun { | ||
err := closeIssue(ctx, client, owner, repositoryName, pr, issueNumber) | ||
if err != nil { | ||
return fmt.Errorf("unable to close issue #%d: %v", issueNumber, err) | ||
} | ||
} | ||
|
||
// Add comment if needed | ||
if pr.Base.GetRef() != "master" { | ||
message := fmt.Sprintf("Closed by #%d.", pr.GetNumber()) | ||
|
||
log.Printf("PR #%d: issue #%d, add comment: %s", pr.GetNumber(), issueNumber, message) | ||
|
||
if !dryRun { | ||
err := addComment(ctx, client, owner, repositoryName, issueNumber, message) | ||
if err != nil { | ||
return fmt.Errorf("unable to add comment on issue #%d: %v", issueNumber, err) | ||
} | ||
} | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func closeIssue(ctx context.Context, client *github.Client, owner string, repositoryName string, pr *github.PullRequest, issueNumber int) error { | ||
|
||
var milestone *int | ||
if pr.Milestone != nil { | ||
milestone = pr.Milestone.Number | ||
} | ||
|
||
issueRequest := &github.IssueRequest{ | ||
Milestone: milestone, | ||
State: github.String("closed"), | ||
} | ||
|
||
_, _, err := client.Issues.Edit(ctx, owner, repositoryName, issueNumber, issueRequest) | ||
return err | ||
} | ||
|
||
func addComment(ctx context.Context, client *github.Client, owner string, repositoryName string, issueNumber int, message string) error { | ||
issueComment := &github.IssueComment{ | ||
Body: github.String(message), | ||
} | ||
_, _, err := client.Issues.CreateComment(ctx, owner, repositoryName, issueNumber, issueComment) | ||
return err | ||
} | ||
|
||
func parseIssueFixes(text string) []int { | ||
var issueNumbers []int | ||
|
||
submatch := globalFixesIssueRE.FindStringSubmatch(strings.Replace(text, ":", "", -1)) | ||
|
||
if len(submatch) != 0 { | ||
issuesRaw := fixesIssueRE.Split(submatch[1], -1) | ||
|
||
for _, issueRaw := range issuesRaw { | ||
cleanIssueRaw := cleanNumberRE.ReplaceAllString(issueRaw, "") | ||
if len(cleanIssueRaw) != 0 { | ||
numb, err := strconv.ParseInt(cleanIssueRaw, 10, 16) | ||
if err != nil { | ||
log.Println(err) | ||
} | ||
issueNumbers = append(issueNumbers, int(numb)) | ||
} | ||
} | ||
} | ||
return issueNumbers | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
package main | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func Test_parseIssueFixes(t *testing.T) { | ||
testCases := []struct { | ||
name string | ||
text string | ||
expectedNumbers []int | ||
}{ | ||
{ | ||
name: "only letters", | ||
text: ` | ||
Fixes dlsqj | ||
`, | ||
expectedNumbers: []int{}, | ||
}, | ||
{ | ||
name: "valid issue numbers coma", | ||
text: ` | ||
Fixes #13 #14, #15,#16, | ||
`, | ||
expectedNumbers: []int{13, 14, 15, 16}, | ||
}, | ||
{ | ||
name: "valid issue numbers space", | ||
text: ` | ||
Fixes #13 #14 #15 #16 | ||
`, | ||
expectedNumbers: []int{13, 14, 15, 16}, | ||
}, | ||
{ | ||
name: "invalid pattern", | ||
text: ` | ||
Fixes #13#14,#15,#16, | ||
`, | ||
expectedNumbers: []int{}, | ||
}, | ||
{ | ||
name: "french style", | ||
text: ` | ||
Fixes : #13,#14,#15,#16, | ||
`, | ||
expectedNumbers: []int{13, 14, 15, 16}, | ||
}, | ||
{ | ||
name: "valid issue numbers coma and :", | ||
text: ` | ||
Fixes: #13,#14,#15,#16, | ||
`, | ||
expectedNumbers: []int{13, 14, 15, 16}, | ||
}, | ||
} | ||
|
||
for _, test := range testCases { | ||
test := test | ||
t.Run(test.name, func(t *testing.T) { | ||
t.Parallel() | ||
|
||
issueNumbers := parseIssueFixes(test.text) | ||
|
||
if (len(issueNumbers) != 0 || len(test.expectedNumbers) != 0) && !reflect.DeepEqual(issueNumbers, test.expectedNumbers) { | ||
t.Errorf("Got %v, expected %v", issueNumbers, test.expectedNumbers) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"runtime" | ||
) | ||
|
||
var ( | ||
version = "dev" | ||
commit = "I don't remember exactly" | ||
date = "I don't remember exactly" | ||
) | ||
|
||
// displayVersion DisplayVersion version. | ||
func displayVersion() { | ||
fmt.Printf(`mjolnir: | ||
version : %s | ||
commit : %s | ||
build date : %s | ||
go version : %s | ||
go compiler : %s | ||
platform : %s/%s | ||
`, version, commit, date, runtime.Version(), runtime.Compiler, runtime.GOOS, runtime.GOARCH) | ||
} |