#!/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;

# This normally lives in ~ensembl/git-ensembl.cfg or ~/git-ensembl.cfg
my $CFG_FILE = 'git-ensembl.cfg';
# All default ensembl repos have this public root
my $GITHUB_HTTPS_ROOT = 'https://github.com/Ensembl/';
my $GITHUB_SSH_ROOT = 'ssh://git@github.com/Ensembl/';
my $EG_GITHUB_HTTPS_ROOT = 'https://github.com/EnsemblGenomes/';
my $EG_GITHUB_SSH_ROOT = 'ssh://git@github.com/EnsemblGenomes/';
my $SO_GITHUB_URL = 'https://github.com/The-Sequence-Ontology/';
my $LOCAL_HOOKS_DIR = '.git/hooks';
my $CENTRAL_HOOKS_DIR;

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');
  $CENTRAL_HOOKS_DIR = File::Spec->catdir($dirname, File::Spec->updir(), 'hooks');
  if (-d $lib) {
    unshift(@INC, $lib);
  }
  else {
    die "Cannot find the lib directory in the expected location $lib";
  }
};

use Pod::Usage;
use Getopt::Long;
use Cwd;
use EnsEMBL::Git;

run();

sub run {
  my $opts = parse_command_line();
  if ($opts->{list}) {
    run_list($opts);
    exit 0;
  }

  chdir $opts->{dir};
  verify_groups($opts);
  foreach my $grp (@{$opts->{groups}}) {
    if ($opts->{clone}) {
      if ($opts->{depth}) {
        run_shallowclone($opts, $grp);
      }
      else {
        run_clone($opts, $grp);
      }
    }
    elsif ($opts->{checkout}) {
      run_checkout($opts, $grp);
    }
    elsif ($opts->{local_checkout}) {
      run_local_checkout($opts, $grp);
    }
    elsif ($opts->{pull}) {
      run_pull($opts, $grp);
    }
    elsif ($opts->{fetch}) {
      run_fetch($opts, $grp);
    }
    elsif ($opts->{status}) {
      run_status($opts, $grp);
    }
    elsif ($opts->{cmd}) {
      run_git_cmd($opts, $grp);
    }
    elsif ($opts->{hooks}) {
      run_hooks($opts, $grp);
    }
  }
  exit 0;
}

sub parse_command_line {
  my $opts = {
      dir     => cwd(),
      remote  => 'origin',
      verbose => 0,
      help    => 0,
      man     => 0
  };

  GetOptions($opts, qw/
      clone
      shallow
      depth=i
      checkout
      local_checkout
      branch=s
      secondary_branch=s
      remote=s
      pull
      fetch
      cmd=s
      status
      dir=s
      groups
      rebase
      list
      ssh
      name=s
      email=s
      force_group
      force_module
      ignore_module=s@
      config|cfg=s
      hooks=s
      verbose
      help|?
      man
  /) or pod2usage(2);

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

  if ($opts->{checkout} && !$opts->{branch}) {
    pod2usage(-exitval => 1, -verbose => 1, -msg => 'You must a --branch when using --checkout');
  }
  if ($opts->{local_checkout} && !$opts->{branch}) {
    pod2usage(-exitval => 1, -verbose => 1, -msg => 'You must a --branch when using --local_checkout');
  }

  # parsing grabbed the -- args so we're left with just the final groups
  if (!$opts->{list} && !@ARGV) {
    pod2usage(-exitval => 1, -verbose => 1, -msg => 'No groups specified; please specify some');
  }
  $opts->{groups} = [ map {s/\/$//; $_} @ARGV ];

  return $opts;
}

sub run_clone {
  my ($opts, $group) = @_;
  my $verbose = $opts->{verbose};
  _loop_modules($opts, $group, sub {
    my ($module, $remote_url) = @_;

    if (-d $module) {
      print STDERR "! Skipping module as there is already a directory called that; try 'git ensembl --pull'\n";
      return;
    }

    print "* Cloning from remote '${remote_url}'\n";
    if (!clone($remote_url, $verbose, $opts->{remote})) {
      print STDERR "! Failed to clone the module '$module'\n";
      return;
    }

    # Now deal with config variables
    if ($opts->{name} || $opts->{email}) {
      safe_cwd($module, sub {
        print "* Setting user/email config variables\n";
        add_config('user.name', $opts->{name}) if $opts->{name};
        add_config('user.email', $opts->{email}) if $opts->{email};
      });
    }

    # Switch branch if we had that info
    my $branch = $opts->{branch};
    my $secondary_branch = $opts->{secondary_branch};
    if ($branch) {
      safe_cwd($module, sub {
        #$remote will be origin since it is the only remote we cloned
        my $remote = 'origin';
        _checkout_tracking_branch($branch, $remote, $verbose, $secondary_branch);
      });
    }

    # Finally enable hooks if hooks directory exists in ensembl-git-tools
    if (-d $CENTRAL_HOOKS_DIR) {
      print "* Enabling git hooks\n";
      _enable_hooks($module, 1);
    }
    else {
      print STDERR "! Skipping enabling hooks as ensembl hooks directory is missing.\n";
    }

    return;
  });

  return;
}

sub run_shallowclone {
  my ($opts, $group) = @_;
  my $verbose = $opts->{verbose};
  _loop_modules($opts, $group, sub {
    my ($module, $remote_url) = @_;

    if (-d $module) {
      print STDERR "! Skipping module as there is already a directory called that; try 'git ensembl --pull'\n";
      return;
    }

    print "* Cloning from remote '${remote_url}'\n";
    if (!shallow_clone($remote_url, $verbose, $opts->{remote}, $opts->{depth}, $opts->{branch}, $opts->{secondary_branch})) {
      print STDERR "! Failed to clone the module '$module'\n";
      return;
    }

    return;
  });
}

sub run_checkout {
  my ($opts, $group) = @_;
  my $branch = $opts->{branch};
  my $secondary_branch = $opts->{secondary_branch};
  my $verbose = $opts->{verbose};
  my $remote = $opts->{remote};
  _loop_modules_and_chdir($opts, $group, sub {
    print "* Fetching from origin before checking out\n";
    fetch($verbose);
    if (!_checkout_tracking_branch($branch, $remote, $verbose, $secondary_branch)) {
      return;
    }
    return;
  });
  return;
}

sub run_pull {
  my ($opts, $group) = @_;
  my $branch = $opts->{branch};
  my $secondary_branch = $opts->{secondary_branch};
  my $verbose = $opts->{verbose};
  my $remote = $opts->{remote};
  my $rebase = $opts->{rebase};
  _loop_modules_and_chdir($opts, $group, sub {
    my ($module) = @_;
    if ($branch) {
      print "* Switching branches before initating pull from origin\n";
      # If we cannot switch then bail
      if (!_checkout_tracking_branch($branch, $remote, $verbose, $secondary_branch)) {
        return;
      }
    }

    print "* Performing pull\n";
    pull($remote, $verbose, $rebase);
    fetch('tags', $verbose);

    return;
  });
  return;
}

# Loops the group, checks for the existence of the specified branch, 
# creates it if it does not exist and then performs a checkout
sub run_local_checkout {
  my ($opts, $group) = @_;
  my $branch = $opts->{branch};
  my $verbose = $opts->{verbose};
  _loop_modules_and_chdir($opts, $group, sub {
    # warn current_branch;
    if (current_branch() eq $branch) {
      print "* No need to switch. We are already on branch $branch\n";
      return;
    }
    if (!branch_exists($branch)) {
      print "* Branch $branch does not exist; creating a new one\n";
      branch($branch, $verbose);
    }
    print "* Checking out $branch\n";
    checkout($branch, $verbose);
    return;
  });
}

sub _checkout_tracking_branch {
  my ($branch, $remote, $verbose, $secondary_branch) = @_;
  print "* Checking out branch '${branch}'";
  print ". Using '${secondary_branch}' as a backup" if $secondary_branch;
  print "\n";
  if (!checkout_tracking($branch, $remote, $verbose, $secondary_branch)) {
    printf STDERR "! Cannot switch to ${branch}\n";
    return 0;
  }
  return 1;
}

sub run_fetch {
  my ($opts, $group) = @_;
  _loop_modules_and_chdir($opts, $group, sub {
    my ($module) = @_;
    print "* Performing fetch\n";
    fetch(undef, $opts->{verbose});
    fetch('tags', $opts->{verbose});
    return;
  });
  return;
}

sub run_list {
  my ($opts) = @_;
  my $modules = get_modules($opts);
  print "[Registered Modules]\n";
  foreach my $module (sort keys %{$modules}) {
    printf("\t%s (%s)\n", $module, $modules->{$module});
  }
  print "\n";

  my $all_groups = get_groups($opts);
  foreach my $group (sort keys %{$all_groups}) {
    my $definitions = $all_groups->{$group};
    printf("[%s] - %s\n", $group, $definitions->{desc});
    printf("\t%s\n", $_) for sort @{$definitions->{modules}};
    print "\n";
  }
  return;
}

sub run_status {
  my ($opts, $group) = @_;
  _loop_modules_and_chdir($opts, $group, sub {
    status();
    return;
  });
  return;
}

sub run_git_cmd {
  my ($opts, $group) = @_;
  my $cmd = $opts->{cmd};
  _loop_modules_and_chdir($opts, $group, sub {
    system_ok("git $cmd");
    return;
  });
  return;
}

sub run_hooks {
  my ($opts, $group) = @_;

  if ($opts->{hooks} !~ /^(en|dis)able$/) {
    pod2usage(-exitval => 1, -verbose => 1, -msg => 'You must provide a flag (enable or disable) when using --hooks');
  }

  my $enable = $opts->{hooks} eq 'enable';

  if ($enable && !-d $CENTRAL_HOOKS_DIR) {
    print STDERR "Could not enable git hooks as hooks directory is missing.\n";
    return;
  }

  printf "* %s git hooks\n", $enable ? 'Enabling' : 'Disabling';

  _loop_modules($opts, $group, sub {
    my ($module) = @_;

    _enable_hooks($module, $enable);

    return;
  });
}

sub _enable_hooks {
  my ($module, $flag) = @_;
  if ($flag) { #enable
    safe_cwd($module, sub {
      opendir(my $hooks_dir, $CENTRAL_HOOKS_DIR);
      while (my $filen = readdir $hooks_dir) {
        symlink "$CENTRAL_HOOKS_DIR/$filen", "$LOCAL_HOOKS_DIR/$filen";
      }
      closedir $hooks_dir;
    });
  }
  else { #disable
    safe_cwd($module, sub {
      opendir(my $hooks_dir, $LOCAL_HOOKS_DIR);
      while (my $filen = readdir $hooks_dir) {
        unlink "$LOCAL_HOOKS_DIR/$filen" if -l "$LOCAL_HOOKS_DIR/$filen";
      }
      closedir $hooks_dir;
    });
  }
}

sub _loop_modules_and_chdir {
  my ($opts, $group, $callback) = @_;
  _loop_modules($opts, $group, sub {
    my ($module, $remote) = @_;
    if (!-d $module) {
      print STDERR "! Skipping module as there is no directory called that; try 'git ensembl --clone'\n";
      return;
    }
    chdir($module);
    if (is_git_repo()) {
      $callback->($module, $remote);
    }
    else {
      print STDERR "! Skipping '${module}' because it is not a Git directory\n";
    }
    chdir(File::Spec->updir());
    return;
  });
}

# Takes a group name and a callback. We grab the modules and for each one we ask 
# the callback to process the name and it's remote URL
sub _loop_modules {
  my ($opts, $group, $callback) = @_;
  printf "* Processing '%s'\n\n", $group;
  my $modules_lookup = get_modules($opts);
  my $modules = group_to_modules($opts, $group);
  if (!@{$modules}) {
    print "* Cannot find any modules. Check that this group was active using --list\n";
    return;
  }

  foreach my $module (sort @{$modules}) {
    if ($opts->{seen_modules}->{$module}) {
      printf "* Skipping '%s' as we have already processed it\n", $module;
      next;
    }
    if ($opts->{ignore_module} && grep ( /^$module$/, @{$opts->{ignore_module}})) {
      printf "* Skipping '%s' requested to ignore it on command line\n", $module;
      next;
    }
    printf "* Working with module '%s'\n", $module;
    my $remote = $modules_lookup->{$module};
    if (!$remote) {
      printf "* Skipping '%s' as we had no remote URL linked to it. Check your config\n", $module;
      next;
    }
    $callback->($module, $remote);
    $opts->{seen_modules}->{$module} = 1;
  }
  continue {
    print "\n";
  }

  return;
}

# Loop through the groups and decide if we have config linked to them
sub verify_groups {
  my ($opts) = @_;
  my $modules = get_modules($opts);
  my $groups = get_groups($opts);
  foreach my $group (@{$opts->{groups}}) {
    if ($opts->{force_module}) {
      if (!exists $modules->{$group}) {
        pod2usage(-exitval => 1, -verbose => 0, -msg => "--force_module is on. We do not understand the module '${group}'. Please use the --list command to see all available groups and modules");
      }
    }
    elsif ($opts->{force_group}) {
      if (!exists $groups->{$group}) {
        pod2usage(-exitval => 1, -verbose => 0, -msg => "--force_group is on. We do not understand the group '${group}'. Please use the --list command to see all available groups and modules");
      }
    }
    else {
      if (!exists $groups->{$group} && !exists $modules->{$group}) {
        pod2usage(-exitval => 1, -verbose => 0, -msg => "We do not understand the group or module '${group}'. Please use the --list command to see all available groups and modules");
      }
    }
  }
  return 1;
}

sub group_to_modules {
  my ($opts, $group) = @_;
  my $modules = get_modules($opts);
  my $groups = get_groups($opts);
  if ($opts->{force_group}) {
    return $groups->{$group}->{modules};
  }
  elsif ($opts->{force_module}) {
    return [ $group ] if exits $modules->{$group};
  }
  else {
    if (exists $modules->{$group}) {
      return [ $group ];
    }
    elsif (exists $groups->{$group}) {
      #Otherwise return the group. We know it's OK because of verify_groups()
      return $groups->{$group}->{modules};
    }
  }
  return {};
}

sub get_groups {
  my ($opts) = @_;
  return $opts->{built}->{groups} if exists $opts->{built}->{groups};
  my $default_groups = _default_groups($opts);
  my $central_cfg = _central_cfg($opts);
  my $user_cfg = _user_cfg($opts);
  my $cmd_cfg = _cmdline_cfg($opts);

  my $groups = {
      %{$default_groups},
      (%{$central_cfg->{groups} || {}}),
      (%{$user_cfg->{groups} || {}}),
      (%{$cmd_cfg->{groups} || {}}),
  };

  if (!exists $groups->{all} || !exists $groups->{available}) {
    my $modules = get_modules($opts);
    $groups->{all} ||= {
        desc    => 'An auto-generated group pointing at all modules known to the script. Use this to apply commands to all repositories',
        modules => [ keys %{$modules} ],
    };
    $groups->{available} ||= {
        desc    => 'An auto-generated group pointing at all modules known to the script, and available in the directory. Use this to apply commands to all available repositories',
        modules => [ grep -d, keys %{$modules} ],
    };
  }

  return $opts->{built}->{groups} = $groups;
}

sub get_modules {
  my ($opts) = @_;
  return $opts->{built}->{modules} if exists $opts->{built}->{modules};
  my $default_modules = _default_modules($opts);
  my $central_cfg = _central_cfg($opts);
  my $user_cfg = _user_cfg($opts);
  my $cmd_cfg = _cmdline_cfg($opts);
  return $opts->{built}->{modules} = {
      %{$default_modules},
      (%{$central_cfg->{modules} || {}}),
      (%{$user_cfg->{modules} || {}}),
      (%{$cmd_cfg->{modules} || {}}),
  };
}

# Grab the user groups from $HOME/$CFG_FILE
sub _user_cfg {
  my ($opts) = @_;
  my $my_home = (getpwuid($<))[7];
  return _json(File::Spec->catfile($my_home, $CFG_FILE));
}

# Grab the central groups from $ENSHOME/$CFG_FILE
sub _central_cfg {
  my ($opts) = @_;
  my $ens_home = (getpwnam('ensembl'))[7];
  # If ens_home was undefined then we don't have an ensembl user so skip
  return {} if !defined $ens_home;
  return _json(File::Spec->catfile($ens_home, $CFG_FILE));
}

# Read config from the command line
sub _cmdline_cfg {
  my ($opts) = @_;
  return _json($opts->{config});
}

# Load a JSON file. Also do error checking for file existence
sub _json {
  my ($file) = @_;
  return {} if !$file;
  return {} if !-f $file;
  return json($file);
}

# Provide the default repos
sub _default_modules {
  my ($opts) = @_;
  my $ens_root = ($opts->{ssh}) ? $GITHUB_SSH_ROOT : $GITHUB_HTTPS_ROOT;
  my $eg_root = ($opts->{ssh}) ? $EG_GITHUB_SSH_ROOT : $EG_GITHUB_HTTPS_ROOT;
  my %def_modules;
  my $def_groups = _default_groups($opts);
  foreach my $group_name (keys %{$def_groups}) {
    foreach my $module (@{$def_groups->{$group_name}->{modules}}) {
      if ($module =~ m/SO-Ontologies/){
        $def_modules{$module} = $SO_GITHUB_URL . $module . '.git';
      } elsif ($module =~ m/^(eg-|ensemblgenomes-)/) {
        $def_modules{$module} = $eg_root . $module . '.git';
      } else {
        $def_modules{$module} = $ens_root . $module . '.git';
      }
    }
  }
  return \%def_modules;
}

# Provide the default groups
sub _default_groups {
  my ($opts) = @_;
  return {
      api           => {
          desc    => 'API module set used for querying and processing Ensembl data',
          modules => [ qw/ensembl ensembl-compara ensembl-variation ensembl-funcgen ensembl-io ensembl-taxonomy ensembl-metadata/ ],
      },
      tools         => {
          desc    => 'Libraries required to run Ensembl tools such as the VEP',
          modules => [ qw/ensembl ensembl-compara ensembl-variation ensembl-funcgen ensembl-tools ensembl-io/ ],
      },
      production    => {
          desc    => 'Libraries required to run Ensembl production',
          modules => [ qw/ensembl ensembl-analysis ensembl-biomart ensembl-compara ensembl-datacheck ensembl-funcgen ensembl-hive ensembl-io ensembl-metadata ensembl-ontology-schema ensembl-orm ensembl-production ensembl-py ensembl-taxonomy ensembl-tools ensembl-variation ensembl-vep ols-client ols-ensembl-loader SO-Ontologies GIFTS/ ],
      },
      rest          => {
          desc    => 'Libraries required to run the Ensembl REST API',
          modules => [ qw/ensembl ensembl-compara ensembl-variation ensembl-funcgen ensembl-rest ensembl-io VEP_plugins ensembl-vep ensembl-hdf5 ensembl-taxonomy ensembl-metadata/ ],
      },
      rapid         => {
          desc    => 'Libraries required to run Ensembl Rapid Release website',
          modules => [ qw/ensembl ensembl-compara ensembl-variation ensembl-funcgen ensembl-webcode public-plugins ebi-plugins ensembl-orm ensembl-tools ensembl-io ensembl-hive ensembl-metadata ensembl-rapid/ ],
      },
      'public-web'  => {
          desc    => 'Libraries required to run an external mirror of the Ensembl website',
          modules => [ qw/ensembl ensembl-compara ensembl-variation ensembl-funcgen ensembl-webcode public-plugins ensembl-orm ensembl-tools ensembl-io/ ],
      },
      web           => {
          desc    => 'Libraries required to run the Ensembl website',
          modules => [ qw/ensembl ensembl-compara ensembl-variation ensembl-funcgen ensembl-webcode public-plugins ebi-plugins ensembl-orm ensembl-tools ensembl-io/ ],
      },
      genebuild     => {
          desc    => 'Libraries required to run the Ensembl Gene Annotation process',
          modules => [ qw/ensembl ensembl-analysis ensembl-hive ensembl-killlist ensembl-production ensembl-taxonomy ensembl-metadata ensembl-variation ensembl-compara ensembl-genes ensembl-io/ ],
      },
      'eg-bacteria' => {
          desc    => 'Libraries required for Ensembl Genomes Bacteria',
          modules => [ qw/eg-web-common eg-web-search ensembl-metadata ensembl-taxonomy eg-web-bacteria/ ],
      },
      'eg-fungi'    => {
          desc    => 'Libraries required for Ensembl Genomes Fungi',
          modules => [ qw/eg-web-common eg-web-search ensembl-metadata ensembl-taxonomy eg-web-fungi/ ],
      },
      'eg-metazoa'  => {
          desc    => 'Libraries required for Ensembl Genomes Metazoa',
          modules => [ qw/eg-web-common eg-web-search ensembl-metadata ensembl-taxonomy eg-web-metazoa/ ],
      },
      'eg-plants'   => {
          desc    => 'Libraries required for Ensembl Genomes Plants',
          modules => [ qw/eg-web-common eg-web-search ensembl-metadata ensembl-taxonomy eg-web-plants/ ],
      },
      'eg-protists' => {
          desc    => 'Libraries required for Ensembl Genomes Protists',
          modules => [ qw/eg-web-common eg-web-search ensembl-metadata ensembl-taxonomy eg-web-protists/ ],
      },
      'eg-g1k'      => {
          desc    => 'Libraries required for 1000 Genomes',
          modules => [ qw/eg-web-common eg-web-search ensemblgenomes-api eg-web-g1k/ ],
      },
      'eg-all'      => {
          desc    => 'All Ensembl Genomes web libraries',
          modules => [ qw/eg-web-common eg-web-search eg-web-testsuite eg-web-bacteria eg-web-fungi eg-web-metazoa eg-web-plants eg-web-protists/ ],
      },
      'eg-parasite' => {
          desc    => 'Libraries required for WormBase ParaSite (Ensembl Genomes Project)',
          modules => [ qw/eg-web-common eg-web-search ensemblgenomes-api eg-web-testsuite eg-web-parasite/ ],
      },
      'regulation'  => {
          desc    => 'Libraries required for Ensembl Regulation',
          modules => [ qw/ensembl ensembl-compara ensembl-variation ensembl-funcgen ensembl-io ensj-healthcheck ensembl-hive ensembl-production ensembl-rest ensembl-test VEP_plugins ensembl-vep ensembl-hdf5 ensembl-datacheck/ ],
      },
  };
}

__END__
=pod

=head1 NAME

git-ensembl - Ensembl-centric Git Utility

=head1 SYNOPSIS

git ensembl [--clone [--name NAME] [--email EMAIL] [--depth DEPTH] [--branch BRANCH] ] 
            [--checkout --branch BRANCH --secondary_branch BRANCH] 
            [--pull] [--fetch] [--remote ORIGIN] [--dir DIR] [--groups] [--list] [--rebase]
            [--config CFG_LOCATION]
            [--hooks enable/disable]
            [--force_group] [--force_module]
            [--cmd 'CMD']
            [-v] [-h] [-m] GROUPS_OR_MODULES

# List all available groups

git ensembl --list

# Clone all the API modules

git ensembl --clone api

# Clone and set a new Git username and email

git ensembl --clone --user 'Name' --email 'email@somewhere.com' api

# Clone a set of shallow repositories. Shallow repos cannot be used for development

git ensembl --clone --shallow --depth 1 api

# Clone using SSH

git ensembl --clone --ssh api

# Pull in new changes (merges into each repo's current branch)

git ensembl --pull api

# Pull in new changes on the given branch

git ensembl --pull --branch master api

# Switch to a branch in all repos or create a new remote tracking branch

git ensembl --checkout --branch global/branch api

# Clone the api group and the ensembl-test repository

git ensembl --clone api ensembl-test

=head1 DESCRIPTION

Provides a number of convienience actions used to clone, switch branches & pull in new changes from remote Ensembl repositories. It also provides a way of addressing multiple repositories by a group name and applying the previously mentioned functions over those groups.

All repositories are cloned using HTTPS as since Git version 1.7.10 the HTTPS protocol has been significantly changed to be as efficient as the SSH and Git protocols.

B<If you are making a read/write clone make sure you specify your username via the --user command line argument. Otherwise Git will use your default authentication details.>

=head1 SCRIPT CONFIGURATION

This script ships with a number of default configurations. You can add to these if they are in the default global location C<~ensembl/git-ensembl.cfg> or your home directory at C<~/git-ensembl.cfg>. The format is a permissive JSON file (accepting comments and tollerant to formatting/trailing punctuation) like so:

    {
      "modules" : {
        "module" : "https://github.com/module.git"
      },
      "groups" : {
        "groupname" : {
          "desc" : "Group description",
          "modules" : ["module"]
        }
      }
    }

For example:

    {
      "modules" : {
        "module" : "https://github.com/module.git"
      },
      "groups" : {
        "coretests" : {
          "desc" : "All the modules you need to get core modules working",
          "modules" : ["ensembl", "ensembl-test"]
        }
      }
    }

Configuration is resolved in the following order

=over 8

=item * Command line configuration

=item * User configuration

=item * Central configuration

=item * Default configuration

=back

When a clashing group is named the higher priority group takes precedence.

=head1 ACTIONS

The script can perform the following actions

=over 8

=item B<list>

Show available groups and the repos it will work with

=item B<clone>

Clone a set of repos. B<--shallow> is available to control the depth of clones

=item B<fetch>

Fetch a new set of changes from origin (GitHub)

=item B<checkout>

Switch branch in all repositories (will fetch from origin first and will create tracking branches where applicable)

=item B<pull>

Fetch remote changes and merge them into the current branch

=item B<cmd>

Apply a generic git command across multiple repositories.

=back

=head1 OPTIONS

=over 8

=item B<GROUPS>

The groups to perform actions on. Use B<--list> to find all available groups

=item B<--clone>

Clone the repositories linked to the given group (Ensembl's remote hosted on GitHub)

=item<B--depth>

Clone the repository with the specified depth. By default this is not on

=item B<--ssh>

Use the SSH protocol for cloning rather than HTTPS. HTTPS is the default clone protocol

=item B<--name>

Set the config variable B<user.name> for this clone.

=item B<--email>

Set the config variable B<user.email> for this clone.

=item B<--checkout>

Checkout the remote branch specified in the B<--branch> command

=item B<--local_checkout>

Checkout the branch specified in the B<--branch> command. Works on any branch so long as it exists. If it does not then the branch will be created

=item B<--branch>

Branch to switch to. Used in conjunction with the B<--checkout>, B<--clone> (with B<--depth>) and B<--pull> commands

=item B<--secondary_branch>

Branch to switch to if the original branch given in C<--branch> was not found. Used in conjunction with the B<--clone>, B<--checkout>, and B<--pull> commands

=item B<--rebase>

When performing PULL requests rebase your local changes onto the remote tracking branch. Do not merge.

=item B<--status>

Show the repositories status

=item B<--fetch>

Fetch changes from origin and also fetch any tags

=item B<--cmd>

When specified we expect the value to be a git command to apply to each and every repository/group specifed. For example if you wanted to list the current hash on all API modules you can use

  git ensembl --cmd 'rev-parse HEAD' api

=item B<--pull>

Perform a pull from origin (Ensembl's remote hosted on GitHub) and all tags. When used in conjunction with B<--branch> it will fetch data from the remote, switch to the tracking branch and then issue the pull request.

=item B<--remote>

Name of the remote. Defaults to origin.

=item B<--dir>

Perform all commands in the given directory

=item B<--config>

Supply a configuration JSON file and use this as the highest level of configuration.

=item B<--force_group>

Force the interpritation of all additional arguments as groups not as modules

=item B<--force_module>

Force the interpritation of all additional arguments as modules not as groups

=item B<--ignore_module>

Ignores a given module when processing an action on a group, may be added multiple times to ignore multiple modules

=item B<--list>

List all available groups

=item B<--hooks>

Enables or disables the available hooks

=item B<--verbose>

Make commands more verbose

=item B<--help>

Print the help information

=item B<--man>

Print a man page

=back

=cut
