1
1
package io .quarkus .bot ;
2
2
3
3
import java .io .IOException ;
4
- import java .util .ArrayList ;
5
- import java .util .Arrays ;
6
- import java .util .Collections ;
7
4
import java .util .List ;
8
- import java .util .Locale ;
9
- import java .util .regex . Pattern ;
5
+ import java .util .Optional ;
6
+ import java .util .StringJoiner ;
10
7
11
8
import jakarta .inject .Inject ;
12
9
13
- import org .jboss . logging . Logger ;
10
+ import org .apache . commons . lang3 . StringUtils ;
14
11
import org .kohsuke .github .GHEventPayload ;
12
+ import org .kohsuke .github .GHIssueComment ;
15
13
import org .kohsuke .github .GHPullRequest ;
16
14
17
15
import io .quarkiverse .githubapp .ConfigFile ;
18
16
import io .quarkiverse .githubapp .event .PullRequest ;
19
17
import io .quarkus .bot .config .Feature ;
20
- import io .quarkus .bot .config .QuarkusGitHubBotConfig ;
21
18
import io .quarkus .bot .config .QuarkusGitHubBotConfigFile ;
22
19
import io .quarkus .bot .util .GHPullRequests ;
23
- import io .quarkus .bot .util . PullRequestFilesMatcher ;
24
- import io .quarkus .bot .util . Strings ;
20
+ import io .quarkus .bot .violation . EditorialViolation ;
21
+ import io .quarkus .bot .violation . ViolationDetectorManager ;
25
22
26
23
class CheckPullRequestEditorialRules {
27
24
28
- private static final Logger LOG = Logger .getLogger (CheckPullRequestEditorialRules .class );
25
+ public static final String HEADER = """
26
+ Thanks for your pull request!
29
27
30
- private static final Pattern SPACE_PATTERN = Pattern .compile ("\\ s+" );
31
- private static final Pattern ISSUE_PATTERN = Pattern .compile ("#[0-9]+" );
32
- private static final Pattern FIX_FEAT_CHORE = Pattern .compile ("^(fix|chore|feat|docs|refactor)[(:].*" );
28
+ Your pull request does not follow our editorial rules. Could you have a look?
33
29
34
- private static final List <String > UPPER_CASE_EXCEPTIONS = Arrays .asList ("gRPC" );
35
- private static final List <String > BOMS = List .of ("bom/application/pom.xml" );
36
- private static final List <String > DOC_CHANGES = List .of ("docs/src/main/asciidoc/" , "README.md" , "LICENSE" ,
37
- "CONTRIBUTING.md" );
30
+ """ ;
38
31
39
32
@ Inject
40
- QuarkusGitHubBotConfig quarkusBotConfig ;
33
+ GitHubBotActions gitHubBotActions ;
34
+
35
+ @ Inject
36
+ ViolationDetectorManager violationManager ;
41
37
42
38
void checkPullRequestEditorialRules (@ PullRequest .Opened GHEventPayload .PullRequest pullRequestPayload ,
43
39
@ ConfigFile ("quarkus-github-bot.yml" ) QuarkusGitHubBotConfigFile quarkusBotConfigFile ) throws IOException {
@@ -48,122 +44,65 @@ void checkPullRequestEditorialRules(@PullRequest.Opened GHEventPayload.PullReque
48
44
String baseBranch = pullRequestPayload .getPullRequest ().getBase ().getRef ();
49
45
50
46
GHPullRequest pullRequest = pullRequestPayload .getPullRequest ();
51
- String body = pullRequest .getBody ();
52
47
String originalTitle = pullRequest .getTitle ();
53
48
String normalizedTitle = GHPullRequests .normalizeTitle (originalTitle , baseBranch );
54
49
55
50
if (!originalTitle .equals (normalizedTitle )) {
56
- pullRequest . setTitle ( normalizedTitle );
51
+ gitHubBotActions . setPullRequestTitle ( pullRequest , normalizedTitle );
57
52
}
58
53
59
- // we remove the potential version prefix before checking the editorial rules
60
- String title = GHPullRequests .dropVersionSuffix (normalizedTitle , baseBranch );
61
-
62
- List <String > titleErrorMessages = getTitleErrorMessages (title );
63
- List <String > bodyErrorMessages = getBodyErrorMessages (body , pullRequest );
54
+ List <EditorialViolation > violations = violationManager .detectViolations (pullRequest );
55
+ List <String > titleErrorMessages = violationManager .getTitleViolationMessages (violations );
56
+ List <String > bodyErrorMessages = violationManager .getBodyViolationMessages (violations );
64
57
65
58
if (titleErrorMessages .isEmpty () && bodyErrorMessages .isEmpty ()) {
66
59
return ;
67
60
}
68
-
69
- StringBuilder comment = new StringBuilder ("""
70
- Thanks for your pull request!
71
-
72
- Your pull request does not follow our editorial rules. Could you have a look?
73
-
74
- """ );
61
+ StringJoiner comment = new StringJoiner ("\n " , HEADER , StringUtils .EMPTY );
75
62
for (String errorMessage : titleErrorMessages ) {
76
- comment .append ("- " ). append ( errorMessage ). append ( " \n " );
63
+ comment .add ("- " + errorMessage );
77
64
}
78
65
for (String errorMessage : bodyErrorMessages ) {
79
- comment .append ("- " ). append ( errorMessage ). append ( " \n " );
66
+ comment .add ("- " + errorMessage );
80
67
}
81
68
82
- if (!quarkusBotConfig .isDryRun ()) {
83
- pullRequest .comment (Strings .commentByBot (comment .toString ()));
84
- } else {
85
- LOG .info ("Pull request #" + pullRequest .getNumber () + " - Add comment " + comment .toString ());
86
- }
69
+ gitHubBotActions .addComment (pullRequest , comment .toString (), true );
87
70
}
88
71
89
- private static List <String > getTitleErrorMessages (String title ) {
90
- List <String > errorMessages = new ArrayList <>();
91
-
92
- if (title == null || title .isEmpty ()) {
93
- return Collections .singletonList ("title should not be empty" );
94
- }
95
72
96
- if (title .endsWith ("." )) {
97
- errorMessages .add ("title should not end up with dot" );
98
- }
99
- if (title .endsWith ("…" )) {
100
- errorMessages .add ("title should not end up with ellipsis (make sure the title is complete)" );
101
- }
102
- if (SPACE_PATTERN .split (title .trim ()).length < 2 ) {
103
- errorMessages .add ("title should count at least 2 words to describe the change properly" );
104
- }
105
- if (!Character .isDigit (title .codePointAt (0 )) && !Character .isUpperCase (title .codePointAt (0 ))
106
- && !isUpperCaseException (title )) {
107
- errorMessages .add ("title should preferably start with an uppercase character (if it makes sense!)" );
108
- }
109
- if (ISSUE_PATTERN .matcher (title ).find ()) {
110
- errorMessages .add ("title should not contain an issue number (use `Fix #1234` in the description instead)" );
111
- }
112
- if (FIX_FEAT_CHORE .matcher (title ).matches ()) {
113
- errorMessages .add ("title should not start with chore/docs/feat/fix/refactor but be a proper sentence" );
73
+ void checkPullRequestEditorialRulesOnEdit (@ PullRequest .Edited GHEventPayload .PullRequest pullRequestPayload ,
74
+ @ ConfigFile ("quarkus-github-bot.yml" ) QuarkusGitHubBotConfigFile quarkusBotConfigFile ) throws IOException {
75
+ if (!Feature .CHECK_EDITORIAL_RULES .isEnabled (quarkusBotConfigFile )) {
76
+ return ;
114
77
}
78
+ var pullRequest = pullRequestPayload .getPullRequest ();
115
79
116
- return errorMessages ;
117
- }
118
-
119
- private static List <String > getBodyErrorMessages (String body , GHPullRequest pullRequest ) throws IOException {
120
- List <String > errorMessages = new ArrayList <>();
80
+ // Detect violations using the violation manager
81
+ List <EditorialViolation > violations = violationManager .detectViolations (pullRequest );
82
+ var titleErrorMessages = violationManager .getTitleViolationMessages (violations );
83
+ var bodyErrorMessages = violationManager .getBodyViolationMessages (violations );
121
84
122
- if ((body == null || body .isBlank ()) && isMeaningfulPullRequest (pullRequest )) {
123
- return List .of (
124
- "description should not be empty, describe your intent or provide links to the issues this PR is fixing (using `Fixes #NNNNN`) or changelogs" );
125
- }
85
+ if (titleErrorMessages .isEmpty () && bodyErrorMessages .isEmpty ()) {
86
+ Optional <GHIssueComment > commentToDeleteOpt = gitHubBotActions .findBotComment (pullRequest );
87
+ commentToDeleteOpt
88
+ .ifPresent (ghIssueComment -> gitHubBotActions .deleteComment (ghIssueComment , pullRequest .getNumber ()));
89
+ } else {
90
+ Optional <GHIssueComment > existingCommentOpt = gitHubBotActions .findBotComment (pullRequest );
126
91
127
- return errorMessages ;
128
- }
92
+ StringJoiner comment = new StringJoiner ("\n " , HEADER , "" );
93
+ for (String errorMessage : titleErrorMessages ) {
94
+ comment .add ("- " + errorMessage );
95
+ }
96
+ for (String errorMessage : bodyErrorMessages ) {
97
+ comment .add ("- " + errorMessage );
98
+ }
129
99
130
- private static boolean isUpperCaseException ( String title ) {
131
- for ( String exception : UPPER_CASE_EXCEPTIONS ) {
132
- if ( title . toLowerCase ( Locale . ROOT ). startsWith ( exception . toLowerCase ( Locale . ROOT ))) {
133
- return true ;
100
+ if ( existingCommentOpt . isPresent () ) {
101
+ gitHubBotActions . updateComment ( existingCommentOpt . get (), comment . toString (), pullRequest . getNumber (), true );
102
+ } else {
103
+ gitHubBotActions . addComment ( pullRequest , comment . toString (), true ) ;
134
104
}
135
105
}
136
-
137
- return false ;
138
106
}
139
107
140
- private static boolean isMeaningfulPullRequest (GHPullRequest pullRequest ) throws IOException {
141
- // Note: these rules will have to be adjusted depending on how it goes
142
- // we don't want to annoy people fixing a typo or require a description for a one liner explained in the title
143
-
144
- // if we have more than one commit, then it's meaningful
145
- if (pullRequest .getCommits () > 1 ) {
146
- return true ;
147
- }
148
-
149
- PullRequestFilesMatcher filesMatcher = new PullRequestFilesMatcher (pullRequest );
150
-
151
- // for changes to the BOM, we are stricter
152
- if (filesMatcher .changedFilesMatch (BOMS )) {
153
- return true ;
154
- }
155
-
156
- // for one liner/two liners, let's be a little more lenient
157
- if (pullRequest .getAdditions () <= 2 && pullRequest .getDeletions () <= 2 ) {
158
- return false ;
159
- }
160
-
161
- // let's be a little more flexible for doc changes
162
- if (filesMatcher .changedFilesOnlyMatch (DOC_CHANGES )
163
- && pullRequest .getAdditions () <= 10 && pullRequest .getDeletions () <= 10 ) {
164
- return false ;
165
- }
166
-
167
- return true ;
168
- }
169
108
}
0 commit comments