123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- #!/usr/bin/env perl
- use strict;
- use warnings;
- use HTTP::Tiny;
- use IO::Socket::SSL 1.52;
- use utf8;
- use Getopt::Long;
- my $Base_URL = "https://api.github.com/repos/";
- my $User_Repo = 'elastic/elasticsearch/';
- my $Issue_URL = "https://github.com/${User_Repo}issues";
- use JSON();
- use URI();
- use URI::Escape qw(uri_escape_utf8);
- our $json = JSON->new->utf8(1);
- our $http = HTTP::Tiny->new(
- default_headers => {
- Accept => "application/vnd.github.v3+json",
- Authorization => load_github_key()
- }
- );
- my %Opts = ( state => 'open' );
- GetOptions(
- \%Opts, #
- 'state=s', 'labels=s', 'add=s', 'remove=s'
- ) || exit usage();
- die usage('--state must be one of open|all|closed')
- unless $Opts{state} =~ /^(open|all|closed)$/;
- die usage('--labels is required') unless $Opts{labels};
- die usage('Either --add or --remove is required')
- unless $Opts{add} || $Opts{remove};
- relabel();
- #===================================
- sub relabel {
- #===================================
- my @remove = split /,/, ( $Opts{remove} || '' );
- my @add = split /,/, ( $Opts{add} || '' );
- my $add_json = $json->encode( \@add );
- my $url = URI->new( $Base_URL . $User_Repo . 'issues' );
- $url->query_form(
- state => $Opts{state},
- labels => $Opts{labels},
- per_page => 100
- );
- my $spool = Spool->new($url);
- while ( my $issue = $spool->next ) {
- my $id = $issue->{number};
- print "$Issue_URL/$id\n";
- if (@add) {
- add_label( $id, $add_json );
- }
- for (@remove) {
- remove_label( $id, $_ );
- }
- }
- print "Done\n";
- }
- #===================================
- sub add_label {
- #===================================
- my ( $id, $json ) = @_;
- my $response = $http->post(
- $Base_URL . $User_Repo . "issues/$id/labels",
- { content => $json,
- headers => { "Content-Type" => "application/json; charset=utf-8" }
- }
- );
- die "$response->{status} $response->{reason}\n"
- unless $response->{success};
- }
- #===================================
- sub remove_label {
- #===================================
- my ( $id, $name ) = @_;
- my $url
- = $Base_URL
- . $User_Repo
- . "issues/$id/labels/"
- . uri_escape_utf8($name);
- my $response = $http->delete($url);
- die "$response->{status} $response->{reason}\n"
- unless $response->{success};
- }
- #===================================
- 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 "token $key";
- }
- #===================================
- sub usage {
- #===================================
- my $msg = shift || '';
- if ($msg) {
- $msg = "\nERROR: $msg\n\n";
- }
- return $msg . <<"USAGE";
- $0 --state=open|closed|all --labels=foo,bar --add=new1,new2 --remove=old1,old2
- USAGE
- }
- package Spool;
- use strict;
- use warnings;
- #===================================
- sub new {
- #===================================
- my $class = shift;
- my $url = shift;
- return bless {
- url => $url,
- buffer => []
- },
- $class;
- }
- #===================================
- sub next {
- #===================================
- my $self = shift;
- if ( @{ $self->{buffer} } == 0 ) {
- $self->refill;
- }
- return shift @{ $self->{buffer} };
- }
- #===================================
- sub refill {
- #===================================
- my $self = shift;
- return unless $self->{url};
- my $response = $http->get( $self->{url} );
- die "$response->{status} $response->{reason}\n"
- unless $response->{success};
- $self->{url} = '';
- if ( my $link = $response->{headers}{link} ) {
- my @links = ref $link eq 'ARRAY' ? @$link : $link;
- for ($link) {
- next unless $link =~ /<([^>]+)>; rel="next"/;
- $self->{url} = $1;
- last;
- }
- }
- push @{ $self->{buffer} }, @{ $json->decode( $response->{content} ) };
- }
|