123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- #!/usr/bin/env perl
- # Licensed to Elasticsearch under one or more contributor
- # license agreements. See the NOTICE file distributed with
- # this work for additional information regarding copyright
- # ownership. Elasticsearch licenses this file to you under
- # the Apache License, Version 2.0 (the "License"); you may
- # not use this file except in compliance with the License.
- # You may obtain a copy of the License at
- #
- # http://www.apache.org/licenses/LICENSE-2.0
- #
- # Unless required by applicable law or agreed to in writing,
- # software distributed under the License is distributed on
- # an 'AS IS' BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
- # either express or implied. See the License for the specific
- # language governing permissions and limitations under the License.
- use strict;
- use warnings;
- use HTTP::Tiny 0.070;
- use IO::Socket::SSL 1.52;
- use utf8;
- my $Github_Key = load_github_key();
- my $Base_URL = "https://${Github_Key}api.github.com/repos/";
- my $User_Repo = 'elastic/elasticsearch/';
- my $Issue_URL = "http://github.com/${User_Repo}issues/";
- my @Groups = (
- ">breaking", ">breaking-java", ">deprecation", ">feature",
- ">enhancement", ">bug", ">regression", ">upgrade"
- );
- my %Ignore = map { $_ => 1 }
- ( ">non-issue", ">refactoring", ">docs", ">test", ">test-failure", ":Core/Build", "backport" );
- my %Group_Labels = (
- '>breaking' => 'Breaking changes',
- '>breaking-java' => 'Breaking Java changes',
- '>deprecation' => 'Deprecations',
- '>feature' => 'New features',
- '>enhancement' => 'Enhancements',
- '>bug' => 'Bug fixes',
- '>regression' => 'Regressions',
- '>upgrade' => 'Upgrades',
- 'other' => 'NOT CLASSIFIED',
- );
- my %Area_Overrides = (
- ':ml' => 'Machine Learning',
- ':beats' => 'Beats Plugin',
- ':Docs' => 'Docs Infrastructure'
- );
- use JSON();
- use Encode qw(encode_utf8);
- my $json = JSON->new->utf8(1);
- my %All_Labels = fetch_labels();
- my $version = shift @ARGV
- or dump_labels();
- dump_labels("Unknown version '$version'")
- unless $All_Labels{$version};
- my $issues = fetch_issues($version);
- dump_issues( $version, $issues );
- #===================================
- sub dump_issues {
- #===================================
- my $version = shift;
- my $issues = shift;
- $version =~ s/v//;
- my $branch = $version;
- $branch =~ s/\.\d+$//;
- my ( $day, $month, $year ) = (gmtime)[ 3 .. 5 ];
- $month++;
- $year += 1900;
- print <<"ASCIIDOC";
- :issue: https://github.com/${User_Repo}issues/
- :pull: https://github.com/${User_Repo}pull/
- [[release-notes-$version]]
- == $version Release Notes
- coming[$version]
- Also see <<breaking-changes-$branch>>.
- ASCIIDOC
- for my $group ( @Groups, 'other' ) {
- my $group_issues = $issues->{$group} or next;
- my $group_id = $group;
- $group_id =~ s/^>//;
- print "[[$group_id-$version]]\n"
- . "[float]\n"
- . "=== $Group_Labels{$group}\n\n";
- for my $header ( sort keys %$group_issues ) {
- my $header_issues = $group_issues->{$header};
- print( $header || 'HEADER MISSING', "::\n" );
- for my $issue (@$header_issues) {
- my $title = $issue->{title};
- if ( $issue->{state} eq 'open' ) {
- $title .= " [OPEN]";
- }
- unless ( $issue->{pull_request} ) {
- $title .= " [ISSUE]";
- }
- my $number = $issue->{number};
- print encode_utf8("* $title {pull}${number}[#${number}]");
- if ( my $related = $issue->{related_issues} ) {
- my %uniq = map { $_ => 1 } @$related;
- print keys %uniq > 1
- ? " (issues: "
- : " (issue: ";
- print join ", ", map {"{issue}${_}[#${_}]"}
- sort keys %uniq;
- print ")";
- }
- print "\n";
- }
- print "\n";
- }
- print "\n\n";
- }
- }
- #===================================
- sub fetch_issues {
- #===================================
- my $version = shift;
- my @issues;
- my %seen;
- for my $state ( 'open', 'closed' ) {
- my $page = 1;
- while (1) {
- my $tranche
- = fetch( $User_Repo
- . 'issues?labels='
- . $version
- . '&pagesize=100&state='
- . $state
- . '&page='
- . $page )
- or die "Couldn't fetch issues for version '$version'";
- push @issues, @$tranche;
- for my $issue (@$tranche) {
- next unless $issue->{pull_request};
- for ( $issue->{body} =~ m{(?:#|${User_Repo}issues/)(\d+)}g ) {
- $seen{$_}++;
- push @{ $issue->{related_issues} }, $_;
- }
- }
- $page++;
- last unless @$tranche;
- }
- }
- my %group;
- ISSUE:
- for my $issue (@issues) {
- next if $seen{ $issue->{number} } && !$issue->{pull_request};
- for ( @{ $issue->{labels} } ) {
- next ISSUE if $Ignore{ $_->{name} };
- }
- # uncomment for including/excluding PRs already issued in other versions
- # next if grep {$_->{name}=~/^v2/} @{$issue->{labels}};
- my %labels = map { $_->{name} => 1 } @{ $issue->{labels} };
- my @area_labels = grep {/^:/} sort keys %labels;
- my ($header) = map { m{:[^/]+/(.+)} && $1 } @area_labels;
- if (scalar @area_labels > 1) {
- $header = "MULTIPLE AREA LABELS";
- }
- if (scalar @area_labels == 1 && exists $Area_Overrides{$area_labels[0]}) {
- $header = $Area_Overrides{$area_labels[0]};
- }
- $header ||= 'NOT CLASSIFIED';
- for (@Groups) {
- if ( $labels{$_} ) {
- push @{ $group{$_}{$header} }, $issue;
- next ISSUE;
- }
- }
- push @{ $group{other}{$header} }, $issue;
- }
- return \%group;
- }
- #===================================
- sub fetch_labels {
- #===================================
- my %all;
- my $page = 1;
- while (1) {
- my $labels = fetch( $User_Repo . 'labels?page=' . $page++ )
- or die "Couldn't retrieve version labels";
- last unless @$labels;
- for (@$labels) {
- my $name = $_->{name};
- next unless $name =~ /^v/;
- $all{$name} = 1;
- }
- }
- return %all;
- }
- #===================================
- sub fetch {
- #===================================
- my $url = $Base_URL . shift();
- my $response = HTTP::Tiny->new->get($url);
- die "$response->{status} $response->{reason}\n"
- unless $response->{success};
- # print $response->{content};
- return $json->decode( $response->{content} );
- }
- #===================================
- sub load_github_key {
- #===================================
- my ($file) = glob("~/.github_auth");
- unless ( -e $file ) {
- warn "File ~/.github_auth doesn't exist - using anonymous API. "
- . "Generate a Personal Access Token at https://github.com/settings/applications\n";
- return '';
- }
- open my $fh, $file or die "Couldn't open $file: $!";
- my ($key) = <$fh> || die "Couldn't read $file: $!";
- $key =~ s/^\s+//;
- $key =~ s/\s+$//;
- die "Invalid GitHub key: $key"
- unless $key =~ /^[0-9a-f]{40}$/;
- return "$key:x-oauth-basic@";
- }
- #===================================
- sub dump_labels {
- #===================================
- my $error = shift || '';
- if ($error) {
- $error = "\nERROR: $error\n";
- }
- my $labels = join( "\n - ", '', ( sort keys %All_Labels ) );
- die <<USAGE
- $error
- USAGE: $0 version > outfile
- Known versions:$labels
- USAGE
- }
|