1
0

es_release_notes.pl 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  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 = qw(
  27. breaking deprecation feature
  28. enhancement bug regression doc test
  29. );
  30. my %Group_Labels = (
  31. breaking => 'Breaking changes',
  32. deprecation => 'Deprecations',
  33. doc => 'Docs',
  34. feature => 'New features',
  35. enhancement => 'Enhancements',
  36. bug => 'Bug fixes',
  37. regression => 'Regression',
  38. test => 'Tests',
  39. other => 'Not classified',
  40. );
  41. use JSON();
  42. use Encode qw(encode_utf8);
  43. my $json = JSON->new->utf8(1);
  44. my %All_Labels = fetch_labels();
  45. my $version = shift @ARGV
  46. or dump_labels();
  47. dump_labels("Unknown version '$version'")
  48. unless $All_Labels{$version};
  49. my $issues = fetch_issues($version);
  50. dump_issues( $version, $issues );
  51. #===================================
  52. sub dump_issues {
  53. #===================================
  54. my $version = shift;
  55. my $issues = shift;
  56. $version =~ s/v//;
  57. my ( $day, $month, $year ) = (gmtime)[ 3 .. 5 ];
  58. $month++;
  59. $year += 1900;
  60. for my $group ( @Groups, 'other' ) {
  61. my $group_issues = $issues->{$group} or next;
  62. print "<h2>$Group_Labels{$group}</h2>\n\n<ul>\n";
  63. for my $header ( sort keys %$group_issues ) {
  64. my $header_issues = $group_issues->{$header};
  65. my $prefix = "<li>";
  66. if ($header) {
  67. print "<li>$header:<ul>";
  68. }
  69. for my $issue (@$header_issues) {
  70. my $title = $issue->{title};
  71. $title =~ s{`([^`]+)`}{<code>$1</code>}g;
  72. if ( $issue->{state} eq 'open' ) {
  73. $title .= " [OPEN]";
  74. }
  75. unless ( $issue->{pull_request} ) {
  76. $title .= " [ISSUE]";
  77. }
  78. my $number = $issue->{number};
  79. print encode_utf8( $prefix
  80. . $title
  81. . qq[ <a href="${Issue_URL}${number}">#${number}</a>] );
  82. if ( my $related = $issue->{related_issues} ) {
  83. my %uniq = map { $_ => 1 } @$related;
  84. print keys %uniq > 1
  85. ? " (issues: "
  86. : " (issue: ";
  87. print join ", ",
  88. map {qq[<a href="${Issue_URL}${_}">#${_}</a>]}
  89. sort keys %uniq;
  90. print ")";
  91. }
  92. print "</li>\n";
  93. }
  94. if ($header) {
  95. print "</ul></li>\n";
  96. }
  97. }
  98. print "</ul>";
  99. print "\n\n";
  100. }
  101. }
  102. #===================================
  103. sub fetch_issues {
  104. #===================================
  105. my $version = shift;
  106. my @issues;
  107. my %seen;
  108. for my $state ( 'open', 'closed' ) {
  109. my $page = 1;
  110. while (1) {
  111. my $tranche
  112. = fetch( $User_Repo
  113. . 'issues?labels='
  114. . $version
  115. . '&pagesize=100&state='
  116. . $state
  117. . '&page='
  118. . $page )
  119. or die "Couldn't fetch issues for version '$version'";
  120. push @issues, @$tranche;
  121. for my $issue (@$tranche) {
  122. next unless $issue->{pull_request};
  123. for ( $issue->{body} =~ m{(?:#|${User_Repo}issues/)(\d+)}g ) {
  124. $seen{$_}++;
  125. push @{ $issue->{related_issues} }, $_;
  126. }
  127. }
  128. $page++;
  129. last unless @$tranche;
  130. }
  131. }
  132. my %group;
  133. ISSUE:
  134. for my $issue (@issues) {
  135. next if $seen{ $issue->{number} } && !$issue->{pull_request};
  136. my %labels = map { $_->{name} => 1 } @{ $issue->{labels} };
  137. my ($header) = map { substr( $_, 1 ) } grep {/^:/} keys %labels;
  138. $header ||= 'NOT CLASSIFIED';
  139. for (@Groups) {
  140. if ( $labels{$_} ) {
  141. push @{ $group{$_}{$header} }, $issue;
  142. next ISSUE;
  143. }
  144. }
  145. push @{ $group{other}{$header} }, $issue;
  146. }
  147. return \%group;
  148. }
  149. #===================================
  150. sub fetch_labels {
  151. #===================================
  152. my %all;
  153. my $page = 1;
  154. while (1) {
  155. my $labels = fetch( $User_Repo . 'labels?page=' . $page++ )
  156. or die "Couldn't retrieve version labels";
  157. last unless @$labels;
  158. for (@$labels) {
  159. my $name = $_->{name};
  160. next unless $name =~ /^v/;
  161. $all{$name} = 1;
  162. }
  163. }
  164. return %all;
  165. }
  166. #===================================
  167. sub fetch {
  168. #===================================
  169. my $url = $Base_URL . shift();
  170. my $response = HTTP::Tiny->new->get($url);
  171. die "$response->{status} $response->{reason}\n"
  172. unless $response->{success};
  173. # print $response->{content};
  174. return $json->decode( $response->{content} );
  175. }
  176. #===================================
  177. sub load_github_key {
  178. #===================================
  179. my ($file) = glob("~/.github_auth");
  180. unless ( -e $file ) {
  181. warn "File ~/.github_auth doesn't exist - using anonymous API. "
  182. . "Generate a Personal Access Token at https://github.com/settings/applications\n";
  183. return '';
  184. }
  185. open my $fh, $file or die "Couldn't open $file: $!";
  186. my ($key) = <$fh> || die "Couldn't read $file: $!";
  187. $key =~ s/^\s+//;
  188. $key =~ s/\s+$//;
  189. die "Invalid GitHub key: $key"
  190. unless $key =~ /^[0-9a-f]{40}$/;
  191. return "$key:x-oauth-basic@";
  192. }
  193. #===================================
  194. sub dump_labels {
  195. #===================================
  196. my $error = shift || '';
  197. if ($error) {
  198. $error = "\nERROR: $error\n";
  199. }
  200. my $labels = join( "\n - ", '', ( sort keys %All_Labels ) );
  201. die <<USAGE
  202. $error
  203. USAGE: $0 version > outfile
  204. Known versions:$labels
  205. USAGE
  206. }