#!/usr/bin/env perl
# Copyright [1999-2015] Wellcome Trust Sanger Institute and the EMBL-European Bioinformatics Institute
# Copyright [2016-2022] EMBL-European Bioinformatics Institute
# 
# Licensed 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;

BEGIN {
  use Cwd;
  use File::Basename;
  use File::Spec;
  my $dirname = dirname(Cwd::realpath(__FILE__));
  my $lib = File::Spec->catdir($dirname, File::Spec->updir(), 'lib');
  if(-d $lib) {
    unshift(@INC, $lib);
  }
  else {
    die "Cannot find the lib directory in the expected location $lib";
  }
};

use EnsEMBL::GitHub qw/parse_oauth_token rest_request paginated_rest_request/;

use Pod::Usage;
use Getopt::Long;
use JSON;
use HTTP::Tiny;
use File::Spec;
use Fcntl ':mode';

run();

sub run {
  my $opts = parse_command_line();
  my $repos = get_repos($opts);
  foreach my $repo (@{$repos}) {
    print "* Working with $repo\n";
    if(! has_branch($opts, $repo)) {
      print "*   Branch was not found. Skipping\n";
      next;
    }
    if(default_branch($opts, $repo)) {
      print "*   Branch is already the active default_branch\n";
    }
    else {
      print "*   Branch is there. Switching\n";
      update_branch($opts, $repo);
    }
    print "*   Finished with repo ${repo}\n";
  }
  return;
}

sub get_repos {
  my ($opts) = @_;
  if(@{$opts->{repository}}) {
    print "* Using user submitted repositories\n";
    return $opts->{repository};
  }
  my $organisation = $opts->{organisation};
  print "* Fetching all repositories from GitHub for $organisation\n";
  my $json = get_json($opts, 'GET', "/orgs/${organisation}/repos?type=all");
  return [ sort map { $_->{name} } @{$json} ];
}

sub default_branch {
  my ($opts, $repo) = @_;
  my $branch = $opts->{branch};
  my $organisation = $opts->{organisation};
  print "*   Checking for default_branch against user branch $branch\n";
  my $json = get_json($opts, 'GET', "/repos/${organisation}/${repo}");
  return 1 if $json->{default_branch} eq $branch;
  return 0;
}

sub has_branch {
  my ($opts, $repo) = @_;
  my $branch = $opts->{branch};
  my $organisation = $opts->{organisation};
  print "*   Checking if $repo has branch $branch\n";
  my $json = get_json($opts, 'GET', "/repos/${organisation}/${repo}/branches");
  foreach my $branch_spec (@{$json}) {
    if($branch_spec->{name} eq $branch) {
      return 1;
    }
  }
  return 0;
}

sub update_branch {
  my ($opts, $repo) = @_;
  if($opts->{'dry-run'}) {
    print "*   Skipping the update as we are in --dry-run\n";
    return;
  }
  
  my $organisation = $opts->{organisation};
  my $branch = $opts->{branch};
  print "*   Updating $repo to branch $branch\n";
  get_json($opts, 'PATCH', "/repos/${organisation}/${repo}", {name => $repo, default_branch => $branch});
  print "* Done\n";
  return;
}

sub get_json {
  my ($opts, $method, $url, $content) = @_;
  my $token = get_oauth_token($opts);
  return paginated_rest_request($method, $url, $token, $content);
}

sub get_oauth_token {
  my ($opts) = @_;
  return $opts->{oauth} if $opts->{oauth};
  return if ! $opts->{oauth_file};
  my $path = $opts->{oauth_file};
  my $token = parse_oauth_token($path);
  return $opts->{oauth} = $token;
}

sub parse_command_line {
  my $opts = {
    repository => [],
    organisation => 'Ensembl',
    help => 0,
    man => 0
  };

  GetOptions($opts, qw/
    repository|repo=s@
    oauth=s
    oauth_file=s
    branch=s
    organisation|organization=s
    dry-run
    help|?
    man
  /) or pod2usage(2);

  pod2usage(1) if $opts->{help};
  pod2usage(-exitval => 0, -verbose => 2) if $opts->{man};

  my $oauth_file = $opts->{oauth_file};
  if(!$opts->{oauth} && !$oauth_file) {
    pod2usage(-exitval => 1, -verbose => 1, -msg => 'You must specify a --oauth token or an --oauth_file');
  }
  if($oauth_file && ! -f $oauth_file) {
    pod2usage(-exitval => 1, -verbose => 1, -msg => 'Cannot find the file specified in your --oauth_file param: '.$oauth_file); 
  }
  if(!$opts->{branch}) {
    pod2usage(-exitval => 1, -verbose => 1, -msg => 'You must specify a --branch to switch to');
  }

  return $opts;
}

__END__
=pod

=head1 NAME

github-switchbranch - A tool for switching default branches in GitHub

=head1 SYNOPSIS

github-switchbranch --oauth OAUTH [--oauth_file FILE] [--organisation ORG] --branch BRANCH [--repository REPO] [--dry-run] [-h] [-m]

# Switch default branch in all repos

github-switchbranch --oauth XXXXXXXXXXXXXXXXXXXXXX --branch release/74

# Switch default branch in a single repo

github-switchbranch --oauth XXXXXXXXXXXXXXXXXXXXXX --branch release/74 --repository ensembl

# Dry run
github-switchbranch --oauth XXXXXXXXXXXXXXXXXXXXXX --branch release/74 --repository ensembl --dry-run

# Using a OAuth file
github-switchbranch --oauth_file ~/.private/github-oauth --branch release/74

# Using a different organisation
github-switchbranch --oauth XXXXXXXXXXXXXXXXXXXXXX --organisation EnsemblGenomes --branch release/release

=head1 DESCRIPTION

A tool for setting the default branch on Ensembl organisation projects held in GitHub. We scan for the user submitted branch and switch it if it is not the active default_branch. 

All of this is done via GitHub's REST API and requires the generation of an oauth token for authentication purposes. You can do this via your account's setting page under Applications and generate a B<Personal Access Token>.

The code can save an OAUTH token in a file and use this for authentication. To do so give it using the B<--oauth_file> option. The file must be readable only by the user (we are strict that access settings must be rw------- for user)

=head1 OPTIONS

=over 8

=item B<--oauth>

The OAuth token to use. More information is available from L<http://developer.github.com/v3/#authentication> and can be generated from your personal settings page.

=item B<--oauth_file>

The file which contains the OAuth key. Must be just the OAuth token (any whitespace will be removed). The file must be read/write only by the user and its containing directory must be read/write/execute by the user alone.

=item B<--organisation|organization>

The GitHub organisation to list repositories for. Defaults to Ensembl

=item B<--branch>

The branch to switch to

=item B<--repository|repo>

The repository to switch. If not specified we use all public repositories

=item B<--dry-run>

Do not update the target repo

=item B<--help>

Print the help information

=item B<--man>

Print a man page

=back

=cut

