package utils; use strict; use warnings; use utf8; use Carp qw(croak); use LWP::UserAgent; use JSON; my $ua = LWP::UserAgent->new; my $json = JSON->new->utf8->indent; sub json_api { my $url = shift; my $res = $ua->get( "https://api.github.com$url" ); $res->is_success or croak $res->status_line; return $json->decode($res->content); } package GitHubBackup; use strict; use warnings; use utf8; use Carp qw(croak); use File::Spec; # both hash and hashref are acceptable sub new { my $class = shift; my $args = (ref $_[0] eq 'HASH') ? $_[0] : {@_}; return bless $args, $class; } sub account { my $self = shift; my $args = shift; if (defined $args) { $self->{repos} = undef; $self->{account} = $args; } return $self->{account}; } sub repository { my $self = shift; my $args = shift; if (defined $args) { $self->{repos} = undef; $self->{repository} = $args; } return $self->{repository}; } sub directory { my $self = shift; my $args = shift; if (defined $args) { $self->{directory} = File::Spec->rel2abs($args); } return $self->{directory}; } sub repos { my $self = shift; return $self->{repos} if ($self->{repos}); my $account = $self->account or croak "account is not set"; if (my $repository = $self->repository) { $self->{repos} = [ GitHubBackup::Repository->new({ directory => sub {$self->directory}, full_name => "$account/$repository", }) ]; return $self->{repos}; } my $page = 1; my @repos; while (1) { my $result = utils::json_api("/users/$account/repos?per_page=100&page=$page"); if (ref($result) eq 'ARRAY' && scalar @$result > 0) { push @repos, @$result; $page++; next; } last; } foreach my $repos (@repos) { push @{$self->{repos}}, GitHubBackup::Repository->new({ directory => sub {$self->directory}, full_name => $repos->{full_name}, clone_url => $repos->{clone_url}, }) ; } return $self->{repos}; } sub backup { my $self = shift; foreach my $repos (@{$self->repos}) { $repos->backup; } return $self; } package GitHubBackup::Repository; use strict; use warnings; use utf8; use Carp qw(croak); use Git::Repository; use File::chdir; use File::Spec; use File::Path qw(mkpath); sub new { my $class = shift; my $args = shift; if (! exists $args->{clone_url}) { my $result = utils::json_api('/repos/' . $args->{full_name}); $args->{clone_url} = $result->{clone_url}; } return bless $args, $class; } sub directory { my $self = shift; my $path = $self->{full_name}; if (my $base = $self->{directory}->()) { $path = File::Spec->catfile($base, $path); } return $path; } sub clone_git { my $self = shift; my $dir = $self->directory . '.git'; if (-d "$dir") { local $CWD = $dir; print "fetch ", $dir, "\n"; Git::Repository->run(fetch => '--all'); return $self; } print "clone ", $dir, "\n"; mkpath $dir; Git::Repository->run(clone => '--mirror' => $self->{clone_url} => $dir); return $self; } sub get_forks { my $self = shift; return $self->{forks} if ($self->{forks}); my $page = 1; while (1) { my $result = utils::json_api("/repos/" . $self->{full_name} . "/forks?per_page=100&page=$page"); if (ref($result) eq 'ARRAY' && scalar @$result > 0) { push @{$self->{forks}}, @$result; $page++; next; } last; } return $self; } sub set_forks { my $self = shift; $self->get_forks; my $dir = $self->directory . '.git'; local $CWD = $dir; my $remotes = Git::Repository->run(branch => '--remotes'); foreach my $fork (@{$self->{forks}}) { if ($remotes =~ /$fork->{full_name}/) { print "skip ", $fork->{full_name}, "\n"; next; } print "add ", $fork->{full_name}, "\n"; Git::Repository->run(remote => add => $fork->{full_name} => $fork->{clone_url}); } print "fetch ", $dir, "\n"; Git::Repository->run(fetch => '--all'); return $self; } sub clone_wiki { my $self = shift; } sub save_issues { my $self = shift; } sub backup { my $self = shift; $self->clone_git; $self->set_forks; $self->clone_wiki; $self->save_issues; return $self; } 1; __END__