├── src ├── main │ ├── resources │ │ └── application.yml │ └── java │ │ └── io │ │ └── spring │ │ └── releasenotesgenerator │ │ ├── github │ │ ├── LinkParser.java │ │ ├── Label.java │ │ ├── PullRequest.java │ │ ├── Page.java │ │ ├── Milestone.java │ │ ├── RegexLinkParser.java │ │ ├── StandardPage.java │ │ ├── User.java │ │ ├── GithubProperties.java │ │ ├── GithubService.java │ │ └── Issue.java │ │ ├── CommandProcessor.java │ │ ├── Application.java │ │ └── ChangelogGenerator.java └── test │ ├── resources │ ├── io │ │ └── spring │ │ │ └── releasenotesgenerator │ │ │ ├── output-with-no-prs │ │ │ ├── output-with-no-enhancements │ │ │ ├── output-with-duplicate-contributors │ │ │ ├── output-with-no-bugs │ │ │ └── github │ │ │ └── closed-issues-for-milestone-page-2.json │ └── Sample-Release-Notes.md │ └── java │ └── io │ └── spring │ └── releasenotesgenerator │ ├── MockIssues.java │ ├── github │ ├── MilestoneTests.java │ ├── GithubServiceTests.java │ └── IssueTests.java │ └── ChangelogGeneratorTests.java ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── .gitignore ├── README.md ├── pom.xml ├── mvnw.cmd └── mvnw /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | releasenotes: 2 | github: 3 | organization: my-org 4 | name: my-repo -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbhave/release-notes-generator/HEAD/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.3/apache-maven-3.5.3-bin.zip 2 | -------------------------------------------------------------------------------- /src/test/resources/io/spring/releasenotesgenerator/output-with-no-prs: -------------------------------------------------------------------------------- 1 | ## :star: New Features 2 | 3 | - Enhancement 1 [#2](enhancement-1-url) 4 | - Enhancement 2 [#4](enhancement-2-url) 5 | 6 | ## :beetle: Bug fixes 7 | 8 | - Bug 1 [#1](bug-1-url) 9 | - Bug 3 [#3](bug-3-url) 10 | 11 | -------------------------------------------------------------------------------- /src/test/resources/io/spring/releasenotesgenerator/output-with-no-enhancements: -------------------------------------------------------------------------------- 1 | ## :beetle: Bug fixes 2 | 3 | - Bug 1 [#1](bug-1-url) 4 | - Bug 3 [#3](bug-3-url) 5 | 6 | ## :heart: Contributors 7 | 8 | We’d like to thank all the contributors who worked on our current release! 9 | 10 | - [@contributor1](contributor1-github-url) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /src/test/resources/io/spring/releasenotesgenerator/output-with-duplicate-contributors: -------------------------------------------------------------------------------- 1 | ## :star: New Features 2 | 3 | - Enhancement 1 [#2](enhancement-1-url) 4 | - Enhancement 2 [#4](enhancement-2-url) 5 | - Enhancement 3 [#5](enhancement-5-url) 6 | - Enhancement 4 [#6](enhancement-6-url) 7 | 8 | ## :heart: Contributors 9 | 10 | We’d like to thank all the contributors who worked on our current release! 11 | 12 | - [@contributor1](contributor1-github-url) -------------------------------------------------------------------------------- /src/test/resources/io/spring/releasenotesgenerator/output-with-no-bugs: -------------------------------------------------------------------------------- 1 | ## :star: New Features 2 | 3 | - Enhancement 1 [#2](enhancement-1-url) 4 | - Enhancement 2 [#4](enhancement-2-url) 5 | - Enhancement 3 [#5](enhancement-5-url) 6 | - Enhancement 4 [#6](enhancement-6-url) 7 | 8 | ## :heart: Contributors 9 | 10 | We’d like to thank all the contributors who worked on our current release! 11 | 12 | - [@contributor1](contributor1-github-url) 13 | - [@contributor2](contributor2-github-url) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Generate release notes for a Github milestone 2 | 3 | To generate markdown release notes that comprise the list of bugs 4 | and issues in a GitHub milestone follow these steps: 5 | 6 | - Clone this project 7 | - Configure the application with the following properties: 8 | ``` 9 | releasenotes.github: 10 | username: 11 | password: 12 | organization: 13 | name: 14 | ``` 15 | 16 | NOTE: When generating release notes for a public repository, the `username` and `password` properties are optional. However, specifying them ensures that you don't hit [Github's rate limit](https://developer.github.com/v3/?#rate-limiting) easily. 17 | 18 | - Run the following commands: 19 | 20 | ``` 21 | $ ./mvnw clean install 22 | 23 | $ java -jar target/github-release-notes-generator-0.0.1-SNAPSHOT.jar 24 | ``` 25 | 26 | NOTE: Make sure to use the milestone number and not the milestone name. 27 | -------------------------------------------------------------------------------- /src/main/java/io/spring/releasenotesgenerator/github/LinkParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.releasenotesgenerator.github; 18 | 19 | import java.util.Map; 20 | 21 | /** 22 | * A {@code LinkParser} that uses a regular expression to parse the header. 23 | * 24 | * @author Madhura Bhave 25 | */ 26 | public interface LinkParser { 27 | 28 | Map parse(String header); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/io/spring/releasenotesgenerator/github/Label.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.releasenotesgenerator.github; 18 | 19 | /** 20 | * Represents GitHub label. 21 | * 22 | * @author Madhura Bhave 23 | */ 24 | public class Label { 25 | 26 | private String name; 27 | 28 | public String getName() { 29 | return this.name; 30 | } 31 | 32 | public void setName(String name) { 33 | this.name = name; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/io/spring/releasenotesgenerator/github/PullRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.releasenotesgenerator.github; 18 | 19 | import com.fasterxml.jackson.annotation.JsonProperty; 20 | 21 | /** 22 | * Represents a Github Pull Request. 23 | * 24 | * @author Madhura Bhave 25 | */ 26 | public class PullRequest { 27 | 28 | @JsonProperty("url") 29 | private String url; 30 | 31 | public String getUrl() { 32 | return this.url; 33 | } 34 | 35 | public void setUrl(String url) { 36 | this.url = url; 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/io/spring/releasenotesgenerator/github/Page.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.releasenotesgenerator.github; 18 | 19 | import java.util.List; 20 | 21 | /** 22 | * A page of results. 23 | * 24 | * @param the type of the contents of the page 25 | * @author Madhura Bhave 26 | */ 27 | public interface Page { 28 | 29 | /** 30 | * Return the next page, if any. 31 | * @return the next page or {@code null} 32 | */ 33 | Page next(); 34 | 35 | /** 36 | * Return the content of the page. 37 | * @return the content 38 | */ 39 | List getContent(); 40 | 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/io/spring/releasenotesgenerator/github/Milestone.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.releasenotesgenerator.github; 18 | 19 | import java.util.Date; 20 | 21 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 22 | import com.fasterxml.jackson.annotation.JsonProperty; 23 | 24 | /** 25 | * Details of a GitHub milestone. 26 | * 27 | * @author Madhura Bhave 28 | */ 29 | @JsonIgnoreProperties(ignoreUnknown = true) 30 | public class Milestone { 31 | 32 | private String title; 33 | 34 | @JsonProperty("closed_at") 35 | private Date dateClosed; 36 | 37 | public String getTitle() { 38 | return this.title; 39 | } 40 | 41 | public void setTitle(String title) { 42 | this.title = title; 43 | } 44 | 45 | public Date getDateClosed() { 46 | return this.dateClosed; 47 | } 48 | 49 | public void setDateClosed(Date dateClosed) { 50 | this.dateClosed = dateClosed; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/spring/releasenotesgenerator/github/RegexLinkParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.releasenotesgenerator.github; 18 | 19 | import java.util.HashMap; 20 | import java.util.Map; 21 | import java.util.regex.Matcher; 22 | import java.util.regex.Pattern; 23 | 24 | import org.springframework.util.StringUtils; 25 | 26 | /** 27 | * A {@code LinkParser} that uses a regular expression to parse the header. 28 | * 29 | * @author Madhura Bhave 30 | */ 31 | public class RegexLinkParser implements LinkParser { 32 | 33 | private static final Pattern LINK_PATTERN = Pattern.compile("<(.+)>; rel=\"(.+)\""); 34 | 35 | @Override 36 | public Map parse(String input) { 37 | Map links = new HashMap<>(); 38 | for (String link : StringUtils.commaDelimitedListToStringArray(input)) { 39 | Matcher matcher = LINK_PATTERN.matcher(link.trim()); 40 | if (matcher.matches()) { 41 | links.put(matcher.group(2), matcher.group(1)); 42 | } 43 | } 44 | return links; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/io/spring/releasenotesgenerator/CommandProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.releasenotesgenerator; 18 | 19 | import java.io.IOException; 20 | import java.util.List; 21 | 22 | import org.springframework.boot.ApplicationArguments; 23 | import org.springframework.boot.ApplicationRunner; 24 | import org.springframework.stereotype.Component; 25 | 26 | /** 27 | * {@link ApplicationRunner} that triggers the generation of the release notes based on 28 | * application arguments. 29 | * 30 | * @author Madhura Bhave 31 | */ 32 | @Component 33 | public class CommandProcessor implements ApplicationRunner { 34 | 35 | private final ChangelogGenerator generator; 36 | 37 | public CommandProcessor(ChangelogGenerator generator) { 38 | this.generator = generator; 39 | } 40 | 41 | @Override 42 | public void run(ApplicationArguments args) throws IOException { 43 | List nonOptionArgs = args.getNonOptionArgs(); 44 | String milestone = nonOptionArgs.get(0); 45 | String path = nonOptionArgs.get(1); 46 | this.generator.generate(Integer.parseInt(milestone), path); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/io/spring/releasenotesgenerator/github/StandardPage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.releasenotesgenerator.github; 18 | 19 | import java.util.List; 20 | import java.util.function.Supplier; 21 | 22 | /** 23 | * Standard implementation of {@link Page}. 24 | * 25 | * @param the type of the contents of the page 26 | * @author Madhura Bhave 27 | */ 28 | public class StandardPage implements Page { 29 | 30 | private List content; 31 | 32 | private Supplier> nextSupplier; 33 | 34 | /** 35 | * Creates a new {@code StandardPage} that has the given {@code content}. The given 36 | * {@code nextSupplier} will be used to obtain the next page {@link #next when 37 | * requested}. 38 | * @param content the content 39 | * @param nextSupplier the supplier of the next page 40 | */ 41 | public StandardPage(List content, Supplier> nextSupplier) { 42 | this.content = content; 43 | this.nextSupplier = nextSupplier; 44 | } 45 | 46 | @Override 47 | public Page next() { 48 | return this.nextSupplier.get(); 49 | } 50 | 51 | @Override 52 | public List getContent() { 53 | return this.content; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/io/spring/releasenotesgenerator/github/User.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.releasenotesgenerator.github; 18 | 19 | import java.util.Objects; 20 | 21 | import com.fasterxml.jackson.annotation.JsonProperty; 22 | 23 | /** 24 | * Details of a GitHub user. 25 | * 26 | * @author Madhura Bhave 27 | */ 28 | public class User { 29 | 30 | @JsonProperty("login") 31 | private String name; 32 | 33 | @JsonProperty("html_url") 34 | private String url; 35 | 36 | public String getName() { 37 | return this.name; 38 | } 39 | 40 | public void setName(String name) { 41 | this.name = name; 42 | } 43 | 44 | public String getUrl() { 45 | return this.url; 46 | } 47 | 48 | public void setUrl(String url) { 49 | this.url = url; 50 | } 51 | 52 | @Override 53 | public boolean equals(Object o) { 54 | if (this == o) { 55 | return true; 56 | } 57 | if (o == null || getClass() != o.getClass()) { 58 | return false; 59 | } 60 | User user = (User) o; 61 | return Objects.equals(this.name, user.name) && Objects.equals(this.url, user.url); 62 | } 63 | 64 | @Override 65 | public int hashCode() { 66 | return Objects.hash(this.name, this.url); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/io/spring/releasenotesgenerator/Application.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.releasenotesgenerator; 18 | 19 | import io.spring.releasenotesgenerator.github.GithubProperties; 20 | import io.spring.releasenotesgenerator.github.GithubService; 21 | import io.spring.releasenotesgenerator.github.RegexLinkParser; 22 | 23 | import org.springframework.boot.SpringApplication; 24 | import org.springframework.boot.autoconfigure.SpringBootApplication; 25 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 26 | import org.springframework.boot.web.client.RestTemplateBuilder; 27 | import org.springframework.context.annotation.Bean; 28 | 29 | /** 30 | * Release notes generator. 31 | * 32 | * @author Madhura Bhave 33 | */ 34 | @SpringBootApplication 35 | @EnableConfigurationProperties(GithubProperties.class) 36 | public class Application { 37 | 38 | public static void main(String[] args) { 39 | SpringApplication.run(Application.class, args); 40 | } 41 | 42 | @Bean 43 | public GithubService githubService(RestTemplateBuilder builder, 44 | GithubProperties properties) { 45 | return new GithubService(properties.getUsername(), properties.getPassword(), 46 | builder, new RegexLinkParser()); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/test/resources/Sample-Release-Notes.md: -------------------------------------------------------------------------------- 1 | ## :star: New Features 2 | 3 | * Align WebFluxTags uri support on WebMvcTags [#12685](https://api.github.com/repos/spring-projects/spring-boot/issues/12685) 4 | 5 | * #12678: Add address setting for DLQ and ExpiryQueue for all managed destinations [#12680](https://api.github.com/repos/spring-projects/spring-boot/issues/12680) 6 | 7 | * Allow validation api in the classpath without an implementation [#12669](https://api.github.com/repos/spring-projects/spring-boot/issues/12669) 8 | 9 | * Ensure kotlin compiler includes method param names [#12641](https://api.github.com/repos/spring-projects/spring-boot/issues/12641) 10 | 11 | * Avoid string copies in SpringBootBanner [#12591](https://api.github.com/repos/spring-projects/spring-boot/issues/12591) 12 | 13 | ## :beetle:Bugs fixed 14 | 15 | * Apply MeterRegistryCustomizer to composites [#12762](https://api.github.com/repos/spring-projects/spring-boot/issues/12762) 16 | 17 | * Cannot set property by OS environment with dots in spring boot 2.0 [#12728](https://api.github.com/repos/spring-projects/spring-boot/issues/12728) 18 | 19 | * Unicode parsing in OriginTrackedPropertiesLoader does not work with all code points [#12716](https://api.github.com/repos/spring-projects/spring-boot/issues/12716) 20 | 21 | * Remove HierarchicalNameMapper as a configured @Bean [#12683](https://api.github.com/repos/spring-projects/spring-boot/issues/12683) 22 | 23 | * Dependency management for Quartz is missing quartz-jobs [#12663](https://api.github.com/repos/spring-projects/spring-boot/issues/12663) 24 | 25 | * AutoConfigurationSorter does not produce the expected result if the complete chain is not provided [#12660](https://api.github.com/repos/spring-projects/spring-boot/issues/12660) 26 | 27 | * Dependency management for JUnit 5 is incomplete [#12635](https://api.github.com/repos/spring-projects/spring-boot/issues/12635) 28 | 29 | ## :heart: Thank you to all the contributors! 30 | 31 | We’d like to thank all the contributors who worked on our current release! 32 | 33 | * 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/test/java/io/spring/releasenotesgenerator/MockIssues.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.releasenotesgenerator; 18 | 19 | import io.spring.releasenotesgenerator.github.Issue; 20 | import io.spring.releasenotesgenerator.github.PullRequest; 21 | import io.spring.releasenotesgenerator.github.User; 22 | 23 | /** 24 | * @author Madhura Bhave 25 | */ 26 | public final class MockIssues { 27 | 28 | private MockIssues() { 29 | 30 | } 31 | 32 | public static Issue getBug(String title, String number, String url) { 33 | return getIssue(title, number, url, Issue.Type.BUG, false, null); 34 | } 35 | 36 | public static Issue getEnhancement(String title, String number, String url) { 37 | return getIssue(title, number, url, Issue.Type.ENHANCEMENT, false, null); 38 | } 39 | 40 | public static Issue getPullRequest(String title, String number, Issue.Type type, 41 | String url, User user) { 42 | return getIssue(title, number, url, type, true, user); 43 | } 44 | 45 | private static Issue getIssue(String title, String number, String url, 46 | Issue.Type enhancement, boolean isPullRequest, User user) { 47 | Issue issue = new Issue(); 48 | issue.setTitle(title); 49 | issue.setNumber(number); 50 | issue.setUrl(url); 51 | issue.setType(enhancement); 52 | if (isPullRequest) { 53 | PullRequest pr = new PullRequest(); 54 | issue.setUser(user); 55 | issue.setPullRequest(pr); 56 | } 57 | return issue; 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/io/spring/releasenotesgenerator/github/GithubProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.releasenotesgenerator.github; 18 | 19 | import org.springframework.boot.context.properties.ConfigurationProperties; 20 | 21 | /** 22 | * Configuration properties for the Github repo. 23 | * 24 | * @author Madhura Bhave 25 | */ 26 | @ConfigurationProperties(prefix = "releasenotes.github") 27 | public class GithubProperties { 28 | 29 | /** 30 | * The username for the github user. 31 | */ 32 | private String username; 33 | 34 | /** 35 | * The password for the github user. 36 | */ 37 | private String password; 38 | 39 | /** 40 | * The github org this repository is under. 41 | */ 42 | private String organization; 43 | 44 | /** 45 | * The name of the github repository. 46 | */ 47 | private String name; 48 | 49 | public String getOrganization() { 50 | return this.organization; 51 | } 52 | 53 | public void setOrganization(String organization) { 54 | this.organization = organization; 55 | } 56 | 57 | public String getName() { 58 | return this.name; 59 | } 60 | 61 | public void setName(String name) { 62 | this.name = name; 63 | } 64 | 65 | public String getUsername() { 66 | return this.username; 67 | } 68 | 69 | public void setUsername(String username) { 70 | this.username = username; 71 | } 72 | 73 | public String getPassword() { 74 | return this.password; 75 | } 76 | 77 | public void setPassword(String password) { 78 | this.password = password; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/io/spring/releasenotesgenerator/github/GithubService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.releasenotesgenerator.github; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Arrays; 21 | import java.util.List; 22 | 23 | import org.springframework.boot.web.client.RestTemplateBuilder; 24 | import org.springframework.http.ResponseEntity; 25 | import org.springframework.util.StringUtils; 26 | import org.springframework.web.client.RestTemplate; 27 | 28 | /** 29 | * Central class for interacting with GitHub's REST API. 30 | * 31 | * @author Madhura Bhave 32 | */ 33 | public class GithubService { 34 | 35 | private static final String ROOT_URI = "https://api.github.com/"; 36 | 37 | private final RestTemplate restTemplate; 38 | 39 | private final LinkParser linkParser; 40 | 41 | public GithubService(String username, String password, 42 | RestTemplateBuilder restTemplateBuilder, LinkParser linkParser) { 43 | this.restTemplate = restTemplateBuilder.basicAuthorization(username, password) 44 | .build(); 45 | this.linkParser = linkParser; 46 | } 47 | 48 | public List getIssuesForMilestone(int milestone, String org, String repo) { 49 | String url = ROOT_URI + "repos/" + org + "/" + repo + "/issues?milestone=" 50 | + milestone + "&state=closed"; 51 | List issues = new ArrayList<>(); 52 | Page page = getPage(url, Issue[].class); 53 | while (page != null) { 54 | issues.addAll(page.getContent()); 55 | page = page.next(); 56 | } 57 | return issues; 58 | } 59 | 60 | private Page getPage(String url, Class type) { 61 | if (!StringUtils.hasText(url)) { 62 | return null; 63 | } 64 | ResponseEntity response = this.restTemplate.getForEntity(url, type); 65 | return new StandardPage<>(Arrays.asList(response.getBody()), 66 | () -> getPage(getNextUrl(response), type)); 67 | } 68 | 69 | private String getNextUrl(ResponseEntity response) { 70 | return this.linkParser.parse(response.getHeaders().getFirst("Link")).get("next"); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/io/spring/releasenotesgenerator/github/MilestoneTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.releasenotesgenerator.github; 18 | 19 | import com.fasterxml.jackson.databind.ObjectMapper; 20 | import org.junit.Test; 21 | 22 | import static org.assertj.core.api.Assertions.assertThat; 23 | 24 | public class MilestoneTests { 25 | 26 | private final ObjectMapper objectMapper = new ObjectMapper(); 27 | 28 | @Test 29 | public void milestoneCanBeDeserialized() throws Exception { 30 | Milestone milestone = this.objectMapper.readValue(getMilestoneJson(), 31 | Milestone.class); 32 | assertThat(milestone.getTitle()).isEqualTo("2.0.1"); 33 | assertThat(milestone.getDateClosed()).isEqualTo("2018-04-05T14:02:25Z"); 34 | } 35 | 36 | private String getMilestoneJson() { 37 | return "{\n" 38 | + " \"url\": \"https://api.github.com/repos/spring-projects/spring-boot/milestones/98\",\n" 39 | + " \"html_url\": \"https://github.com/spring-projects/spring-boot/milestone/98\",\n" 40 | + " \"labels_url\": \"https://api.github.com/repos/spring-projects/spring-boot/milestones/98/labels\",\n" 41 | + " \"id\": 3152524,\n" + " \"number\": 98,\n" 42 | + " \"title\": \"2.0.1\",\n" + " \"description\": \"\",\n" 43 | + " \"creator\": {\n" + " \"login\": \"philwebb\",\n" 44 | + " \"id\": 519772,\n" 45 | + " \"avatar_url\": \"https://avatars1.githubusercontent.com/u/519772?v=4\",\n" 46 | + " \"gravatar_id\": \"\",\n" 47 | + " \"url\": \"https://api.github.com/users/philwebb\",\n" 48 | + " \"html_url\": \"https://github.com/philwebb\",\n" 49 | + " \"followers_url\": \"https://api.github.com/users/philwebb/followers\",\n" 50 | + " \"following_url\": \"https://api.github.com/users/philwebb/following{/other_user}\",\n" 51 | + " \"gists_url\": \"https://api.github.com/users/philwebb/gists{/gist_id}\",\n" 52 | + " \"starred_url\": \"https://api.github.com/users/philwebb/starred{/owner}{/repo}\",\n" 53 | + " \"subscriptions_url\": \"https://api.github.com/users/philwebb/subscriptions\",\n" 54 | + " \"organizations_url\": \"https://api.github.com/users/philwebb/orgs\",\n" 55 | + " \"repos_url\": \"https://api.github.com/users/philwebb/repos\",\n" 56 | + " \"events_url\": \"https://api.github.com/users/philwebb/events{/privacy}\",\n" 57 | + " \"received_events_url\": \"https://api.github.com/users/philwebb/received_events\",\n" 58 | + " \"type\": \"User\",\n" + " \"site_admin\": false\n" 59 | + " },\n" + " \"open_issues\": 0,\n" 60 | + " \"closed_issues\": 165,\n" + " \"state\": \"closed\",\n" 61 | + " \"created_at\": \"2018-03-01T06:07:33Z\",\n" 62 | + " \"updated_at\": \"2018-04-05T14:02:25Z\",\n" 63 | + " \"due_on\": \"2018-04-05T07:00:00Z\",\n" 64 | + " \"closed_at\": \"2018-04-05T14:02:25Z\"\n" + " }"; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.example 8 | github-release-notes-generator 9 | 0.0.1-SNAPSHOT 10 | jar 11 | 12 | io.spring.releasenotesgenerator.github-release-notes-generator 13 | Release notes generator for Github projects 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter-parent 18 | 2.0.4.RELEASE 19 | 20 | 21 | 22 | 23 | UTF-8 24 | UTF-8 25 | 1.8 26 | 0.0.6 27 | 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-configuration-processor 37 | true 38 | 39 | 40 | org.springframework 41 | spring-web 42 | 43 | 44 | com.fasterxml.jackson.core 45 | jackson-databind 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter-test 50 | test 51 | 52 | 53 | 54 | 55 | 56 | 57 | io.spring.javaformat 58 | spring-javaformat-maven-plugin 59 | ${spring-javaformat.version} 60 | 61 | 62 | validate 63 | 64 | validate 65 | 66 | 67 | 68 | 69 | 70 | org.apache.maven.plugins 71 | maven-checkstyle-plugin 72 | 3.0.0 73 | 74 | 75 | com.puppycrawl.tools 76 | checkstyle 77 | 8.11 78 | 79 | 80 | io.spring.javaformat 81 | spring-javaformat-checkstyle 82 | ${spring-javaformat.version} 83 | 84 | 85 | 86 | 87 | checkstyle-validation 88 | validate 89 | 90 | 91 | io/spring/javaformat/checkstyle/checkstyle.xml 92 | 93 | true 94 | 95 | 96 | check 97 | 98 | 99 | 100 | 101 | 102 | org.springframework.boot 103 | spring-boot-maven-plugin 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/test/java/io/spring/releasenotesgenerator/github/GithubServiceTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.releasenotesgenerator.github; 18 | 19 | import java.util.List; 20 | 21 | import org.junit.Rule; 22 | import org.junit.Test; 23 | import org.junit.rules.ExpectedException; 24 | import org.junit.runner.RunWith; 25 | 26 | import org.springframework.beans.factory.annotation.Autowired; 27 | import org.springframework.boot.test.autoconfigure.web.client.RestClientTest; 28 | import org.springframework.core.io.ClassPathResource; 29 | import org.springframework.http.HttpHeaders; 30 | import org.springframework.http.HttpMethod; 31 | import org.springframework.http.MediaType; 32 | import org.springframework.test.context.junit4.SpringRunner; 33 | import org.springframework.test.web.client.MockRestServiceServer; 34 | 35 | import static org.assertj.core.api.Assertions.assertThat; 36 | import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; 37 | import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; 38 | import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess; 39 | 40 | /** 41 | * Tests for {@link GithubService}. 42 | * 43 | * @author Madhura Bhave 44 | */ 45 | @RunWith(SpringRunner.class) 46 | @RestClientTest({ GithubService.class, RegexLinkParser.class }) 47 | public class GithubServiceTests { 48 | 49 | @Rule 50 | public ExpectedException thrown = ExpectedException.none(); 51 | 52 | @Autowired 53 | private MockRestServiceServer server; 54 | 55 | @Autowired 56 | private GithubService service; 57 | 58 | @Test 59 | public void getIssuesWhenNoIssues() { 60 | this.server.expect(requestTo( 61 | "https://api.github.com/repos/org/repo/issues?milestone=23&state=closed")) 62 | .andExpect(method(HttpMethod.GET)) 63 | .andRespond(withSuccess("[]", MediaType.APPLICATION_JSON)); 64 | List issues = this.service.getIssuesForMilestone(23, "org", "repo"); 65 | assertThat(issues.size()).isEqualTo(0); 66 | } 67 | 68 | @Test 69 | public void getIssuesWhenSinglePageOfIssuesPresent() { 70 | this.server.expect(requestTo( 71 | "https://api.github.com/repos/org/repo/issues?milestone=23&state=closed")) 72 | .andExpect(method(HttpMethod.GET)) 73 | .andRespond(withSuccess( 74 | getClassPathResource("closed-issues-for-milestone-page-1.json"), 75 | MediaType.APPLICATION_JSON)); 76 | List issues = this.service.getIssuesForMilestone(23, "org", "repo"); 77 | assertThat(issues.size()).isEqualTo(30); 78 | } 79 | 80 | @Test 81 | public void getIssuesWhenMultiplePagesOfIssuesPresent() { 82 | HttpHeaders headers = new HttpHeaders(); 83 | headers.set("Link", "; rel=\"next\""); 84 | this.server.expect(requestTo( 85 | "https://api.github.com/repos/org/repo/issues?milestone=23&state=closed")) 86 | .andExpect(method(HttpMethod.GET)) 87 | .andRespond(withSuccess( 88 | getClassPathResource("closed-issues-for-milestone-page-1.json"), 89 | MediaType.APPLICATION_JSON).headers(headers)); 90 | this.server.expect(requestTo("/page-two")).andExpect(method(HttpMethod.GET)) 91 | .andRespond(withSuccess( 92 | getClassPathResource("closed-issues-for-milestone-page-2.json"), 93 | MediaType.APPLICATION_JSON)); 94 | List issues = this.service.getIssuesForMilestone(23, "org", "repo"); 95 | assertThat(issues.size()).isEqualTo(60); 96 | } 97 | 98 | private ClassPathResource getClassPathResource(String path) { 99 | return new ClassPathResource(path, getClass()); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/io/spring/releasenotesgenerator/github/Issue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2012-2018 the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.spring.releasenotesgenerator.github; 18 | 19 | import java.util.List; 20 | 21 | import com.fasterxml.jackson.annotation.JsonProperty; 22 | 23 | /** 24 | * Details of a GitHub issue. 25 | * 26 | * @author Madhura Bhave 27 | */ 28 | public class Issue { 29 | 30 | private int id; 31 | 32 | private String number; 33 | 34 | private String title; 35 | 36 | private User user; 37 | 38 | private List