change information messages
[lab.git] / Dev / github / GitHubBackup.pm
index 18aabc7..781192b 100644 (file)
@@ -1,4 +1,4 @@
-package utils;
+package GitHubBackup::API;
 use strict;
 use warnings;
 use utf8;
@@ -6,11 +6,20 @@ use Carp qw(croak);
 
 use LWP::UserAgent;
 use JSON;
-my $ua = LWP::UserAgent->new;
-my $json = JSON->new->utf8->indent;
+
+sub new {
+    my $class = shift;
+    my $args  = shift;
+    
+    return bless $args, $class;
+}
 
 sub json_api {
-    my $url = shift;
+    my $self = shift;
+    my $url  = shift;
+    
+    my $ua = LWP::UserAgent->new;
+    my $json = JSON->new->utf8->indent;
     
     my $res = $ua->get(
         "https://api.github.com$url"
@@ -21,6 +30,41 @@ sub json_api {
     return $json->decode($res->content);
 }
 
+sub get {
+    my $self = shift;
+    my $url  = shift;
+    my %parameters = @_;
+    
+    if ($self->access_token) {
+        $parameters{access_token} = $self->access_token;
+    }
+    my $parameters = '';
+    while (my($key, $value) = each %parameters) {
+        $parameters .= "&$key=$value";
+    }
+    
+    my $page = 1;
+    my $data = [];
+    while(1) {
+        my $result = $self->json_api("$url?per_page=100&page=$page$parameters");
+        if (ref($result) eq 'ARRAY' && scalar @$result > 0) {
+            push @$data, @$result;
+            $page++;
+            
+            next;
+        }
+        last;
+    }
+    
+    return $data;
+}
+
+sub access_token {
+    my $self = shift;
+    
+    return $self->{access_token}->();
+}
+
 
 package GitHubBackup;
 
@@ -74,41 +118,51 @@ sub directory {
     return $self->{directory};
 }
 
+sub access_token {
+    my $self = shift;
+    my $args = shift;
+    
+    if (defined $args) {
+        $self->{access_token} = $args;
+    }
+    
+    return $self->{access_token};
+}
+
+sub api {
+    my $self = shift;
+    
+    unless ($self->{api}) {
+        $self->{api} = GitHubBackup::API->new({
+            access_token => sub {$self->access_token},
+        });
+    }
+    
+    return $self->{api};
+}
+
 sub repos {
     my $self = shift;
     return $self->{repos} if ($self->{repos});
     
+    $self->{repos} = [];
+    
     my $account = $self->account or croak "account is not set";
+    my $token = ($self->access_token) ? "?access_token=" . $self->access_token : '';
+    my $result;
     if (my $repository = $self->repository) {
-        $self->{repos} = [
-            GitHubBackup::Repository->new({
-                directory => sub {$self->directory},
-                full_name => "$account/$repository",
-            })
-        ];
-        
-        return $self->{repos};
+        $result = [ $self->api->json_api("/repos/$account/$repository$token") ];
     }
-    
-    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;
+    else {
+        $result = $self->api->get("/users/$account/repos");
     }
     
-    foreach my $repos (@repos) {
+    foreach my $repos (@$result) {
         push @{$self->{repos}},
             GitHubBackup::Repository->new({
                 directory => sub {$self->directory},
-                full_name => $repos->{full_name},
-                clone_url => $repos->{clone_url},
+                api       => sub {$self->api},
+                repos     => $repos,
             })
         ;
     }
@@ -137,23 +191,45 @@ use Git::Repository;
 use File::chdir;
 use File::Spec;
 use File::Path qw(mkpath);
+use LWP::UserAgent;
+use JSON;
+
 
 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 clone_url {
+    return (shift)->{repos}{clone_url};
+}
+
+sub full_name {
+    return (shift)->{repos}{full_name};
+}
+
+sub has_downloads {
+    return (shift)->{repos}{has_downloads};
+}
+
+sub forks_count {
+    return (shift)->{repos}{forks_count};
+}
+
+sub has_wiki {
+    return (shift)->{repos}{has_wiki};
+}
+
+sub has_issues {
+    return (shift)->{repos}{has_issues};
+}
+
 sub directory {
     my $self = shift;
     
-    my $path = $self->{full_name};
+    my $path = $self->full_name;
     if (my $base = $self->{directory}->()) {
         $path = File::Spec->catfile($base, $path);
     }
@@ -161,6 +237,21 @@ sub directory {
     return $path;
 }
 
+sub api {
+    my $self = shift;
+    
+    return $self->{api}->();
+}
+
+sub message {
+    my $self = shift;
+    my $message  = shift;
+    
+    print $self->full_name, " $message\n";
+    
+    return $self;
+}
+
 sub sync {
     my $self = shift;
     my $url = shift;
@@ -168,12 +259,12 @@ sub sync {
     
     if (-d "$dir") {
         local $CWD = $dir;
-        print "fetch ", $dir, "\n";
+        $self->message("fetch --all $dir");
         Git::Repository->run(fetch => '--all');
         return $self;
     }
     
-    print "clone ", $dir, "\n";
+    $self->message("clone --mirror $dir");
     mkpath $dir;
     Git::Repository->run(clone => '--mirror' => $url => $dir);
     
@@ -184,54 +275,42 @@ sub clone_git {
     my $self = shift;
     
     my $dir = $self->directory . '.git';
-    my $url = $self->{clone_url};
+    my $url = $self->clone_url;
     
     $self->sync($url => $dir);
     
     return $self;
 }
 
-sub get_forks {
+sub 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;
-    }
+    $self->{forks} = $self->api->get("/repos/" . $self->full_name . "/forks");
     
-    return $self;
+    return $self->{forks};
 }
 
 sub set_forks {
     my $self = shift;
     
-    $self->get_forks;
-    
     my $dir = $self->directory . '.git';
     local $CWD = $dir;
     
     my $remotes = Git::Repository->run(branch => '--remotes');
     my @fetch;
-    foreach my $fork (@{$self->{forks}}) {
+    foreach my $fork (@{$self->forks}) {
         if ($remotes =~ /$fork->{full_name}/) {
-            print "skip ", $fork->{full_name}, "\n";
+            $self->message("have ". $fork->{full_name});
             next;
         }
-        print "add ", $fork->{full_name}, "\n";
+        $self->message("remote add ". $fork->{full_name});
         Git::Repository->run(remote => add => $fork->{full_name} => $fork->{clone_url});
         push @fetch, $fork->{full_name};
     }
     
     foreach my $fork (@fetch) {
-        print "fetch ", $fork, "\n";
+        $self->message("fetch $fork");
         Git::Repository->run(fetch => $fork);
     }
     
@@ -242,24 +321,67 @@ sub clone_wiki {
     my $self = shift;
     
     my $dir = $self->directory . '.wiki.git';
-    my $url = 'https://github.com/' . $self->{full_name} . '.wiki.git';
+    my $url = 'https://github.com/' . $self->full_name . '.wiki.git';
+    
+    my $ua = LWP::UserAgent->new(max_redirect => 0);
+    my $res = $ua->head(
+        'https://github.com/' . $self->full_name . '/wiki'
+    );
+    if ($res->code != 200) {
+        $self->message("wiki does not exist");
+        return $self;
+    }
     
     $self->sync($url => $dir);
     
     return $self;
 }
 
+sub issues {
+    my $self = shift;
+    return $self->{issues} if ($self->{issues});
+    
+    my $open   = $self->api->get("/repos/" . $self->full_name . "/issues");
+    my $closed = $self->api->get("/repos/" . $self->full_name . "/issues", state => 'closed');
+    
+    if ($open)   { push @{$self->{issues}}, @$open }
+    if ($closed) { push @{$self->{issues}}, @$closed }
+    
+    return $self->{issues};
+}
+
 sub save_issues {
     my $self = shift;
+    
+    my $ua = LWP::UserAgent->new;
+    my $json = JSON->new->utf8->indent;
+    
+    my $dir = $self->directory . '.issues';
+    mkpath $dir unless (-d $dir);
+    local $CWD = $dir;
+    foreach my $issue (@{$self->issues}) {
+        my $number = $issue->{number};
+        $self->message("save issue/$number");
+        
+        open my $fh, ">$number.json";
+        print $fh $json->encode($issue);
+        close $fh;
+        
+        if (exists $issue->{pull_request}{patch_url}) {
+            $ua->mirror($issue->{pull_request}{patch_url} => "$number.patch");
+        }
+    }
+    
+    return $self;
 }
 
 sub backup {
     my $self = shift;
     
-    $self->clone_git;
-    $self->set_forks;
-    $self->clone_wiki;
-    $self->save_issues;
+    $self->clone_git   if ($self->has_downloads eq 'true');
+    $self->set_forks   if ($self->forks_count > 0);
+    $self->clone_wiki  if ($self->has_wiki eq 'true');
+    $self->save_issues if ($self->has_issues eq 'true');
     
     return $self;
 }