8000 Add a PR description check for the license text · hibernate/hibernate-github-bot@e402989 · GitHub
[go: up one dir, main page]

Skip to content

Commit e402989

Browse files
committed
Add a PR description check for the license text
1 parent 113a48a commit e402989

File tree

5 files changed

+697
-15
lines changed

5 files changed

+697
-15
lines changed

src/main/java/org/hibernate/infra/bot/CheckPullRequestContributionRules.java

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.util.LinkedHashSet;
66
import java.util.List;
77
import java.util.Set;
8+
import java.util.regex.Matcher;
89
import java.util.regex.Pattern;
910

1011
import jakarta.inject.Inject;
@@ -55,35 +56,37 @@ public class CheckPullRequestContributionRules {
5556
void pullRequestChanged(
5657
@PullRequest.Opened @PullRequest.Reopened @PullRequest.Edited @PullRequest.Synchronize
5758
GHEventPayload.PullRequest payload,
58-
@ConfigFile("hibernate-github-bot.yml") RepositoryConfig repositoryConfig) throws IOException {
59-
checkPullRequestContributionRules( payload.getRepository(), repositoryConfig,
60-
payload.getPullRequest()
61-
);
59+
@ConfigFile("hibernate-github-bot.yml") RepositoryConfig repositoryConfig,
60+
@ConfigFile("PULL_REQUEST_TEMPLATE.md") String pullRequestTemplate) throws IOException {
61+
checkPullRequestContributionRules( payload.getRepository(), repositoryConfig, pullRequestTemplate, payload.getPullRequest() );
6262
}
6363

6464
void checkRunRequested(@CheckRun.Rerequested GHEventPayload.CheckRun payload,
65-
@ConfigFile("hibernate-github-bot.yml") RepositoryConfig repositoryConfig) throws IOException {
65+
@ConfigFile("hibernate-github-bot.yml") RepositoryConfig repositoryConfig,
66+
@ConfigFile("PULL_REQUEST_TEMPLATE.md") String pullRequestTemplate) throws IOException {
6667
for ( GHPullRequest pullRequest : payload.getCheckRun().getPullRequests() ) {
67-
checkPullRequestContributionRules( payload.getRepository(), repositoryConfig, pullRequest );
68+
checkPullRequestContributionRules( payload.getRepository(), repositoryConfig, pullRequestTemplate, pullRequest );
6869
}
6970
}
7071

7172
void checkSuiteRequested(@CheckSuite.Requested @CheckSuite.Rerequested GHEventPayload.CheckSuite payload,
72-
@ConfigFile("hibernate-github-bot.yml") RepositoryConfig repositoryConfig) throws IOException {
73+
@ConfigFile("hibernate-github-bot.yml") RepositoryConfig repositoryConfig,
74+
@ConfigFile("PULL_REQUEST_TEMPLATE.md") String pullRequestTemplate) throws IOException {
7375
for ( GHPullRequest pullRequest : payload.getCheckSuite().getPullRequests() ) {
74-
checkPullRequestContributionRules( payload.getRepository(), repositoryConfig, pullRequest );
76+
checkPullRequestContributionRules( payload.getRepository(), repositoryConfig, pullRequestTemplate, pullRequest );
7577
}
7678
}
7779

7880
private void checkPullRequestContributionRules(GHRepository repository, RepositoryConfig repositoryConfig,
81+
String pullRequestTemplate,
7982
GHPullRequest pullRequest)
8083
throws IOException {
8184
if ( !shouldCheck( repository, pullRequest ) ) {
8285
return;
8386
}
8487

8588
PullRequestCheckRunContext context = new PullRequestCheckRunContext( deploymentConfig, repository, repositoryConfig, pullRequest );
86-
List<PullRequestCheck> checks = createChecks( repositoryConfig );
89+
List<PullRequestCheck> checks = createChecks( repositoryConfig, pullRequestTemplate );
8790
List<PullRequestCheckRunOutput> outputs = new ArrayList<>();
8891
for ( PullRequestCheck check : checks ) {
8992
outputs.add( PullRequestCheck.run( context, check ) );
@@ -133,7 +136,7 @@ private GHIssueComment findExistingComment(GHPullRequest pullRequest) throws IOE
133136
return null;
134137
}
135138

136-
private List<PullRequestCheck> createChecks(RepositoryConfig repositoryConfig) {
139+
private List<PullRequestCheck> createChecks(RepositoryConfig repositoryConfig, String pullRequestTemplate) {
137140
List<PullRequestCheck> checks = new ArrayList<>();
138141
checks.add( new TitleCheck() );
139142

@@ -150,6 +153,19 @@ private List<PullRequestCheck> createChecks(RepositoryConfig repositoryConfig) {
150153
) ) );
151154
}
152155

156+
if ( repositoryConfig != null
157+
&& repositoryConfig.licenseAgreement != null
158+
&& repositoryConfig.licenseAgreement.getEnabled().orElse( Boolean.FALSE ) ) {
159+
Matcher matcher = repositoryConfig.licenseAgreement.getPullRequestTemplatePattern().matcher( pullRequestTemplate );
160+
if ( matcher.matches()
161+
&& matcher.groupCount() == 1 ) {
162+
checks.add( new LicenseCheck( matcher.group( 1 ).trim() ) );
163+
}
164+
else {
165+
throw new IllegalArgumentException( "Misconfigured license agreement check. Pattern should contain exactly 1 match group. Pattern: %s. Fetched Pull Request template: %s".formatted( repositoryConfig.licenseAgreement.getPullRequestTemplatePattern(), pullRequestTemplate ) );
166+
}
167+
}
168+
153169
return checks;
154170
}
155171

@@ -256,4 +272,26 @@ private boolean shouldCheckPullRequest(PullRequestCheckRunContext context) throw
256272
}
257273
}
258274

275+
static class LicenseCheck extends PullRequestCheck {
276+
277+
private final String agreementText;
278+
279+
protected LicenseCheck(String agreementText) {
280+
super( "Contribution — License agreement" );
281+
this.agreementText = agreementText;
282+
}
283+
284+
@Override
285+
public void perform(PullRequestCheckRunContext context, PullRequestCheckRunOutput output) throws IOException {
286+
String body = context.pullRequest.getBody();
287+
output.rule( """
288+
The pull request description must contain the following license agreement text:
289+
```
290+
%s
291+
```
292+
""".formatted( agreementText ) )
293+
.result( body != null && body.contains( agreementText ) );
294+
}
295+
}
296+
259297
}

src/main/java/org/hibernate/infra/bot/config/RepositoryConfig.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ public class RepositoryConfig {
1414

1515
public Develocity develocity;
1616

17+
public LicenseAgreement licenseAgreement;
18+
1719
public static class JiraConfig {
1820
private Optional<Pattern> issueKeyPattern = Optional.empty();
1921

@@ -129,4 +131,24 @@ public void setPattern(String pattern) {
129131
}
130132
}
131133

134+
public static class LicenseAgreement {
135+
private Optional<Boolean> enabled = Optional.empty();
136+
private Pattern pullRequestTemplatePattern = Patterns.compile( ".+([-]{22}.+[-]{22}).++" );
137+
138+
public Optional<Boolean> getEnabled() {
139+
return enabled;
140+
}
141+
142+
public void setEnabled(Boolean enabled) {
143+
this.enabled = Optional.of( enabled );
144+
}
145+
146+
public Pattern getPullRequestTemplatePattern() {
147+
return pullRequestTemplatePattern;
148+
}
149+
150+
public void setPullRequestTemplatePattern(String pullRequestTemplatePattern) {
151+
this.pullRequestTemplatePattern = Patterns.compile( pullRequestTemplatePattern );
152+
}
153+
}
132154
}

src/test/java/org/hibernate/infra/bot/tests/AbstractPullRequestTest.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,18 @@
66

77
import java.io.IOException;
88

9-
import org.junit.jupiter.api.extension.ExtendWith;
10-
11-
import io.quarkiverse.githubapp.testing.GitHubAppTest;
12-
import io.quarkus.test.junit.QuarkusTest;
139
import org.kohsuke.github.GHCheckRun;
1410
import org.kohsuke.github.GHCheckRunBuilder;
1511
import org.kohsuke.github.GHRepository;
1612
import org.mockito.Answers;
17-
import org.mockito.junit.jupiter.MockitoExtension;
1813

1914
abstract class AbstractPullRequestTest {
2015
final GHCheckRunBuilder titleCheckRunCreateBuilderMock = mockCheckRunBuilder();
2116
final GHCheckRunBuilder titleCheckRunUpdateBuilderMock = mockCheckRunBuilder();
2217
final GHCheckRunBuilder jiraCheckRunCreateBuilderMock = mockCheckRunBuilder();
2318
final GHCheckRunBuilder jiraCheckRunUpdateBuilderMock = mockCheckRunBuilder();
19+
final GHCheckRunBuilder licenseCheckRunCreateBuilderMock = mockCheckRunBuilder();
20+
final GHCheckRunBuilder licenseCheckRunUpdateBuilderMock = mockCheckRunBuilder();
2421

2522
GHCheckRunBuilder mockCheckRunBuilder() {
2623
return mock( GHCheckRunBuilder.class, withSettings().defaultAnswer( Answers.RETURNS_SELF ) );
@@ -38,6 +35,12 @@ void mockCheckRuns(GHRepository repoMock, String headSHA) throws IOException {
3835
jiraCheckRunCreateBuilderMock, jiraCheckRunMock, 43L
3936
);
4037
mockUpdateCheckRun( repoMock, 43L, jiraCheckRunUpdateBuilderMock, jiraCheckRunMock );
38+
39+
GHCheckRun licenseCheckRunMock = mock( GHCheckRun.class );
40+
mockCreateCheckRun( repoMock, "Contribution — License agreement", headSHA,
41+
licenseCheckRunCreateBuilderMock, licenseCheckRunMock, 44L
42+
);
43+
mockUpdateCheckRun( repoMock, 44L, licenseCheckRunUpdateBuilderMock, licenseCheckRunMock );
4144
}
4245

4346
void mockCreateCheckRun(GHRepository repoMock, String name, String headSHA,
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
package org.hibernate.infra.bot.tests;
2+
3+
import static io.quarkiverse.githubapp.testing.GitHubAppTesting.given;
4+
import static org.assertj.core.api.Assertions.assertThat;
5+
import static org.mockito.Mockito.verify;
6+
import static org.mockito.Mockito.verifyNoMoreInteractions;
7+
import static org.mockito.Mockito.when;
8+
9+
import java.io.IOException;
10+
11+
import org.junit.jupiter.api.Test;
12+
import org.junit.jupiter.api.extension.ExtendWith;
13+
14+
import io.quarkiverse.githubapp.testing.GitHubAppTest;
15+
import io.quarkus.test.junit.QuarkusTest;
16+
import org.kohsuke.github.GHEvent;
17+
import org.kohsuke.github.GHPullRequest;
18+
import org.kohsuke.github.GHRepository;
19+
import org.mockito.ArgumentCaptor;
20+
import org.mockito.junit.jupiter.MockitoExtension;
21+
22+
@QuarkusTest
23+
@GitHubAppTest
24+
@ExtendWith(MockitoExtension.class)
25+
public class CheckPullRequestContributionRulesLicenseTest extends AbstractPullRequestTest {
26+
@Test
27+
void bodyMissingLicenseAgreement() throws IOException {
28+
long repoId = 344815557L;
29+
long prId = 585627026L;
30+
given()
31+
.github( mocks -> {
32+
mocks.configFile("hibernate-github-bot.yml")
33+
.fromString( """
34+
jira:
35+
projectKey: "HSEARCH"
36+
licenseAgreement:
37+
enabled: true
38+
""" );
39+
mocks.configFile("PULL_REQUEST_TEMPLATE.md")
40+
.fromString( """
41+
[Please describe here what your change is about]
42+
43+
<!--
44+
Please read and do not remove the following lines:
45+
-->
46+
----------------------
47+
By submitting this pull request, I confirm that my contribution is made under the terms of the [Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0.txt)
48+
and can be relicensed under the terms of the [LGPL v2.1 license](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt) in the future at the maintainers' discretion.
49+
For more information on licensing, please check [here](https://github.com/hibernate/hibernate-search/blob/main/CONTRIBUTING.md#legal).
50+
51+
----------------------
52+
""" );
53+
54+
GHRepository repoMock = mocks.repository( "yrodiere/hibernate-github-bot-playground" );
55+
when( repoMock.getId() ).thenReturn( repoId );
56+
57+
PullRequestMockHelper.start( mocks, prId, repoMock )
58+
.commit( "HSEARCH-1111 Correct message" )
59+
.comment( "Some comment" )
60+
.comment( "Some other comment" );
61+
62+
mockCheckRuns( repoMock, "6e9f11a1e2946b207c6eb245ec942f2b5a3ea156" );
63+
} )
64+
.when()
65+
.payloadFromClasspath( "/pullrequest-opened-hsearch-1111.json" )
66+
.event( GHEvent.PULL_REQUEST )
67+
.then()
68+
.github( mocks -> {
69+
GHPullRequest prMock = mocks.pullRequest( prId );
70+
ArgumentCaptor<String> messageCaptor = ArgumentCaptor.forClass( String.class );
71+
verify( prMock ).comment( messageCaptor.capture() );
72+
assertThat( messageCaptor.getValue() )
73+
.isEqualTo( """
74+
Thanks for your pull request!
75+
76+
This pull request does not follow the contribution rules. Could you have a look?
77+
78+
❌ The pull request description must contain the following license agreement text:
79+
```
80+
----------------------
81+
By submitting this pull request, I confirm that my contribution is made under the terms of the [Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0.txt)
82+
and can be relicensed under the terms of the [LGPL v2.1 license](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt) in the future at the maintainers' discretion.
83+
For more information on licensing, please check [here](https://github.com/hibernate/hibernate-search/blob/main/CONTRIBUTING.md#legal).
84+
85+
----------------------
86+
```
87+
88+
89+
› This message was automatically generated.""" );
90+
verifyNoMoreInteractions( mocks.ghObjects() );
91+
} );
92+
}
93+
94+
@Test
95+
void bodyContainsLicenseAgreement() throws IOException {
96+
long repoId = 344815557L;
97+
long prId = 585627026L;
98+
given()
99+
.github( mocks -> {
100+
mocks.configFile("hibernate-github-bot.yml")
101+
.fromString( """
102+
jira:
103+
projectKey: "HSEARCH"
104+
licenseAgreement:
105+
enabled: true
106+
""" );
107+
mocks.configFile("PULL_REQUEST_TEMPLATE.md")
108+
.fromString( """
109+
[Please describe here what your change is about]
110+
111+
<!--
112+
Please read and do not remove the following lines:
113+
-->
114+
----------------------
115+
By submitting this pull request, I confirm that my contribution is made under the terms of the [Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0.txt)
116+
and can be relicensed under the terms of the [LGPL v2.1 license](https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt) in the future at the maintainers' discretion.
117+
For more information on licensing, please check [here](https://github.com/hibernate/hibernate-search/blob/main/CONTRIBUTING.md#legal).
118+
119+
----------------------
120+
""" );
121+
122+
GHRepository repoMock = mocks.repository( "yrodiere/hibernate-github-bot-playground" );
123+
when( repoMock.getId() ).thenReturn( repoId );
124+
125+
PullRequestMockHelper.start( mocks, prId, repoMock )
126+
.commit( "HSEARCH-1111 Correct message" )
127+
.comment( "Some comment" )
128+
.comment( "Some other comment" );
129+
130+
mockCheckRuns( repoMock, "6e9f11a1e2946b207c6eb245ec942f2b5a3ea156" );
131+
} )
132+
.when()
133+
.payloadFromClasspath( "/pullrequest-opened-hsearch-1111-with-license.json" )
134+
.event( GHEvent.PULL_REQUEST )
135+
.then()
136+
.github( mocks -> {
137+
verifyNoMoreInteractions( mocks.ghObjects() );
138+
} );
139+
}
140+
141+
}

0 commit comments

Comments
 (0)
0