#!/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 Already exist. Skipping\n";
      next;
    }
    print "* Will now create branch $opts->{branch}\n";
    create_branch($opts, $repo);
    if ($opts->{'protect'}) {
      print "* Protecting branch $opts->{branch}\n";
      protect_branch($opts, $repo);
    }
    print "* Finished with repo ${repo}\n";
  }
  return;
}

sub get_repos {
  my ($opts) = @_;
  print "* Using user submitted repositories\n";
  return $opts->{repository};
}

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 create_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 = "refs/heads/".$opts->{branch};
  print "*   Getting $opts->{from_branch} hash reference (sha)\n";
  my $sha = get_ref_from_branch($opts,$repo);
  print "*   Creating branch $branch in $repo\n";
  get_json($opts, 'POST', "/repos/${organisation}/${repo}/git/refs", {ref => $branch, sha => $sha});
  print "* Done\n";
  return;
}

sub protect_branch {
  my ($opts, $repo) = @_;
  if($opts->{'dry-run'}) {
    print "*   Skipping the protection as we are in --dry-run\n";
    return;
  }
  my $organisation = $opts->{organisation};
  my $required_pull_request_reviews = {"dismiss_stale_reviews" => \0,"require_code_owner_reviews" => \0,"required_approving_review_count" => 1};
  my $branch = $opts->{branch};
  print "*   Protecting $branch in $repo\n";
  get_json($opts, 'PUT', "/repos/${organisation}/${repo}/branches/${branch}/protection", {required_status_checks => undef, enforce_admins => \1, required_pull_request_reviews => ${required_pull_request_reviews}, restrictions => undef});
  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 get_ref_from_branch {
  my ($opts,$repo) = @_;
  my $from_branch = $opts->{from_branch};
  my $organisation = $opts->{organisation};
  my $json = get_json($opts, 'GET', "/repos/${organisation}/${repo}/git/refs/heads/${from_branch}");
  return $json->{object}->{sha};
}

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

  GetOptions($opts, qw/
    repository|repo=s@
    oauth=s
    oauth_file=s
    branch=s
    from_branch=s
    organisation|organization=s
    dry-run
    protect
    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 name to create');
  }
  if(!$opts->{repository}) {
    pod2usage(-exitval => 1, -verbose => 1, -msg => 'You must specify a --repository');
  }
  if(!$opts->{from_branch}) {
    pod2usage(-exitval => 1, -verbose => 1, -msg => 'You must specify a --from_branch name used to create the new branch from');
  }

  return $opts;
}

__END__
=pod

=head1 NAME

github-createbranch - A tool for creating branches in GitHub

=head1 SYNOPSIS

github-createbranch --oauth OAUTH [--oauth_file FILE] [--organisation ORG] --branch BRANCH --repository REPO --from_branch FROM_BRANCH [] [--dry-run] [-h] [-m] [--protect]

# Create branch in a single repo

github-createbranch --oauth XXXXXXXXXXXXXXXXXXXXXX --branch release/74 --repository ensembl --from_branch master

# Create branch and protect it in a single repo

github-createbranch --oauth XXXXXXXXXXXXXXXXXXXXXX --branch release/74 --repository ensembl --from_branch master --protect

# Dry run
github-createbranch --oauth XXXXXXXXXXXXXXXXXXXXXX --branch release/74 --repository ensembl --from_branch master --dry-run

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

# Using a different organisation
github-createbranch --oauth XXXXXXXXXXXXXXXXXXXXXX --organisation EnsemblGenomes --branch release/release --from_branch master

=head1 DESCRIPTION

A tool for automatically create the release branch on Ensembl organisation projects held in GitHub.  

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 name of the branch to create

=item B<--from_branch>

The name of the branch to create from. E.g create a branch from master.

=item B<--repository|repo>

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

=item B<--protect>

Protect the branch against push, user will have to create a pull request that will need to be reviewed by one of the repo's team member.

=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

