-
-
Notifications
You must be signed in to change notification settings - Fork 69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Merge/release] Add commit message linting feature #43
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,7 @@ import ( | |
"github.com/google/go-github/github" | ||
) | ||
|
||
func handlePullRequest(req types.PullRequestOuter) { | ||
func handlePullRequest(req types.PullRequestOuter, prFeatures types.PullRequestFeatures) { | ||
ctx := context.Background() | ||
|
||
token := os.Getenv("access_token") | ||
|
@@ -32,88 +32,98 @@ func handlePullRequest(req types.PullRequestOuter) { | |
|
||
client := auth.MakeClient(ctx, token) | ||
|
||
hasUnsignedCommits, err := hasUnsigned(req, client) | ||
commits, commitFetchErr := getCommits(req, client) | ||
|
||
if err != nil { | ||
log.Fatal(err) | ||
} else if hasUnsignedCommits { | ||
fmt.Println("May need to apply labels on item.") | ||
if commitFetchErr != nil { | ||
log.Fatal(commitFetchErr) | ||
} | ||
|
||
issue, _, labelErr := client.Issues.Get(ctx, req.Repository.Owner.Login, req.Repository.Name, req.PullRequest.Number) | ||
issue, _, labelErr := client.Issues.Get(ctx, req.Repository.Owner.Login, req.Repository.Name, req.PullRequest.Number) | ||
if labelErr != nil { | ||
log.Fatalln("Unable to fetch labels for PR %s/%s/#%d", req.Repository.Owner, req.Repository.Name, req.PullRequest.Number) | ||
} | ||
|
||
if labelErr != nil { | ||
log.Fatalln(labelErr) | ||
if prFeatures.CommitLintingFeature { | ||
lintResult := lintCommits(commits) | ||
applyErr := applyLintingLabel(req, client, issue, lintResult) | ||
if applyErr != nil { | ||
log.Printf("Error applying linting rule: %s", applyErr) | ||
} | ||
fmt.Println("Current labels ", issue.Labels) | ||
} | ||
|
||
if hasNoDcoLabel(issue) == false { | ||
fmt.Println("Applying label") | ||
_, res, assignLabelErr := client.Issues.AddLabelsToIssue(ctx, req.Repository.Owner.Login, req.Repository.Name, req.PullRequest.Number, []string{"no-dco"}) | ||
if assignLabelErr != nil { | ||
log.Fatalf("%s limit: %d, remaining: %d", assignLabelErr, res.Limit, res.Remaining) | ||
} | ||
if prFeatures.DCOCheckFeature { | ||
if hasUnsigned(commits) == true { | ||
fmt.Println("May need to apply labels on item.") | ||
|
||
link := fmt.Sprintf("https://github.com/%s/%s/blob/master/CONTRIBUTING.md", req.Repository.Owner.Login, req.Repository.Name) | ||
body := `Thank you for your contribution. I've just checked and your commit doesn't appear to be signed-off. | ||
That's something we need before your Pull Request can be merged. Please see our [contributing guide](` + link + `).` | ||
fmt.Println("Current labels ", issue.Labels) | ||
|
||
comment := &github.IssueComment{ | ||
Body: &body, | ||
} | ||
if hasLabelAssigned("no-dco", issue) == false { | ||
fmt.Println("Applying label") | ||
_, res, assignLabelErr := client.Issues.AddLabelsToIssue(ctx, req.Repository.Owner.Login, req.Repository.Name, req.PullRequest.Number, []string{"no-dco"}) | ||
if assignLabelErr != nil { | ||
log.Fatalf("%s limit: %d, remaining: %d", assignLabelErr, res.Limit, res.Remaining) | ||
} | ||
|
||
comment, resp, err := client.Issues.CreateComment(ctx, req.Repository.Owner.Login, req.Repository.Name, req.PullRequest.Number, comment) | ||
if err != nil { | ||
log.Fatalf("%s limit: %d, remaining: %d", assignLabelErr, resp.Limit, resp.Remaining) | ||
log.Fatal(err) | ||
} | ||
fmt.Println(comment, resp.Rate) | ||
} | ||
} else { | ||
fmt.Println("Things look OK right now.") | ||
issue, res, labelErr := client.Issues.Get(ctx, req.Repository.Owner.Login, req.Repository.Name, req.PullRequest.Number) | ||
link := fmt.Sprintf("https://github.com/%s/%s/blob/master/CONTRIBUTING.md", req.Repository.Owner.Login, req.Repository.Name) | ||
body := `Thank you for your contribution. I've just checked and your commit doesn't appear to be signed-off. | ||
That's something we need before your Pull Request can be merged. Please see our [contributing guide](` + link + `).` | ||
|
||
if labelErr != nil { | ||
log.Fatalf("%s limit: %d, remaining: %d", labelErr, res.Limit, res.Remaining) | ||
log.Fatalln() | ||
} | ||
comment := &github.IssueComment{ | ||
Body: &body, | ||
} | ||
|
||
if hasNoDcoLabel(issue) { | ||
fmt.Println("Removing label") | ||
_, removeLabelErr := client.Issues.RemoveLabelForIssue(ctx, req.Repository.Owner.Login, req.Repository.Name, req.PullRequest.Number, "no-dco") | ||
if removeLabelErr != nil { | ||
log.Fatal(removeLabelErr) | ||
comment, resp, err := client.Issues.CreateComment(ctx, req.Repository.Owner.Login, req.Repository.Name, req.PullRequest.Number, comment) | ||
if err != nil { | ||
log.Fatalf("%s limit: %d, remaining: %d", assignLabelErr, resp.Limit, resp.Remaining) | ||
log.Fatal(err) | ||
} | ||
fmt.Println(comment, resp.Rate) | ||
} | ||
} else { | ||
fmt.Println("Things look OK right now.") | ||
|
||
if hasLabelAssigned("no-dco", issue) { | ||
fmt.Println("Removing label") | ||
_, removeLabelErr := client.Issues.RemoveLabelForIssue(ctx, req.Repository.Owner.Login, req.Repository.Name, req.PullRequest.Number, "no-dco") | ||
if removeLabelErr != nil { | ||
log.Fatal(removeLabelErr) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
func hasNoDcoLabel(issue *github.Issue) bool { | ||
func hasLabelAssigned(labelName string, issue *github.Issue) bool { | ||
if issue != nil { | ||
for _, label := range issue.Labels { | ||
if label.GetName() == "no-dco" { | ||
if label.GetName() == labelName { | ||
return true | ||
} | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
func hasUnsigned(req types.PullRequestOuter, client *github.Client) (bool, error) { | ||
hasUnsigned := false | ||
func getCommits(req types.PullRequestOuter, client *github.Client) ([]*github.RepositoryCommit, error) { | ||
ctx := context.Background() | ||
|
||
var err error | ||
var responseErr error | ||
listOpts := &github.ListOptions{ | ||
Page: 0, | ||
} | ||
|
||
commits, resp, err := client.PullRequests.ListCommits(ctx, req.Repository.Owner.Login, req.Repository.Name, req.PullRequest.Number, listOpts) | ||
if err != nil { | ||
log.Fatalf("Error getting PR %d\n%s", req.PullRequest.Number, err.Error()) | ||
return hasUnsigned, err | ||
responseErr = fmt.Errorf("unable to fetch commits for PR: %d, Error: %s", req.PullRequest.Number, err.Error()) | ||
} | ||
|
||
fmt.Println("Rate limiting", resp.Rate) | ||
log.Printf("Rate limiting remaining: %d", resp.Rate.Remaining) | ||
return commits, responseErr | ||
} | ||
|
||
func hasUnsigned(commits []*github.RepositoryCommit) bool { | ||
hasUnsigned := false | ||
|
||
for _, commit := range commits { | ||
if commit.Commit != nil && commit.Commit.Message != nil { | ||
|
@@ -123,9 +133,97 @@ func hasUnsigned(req types.PullRequestOuter, client *github.Client) (bool, error | |
} | ||
} | ||
|
||
return hasUnsigned, err | ||
return hasUnsigned | ||
} | ||
|
||
func isSigned(msg string) bool { | ||
return strings.Contains(msg, "Signed-off-by:") | ||
} | ||
|
||
func lintCommits(commits []*github.RepositoryCommit) bool { | ||
for _, commit := range commits { | ||
if commit.Commit != nil && commit.Commit.Message != nil { | ||
if lintCommit(commit.Commit.Message) == false { | ||
return false | ||
} | ||
} | ||
} | ||
return true | ||
} | ||
|
||
func lintCommit(message *string) bool { | ||
var valid bool | ||
|
||
if message == nil { | ||
return false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As this is tested in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure.. I might have to double-check |
||
} | ||
|
||
parts := strings.Split(*message, "\n") | ||
|
||
if len(parts) > 0 { | ||
lengthValid := len(parts[0]) <= 50 | ||
var startsWithUpper bool | ||
|
||
firstCharacter := getFirstCharacter(parts[0]) | ||
if firstCharacter != nil { | ||
startsWithUpper = len(*firstCharacter) > 0 && strings.ToUpper(*firstCharacter) == *firstCharacter | ||
} | ||
|
||
valid = lengthValid && startsWithUpper | ||
} | ||
|
||
return valid | ||
} | ||
|
||
func getFirstCharacter(msg string) *string { | ||
var ret *string | ||
|
||
for _, runeVal := range msg { | ||
asStr := string(runeVal) | ||
ret = &asStr | ||
break | ||
} | ||
|
||
return ret | ||
} | ||
|
||
func applyLintingLabel(req types.PullRequestOuter, client *github.Client, issue *github.Issue, lintResult bool) error { | ||
labelCaption := "review/commit-message" | ||
var actionErr error | ||
hasLabel := hasLabelAssigned(labelCaption, issue) | ||
|
||
ctx := context.Background() | ||
|
||
if hasLabel { | ||
if lintResult == true { | ||
res, assignLabelErr := client.Issues.RemoveLabelForIssue(ctx, req.Repository.Owner.Login, req.Repository.Name, req.PullRequest.Number, labelCaption) | ||
if assignLabelErr != nil { | ||
actionErr = fmt.Errorf("removeLabel: %s limit: %d, remaining: %d", assignLabelErr, res.Limit, res.Remaining) | ||
} | ||
} | ||
} else { | ||
if lintResult == false { | ||
_, res, assignLabelErr := client.Issues.AddLabelsToIssue(ctx, req.Repository.Owner.Login, req.Repository.Name, req.PullRequest.Number, []string{labelCaption}) | ||
if assignLabelErr != nil { | ||
actionErr = fmt.Errorf("addLabel: %s limit: %d, remaining: %d", assignLabelErr, res.Limit, res.Remaining) | ||
} | ||
link := fmt.Sprintf("https://github.com/%s/%s/blob/master/CONTRIBUTING.md", req.Repository.Owner.Login, req.Repository.Name) | ||
|
||
body := `Please check that your commit messages fit within [these guidelines](` + link + `): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think there needs to be a quick link here to make it as easy as possible for everyone to edit their commit history. A direct link to this page (as in the contributing guide) would be perfect. Prior Slack discussion here. Thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes a link could be good for re-writing history. |
||
- Commit subject should not exceed 50 characters | ||
- Commit subject should start with an uppercase letter | ||
` | ||
|
||
comment := &github.IssueComment{ | ||
Body: &body, | ||
} | ||
|
||
_, _, err := client.Issues.CreateComment(ctx, req.Repository.Owner.Login, req.Repository.Name, req.PullRequest.Number, comment) | ||
if err != nil { | ||
actionErr = fmt.Errorf("unable to create comment due to linting check: %s", err) | ||
} | ||
} | ||
} | ||
|
||
return actionErr | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Less than or equal to 50 characters
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GitHub will allow up to 72 characters before it wraps. I'm wondering whether we should expand the limit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In all areas that the subject is displayed? Is the client similar? The referenced article is 4 years old so gh may have changed in that time.
I liked the idea of that being configurable, even if it was through env-vars.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I could move the hard-limit to 72 characters as per GitHub UI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
subject lines contain no more than 72 characters