es_release_notes.pl 6.9 KB

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