package Github::Backup;

use strict;
use warnings;

use Carp qw(croak);
use Data::Dumper;
use Git::Repository;
use File::Copy;
use File::Path;
use JSON;
use LWP::UserAgent;
use Moo;
use Pithub;

use namespace::clean;

our $VERSION = '1.02';

# external

has api_user => (
    is => 'rw',
has _clean => (
    # used to clean up test backup directories
    is => 'rw',
has dir => (
    is => 'rw',
has forks => (
    is => 'rw',
has token => (
    is => 'rw',
has proxy => (
    is => 'rw',
has user => (
    is => 'rw',

# internal

has gh => (
    # Pithub object
    is => 'rw',
has stg => (
    # staging dir
    is => 'rw',

sub BUILD {
    my ($self) = @_;

    if (! $self->token){
        $self->token($ENV{GITHUB_TOKEN}) if $ENV{GITHUB_TOKEN};

    for ($self->api_user, $self->token, $self->dir){
        if (! $_){
            croak "When instantiating an object, the 'api_user', 'token' and " .
                  "'dir' parameters are mandatory...\n";

    my $ua = LWP::UserAgent->new;

    if ($self->proxy){
        $ENV{http_proxy} = $self->proxy;
        $ENV{https_proxy} = $self->proxy;


    my $gh = Pithub->new(
        ua => $ua,
        user => $self->api_user,
        token => $self->token

    $self->stg($self->dir . '.stg');

    $self->user($self->api_user) if ! defined $self->user;

    if (-d $self->stg){
        rmtree $self->stg or die "can't remove the old staging directory...$!";

    mkdir $self->stg or die "can't create the backup staging directory...$!\n";

sub repos {
    my ($self) = @_;

    my $repo_list = $self->gh->repos->list(user => $self->user);

    my @repos;

    while (my $repo = $repo_list->next){
        push @repos, $repo;

    for my $repo (@repos){

        my $stg = $self->stg . "/$repo->{name}";

        if (! $self->forks){
            if (! exists $repo->{parent}){
                    clone => $repo->{clone_url} => $stg,
                    {quiet => 1}
        else {
                clone => $repo->{clone_url} => $stg,
                { quiet => 1 }
sub issues {
    my ($self) = @_;

    mkdir $self->stg . "/issues" or die "can't create the 'issues' dir: $!";

    my $repo_list = $self->gh->repos->list(user => $self->user);

    while (my $repo = $repo_list->next){
        my $issue_list = $self->gh->issues->list(
            user => $self->user,
            repo => $repo->{name}

        my $issue_dir = $self->stg . "/issues/$repo->{name}";

        my $dir_created = 0;

        while (my $issue = $issue_list->next){
            if (! $dir_created) {
                mkdir $issue_dir or die $!;
                $dir_created = 1;
            open my $fh, '>', "$issue_dir/$issue->{id}"
                or die "can't create the issue file";

            print $fh encode_json $issue;
    my $self = shift;

    if ($self->dir && -d $self->dir) {
        rmtree $self->dir or die "can't remove the old backup directory: $!";

    if ($self->stg && -d $self->stg) {
        move $self->stg,
            $self->dir or die "can't rename the staging directory: $!";

    if ($self->dir && -d $self->dir && $self->_clean) {
        # we're in testing mode, clean everything up
        rmtree $self->dir
            or die "can't remove the test backup directory...$!";


=head1 NAME

Github::Backup - Back up your Github repositories and/or issues locally


    github_backup \
        --user stevieb9 \
        --token 003e12e0780025889f8da286d89d144323c20c1ff7 \
        --dir /home/steve/github_backup \
        --repos \

    # You can store the token in an environment variable as opposed to sending
    # it on the command line

    export GITHUB_TOKEN=003e12e0780025889f8da286d89d144323c20c1ff7

    github_backup -u stevieb9 -d ~/github_backup -r


The cloud is a wonderful thing, but things do happen. Use this distribution to
back up all of your Github repositories and/or issues to your local machine for
both assurance of data accessibility due to outage, data loss, or just simply
off-line use.


=head2 -u | --user

Mandatory: Your Github username.

=head2 -t | --token

Mandatory: Your Github API token. If you wish to not include this on the
command line, you can put the token into the C<GITHUB_TOKEN> environment

=head2 -d | --dir

Mandatory: The backup directory where your repositories and/or issues will be
stored. The format of the directory structure will be as follows:

        - issues/
            - repo1/
                - issue_id_x
                - issue_id_y
            - repo2/
                - issue_id_a
        - repo1/
            - repository data
        - repo2/
            - repository data

The repositories are stored as found on Github. The issues are stored in JSON

=head2 -r | --repos

Optional: Back up all of your repositories found on Github.

Note that either C<--repos> or C<--issues> must be sent in.

=head2 -i | --issues

Optional: Back up all of your issues across all of your Github repositories.

Note that either C<--issues> or C<--repos> must be sent in.

=head2 -p | --proxy

Optional: Send in a proxy in the format C<> and
we'll use this to do our fetching.

=head2 -h | --help

Display the usage information page.


=head2 new

Instantiates and returns a new L<Github::Backup> object.


=head3 api_user

Mandatory, String: Your Github username.

=head3 token

Mandatory, String: Your Github API token. Note that if you do not wish to store
this in code, you can put it into the C<GITHUB_TOKEN> environment variable,
and we'll read it in from there instead.

=head3 dir

Mandatory, String: The directory that you wish to store your downloaded Github
information to.

=head3 proxy

Optional, String: Send in a proxy in the format
C<> and we'll use this to do our fetching.

=head2 repos

Takes no parameters. Backs up all of your Github repositories, and stores them
in the specified backup directory.

=head2 issues

Takes no parameters. Backs up all of your Github issues. Stores them per-repo
within the C</backup_dir/issues> directory.


- Slowly, I will add new functionality such as backing up *all* Github data, as
well as provide the ability to restore to Github the various items.

- Add more tests. Usually I don't release a distribution with such few tests,
but in this case I have. I digress.

=head1 AUTHOR

Steve Bertrand, C<< <steveb at> >>


Copyright 2017,2018 Steve Bertrand.

This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.

See L<> for more information.