es_release_notes.pl 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. #!/usr/bin/env perl
  2. # Licensed to Elasticsearch under one or more contributor
  3. # license agreements. See the NOTICE file distributed with
  4. # this work for additional information regarding copyright
  5. # ownership. Elasticsearch licenses this file to you under
  6. # the Apache License, Version 2.0 (the "License"); you may
  7. # not use this file except in compliance with the License.
  8. # You may obtain a copy of the License at
  9. #
  10. # http://www.apache.org/licenses/LICENSE-2.0
  11. #
  12. # Unless required by applicable law or agreed to in writing,
  13. # software distributed under the License is distributed on
  14. # an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
  15. # either express or implied. See the License for the specific
  16. # language governing permissions and limitations under the License.
  17. use strict;
  18. use warnings;
  19. use HTTP::Tiny;
  20. use IO::Socket::SSL 1.52;
  21. use utf8;
  22. my $Github_Key = load_github_key();
  23. my $Base_URL = "https://${Github_Key}api.github.com/repos/";
  24. my $User_Repo = 'elastic/elasticsearch/';
  25. my $Issue_URL = "http://github.com/${User_Repo}issues/";
  26. my @Groups = (
  27. "breaking", "breaking-java", "deprecation", "feature",
  28. "enhancement", "bug", "regression", "upgrade", "non-issue", "build",
  29. "docs", "test"
  30. );
  31. my %Group_Labels = (
  32. breaking => 'Breaking changes',
  33. 'breaking-java' => 'Breaking Java changes',
  34. build => 'Build',
  35. deprecation => 'Deprecations',
  36. docs => 'Docs',
  37. feature => 'New features',
  38. enhancement => 'Enhancements',
  39. bug => 'Bug fixes',
  40. regression => 'Regressions',
  41. test => 'Tests',
  42. upgrade => 'Upgrades',
  43. "non-issue" => 'Non-issue',
  44. other => 'NOT CLASSIFIED',
  45. );
  46. use JSON();
  47. use Encode qw(encode_utf8);
  48. my $json = JSON->new->utf8(1);
  49. my %All_Labels = fetch_labels();
  50. my $version = shift @ARGV
  51. or dump_labels();
  52. dump_labels("Unknown version '$version'")
  53. unless $All_Labels{$version};
  54. my $issues = fetch_issues($version);
  55. dump_issues( $version, $issues );
  56. #===================================
  57. sub dump_issues {
  58. #===================================
  59. my $version = shift;
  60. my $issues = shift;
  61. $version =~ s/v//;
  62. my ( $day, $month, $year ) = (gmtime)[ 3 .. 5 ];
  63. $month++;
  64. $year += 1900;
  65. print <<"ASCIIDOC";
  66. :issue: https://github.com/${User_Repo}issues/
  67. :pull: https://github.com/${User_Repo}pull/
  68. [[release-notes-$version]]
  69. == $version Release Notes
  70. ASCIIDOC
  71. for my $group ( @Groups, 'other' ) {
  72. my $group_issues = $issues->{$group} or next;
  73. print "[[$group-$version]]\n"
  74. . "[float]\n"
  75. . "=== $Group_Labels{$group}\n\n";
  76. for my $header ( sort keys %$group_issues ) {
  77. my $header_issues = $group_issues->{$header};
  78. print( $header || 'HEADER MISSING', "::\n" );
  79. for my $issue (@$header_issues) {
  80. my $title = $issue->{title};
  81. if ( $issue->{state} eq 'open' ) {
  82. $title .= " [OPEN]";
  83. }
  84. unless ( $issue->{pull_request} ) {
  85. $title .= " [ISSUE]";
  86. }
  87. my $number = $issue->{number};
  88. print encode_utf8("* $title {pull}${number}[#${number}]");
  89. if ( my $related = $issue->{related_issues} ) {
  90. my %uniq = map { $_ => 1 } @$related;
  91. print keys %uniq > 1
  92. ? " (issues: "
  93. : " (issue: ";
  94. print join ", ", map {"{issue}${_}[#${_}]"}
  95. sort keys %uniq;
  96. print ")";
  97. }
  98. print "\n";
  99. }
  100. print "\n";
  101. }
  102. print "\n\n";
  103. }
  104. }
  105. #===================================
  106. sub fetch_issues {
  107. #===================================
  108. my $version = shift;
  109. my @issues;
  110. my %seen;
  111. for my $state ( 'open', 'closed' ) {
  112. my $page = 1;
  113. while (1) {
  114. my $tranche
  115. = fetch( $User_Repo
  116. . 'issues?labels='
  117. . $version
  118. . '&pagesize=100&state='
  119. . $state
  120. . '&page='
  121. . $page )
  122. or die "Couldn't fetch issues for version '$version'";
  123. push @issues, @$tranche;
  124. for my $issue (@$tranche) {
  125. next unless $issue->{pull_request};
  126. for ( $issue->{body} =~ m{(?:#|${User_Repo}issues/)(\d+)}g ) {
  127. $seen{$_}++;
  128. push @{ $issue->{related_issues} }, $_;
  129. }
  130. }
  131. $page++;
  132. last unless @$tranche;
  133. }
  134. }
  135. my %group;
  136. ISSUE:
  137. for my $issue (@issues) {
  138. next if $seen{ $issue->{number} } && !$issue->{pull_request};
  139. # uncomment for including/excluding PRs already issued in other versions
  140. # next if grep {$_->{name}=~/^v2/} @{$issue->{labels}};
  141. my %labels = map { $_->{name} => 1 } @{ $issue->{labels} };
  142. my ($header) = map { substr( $_, 1 ) } grep {/^:/} sort keys %labels;
  143. $header ||= 'NOT CLASSIFIED';
  144. for (@Groups) {
  145. if ( $labels{$_} ) {
  146. push @{ $group{$_}{$header} }, $issue;
  147. next ISSUE;
  148. }
  149. }
  150. push @{ $group{other}{$header} }, $issue;
  151. }
  152. return \%group;
  153. }
  154. #===================================
  155. sub fetch_labels {
  156. #===================================
  157. my %all;
  158. my $page = 1;
  159. while (1) {
  160. my $labels = fetch( $User_Repo . 'labels?page=' . $page++ )
  161. or die "Couldn't retrieve version labels";
  162. last unless @$labels;
  163. for (@$labels) {
  164. my $name = $_->{name};
  165. next unless $name =~ /^v/;
  166. $all{$name} = 1;
  167. }
  168. }
  169. return %all;
  170. }
  171. #===================================
  172. sub fetch {
  173. #===================================
  174. my $url = $Base_URL . shift();
  175. my $response = HTTP::Tiny->new->get($url);
  176. die "$response->{status} $response->{reason}\n"
  177. unless $response->{success};
  178. # print $response->{content};
  179. return $json->decode( $response->{content} );
  180. }
  181. #===================================
  182. sub load_github_key {
  183. #===================================
  184. my ($file) = glob("~/.github_auth");
  185. unless ( -e $file ) {
  186. warn "File ~/.github_auth doesn't exist - using anonymous API. "
  187. . "Generate a Personal Access Token at https://github.com/settings/applications\n";
  188. return '';
  189. }
  190. open my $fh, $file or die "Couldn't open $file: $!";
  191. my ($key) = <$fh> || die "Couldn't read $file: $!";
  192. $key =~ s/^\s+//;
  193. $key =~ s/\s+$//;
  194. die "Invalid GitHub key: $key"
  195. unless $key =~ /^[0-9a-f]{40}$/;
  196. return "$key:x-oauth-basic@";
  197. }
  198. #===================================
  199. sub dump_labels {
  200. #===================================
  201. my $error = shift || '';
  202. if ($error) {
  203. $error = "\nERROR: $error\n";
  204. }
  205. my $labels = join( "\n - ", '', ( sort keys %All_Labels ) );
  206. die <<USAGE
  207. $error
  208. USAGE: $0 version > outfile
  209. Known versions:$labels
  210. USAGE
  211. }