source: lab.git/Dev/github/GitHubBackup.pm @ f7f894d

Last change on this file since f7f894d was f7f894d, checked in by Ken-ichi Mito <mitty@…>, 11 years ago

change messages, add sample script

  • Property mode set to 100644
File size: 6.7 KB
RevLine 
[6a2e5f1]1package utils;
2use strict;
3use warnings;
4use utf8;
5use Carp qw(croak);
6
7use LWP::UserAgent;
8use JSON;
9
10sub json_api {
11    my $url = shift;
12   
[49aca65]13    my $ua = LWP::UserAgent->new;
14    my $json = JSON->new->utf8->indent;
15   
[6a2e5f1]16    my $res = $ua->get(
17        "https://api.github.com$url"
18    );
19   
20    $res->is_success or croak $res->status_line;
21   
22    return $json->decode($res->content);
23}
24
[fc647d5]25sub get {
26    my $url = shift;
[55d0513]27    my %parameters = @_;
28   
29    my $parameters = '';
30    while (my($key, $value) = each %parameters) {
31        $parameters .= "&$key=$value";
32    }
[fc647d5]33   
34    my $page = 1;
[d51047d]35    my $data = [];
[fc647d5]36    while(1) {
[55d0513]37        my $result = json_api("$url?per_page=100&page=$page$parameters");
[fc647d5]38        if (ref($result) eq 'ARRAY' && scalar @$result > 0) {
39            push @$data, @$result;
40            $page++;
41           
42            next;
43        }
44        last;
45    }
46   
47    return $data;
48}
[19233f5]49
[6a2e5f1]50package GitHubBackup;
51
52use strict;
53use warnings;
54use utf8;
55use Carp qw(croak);
[6f31bfb]56use File::Spec;
[6a2e5f1]57
58
59# both hash and hashref are acceptable
60sub new {
61    my $class = shift;
62    my $args = (ref $_[0] eq 'HASH') ? $_[0] : {@_};
[19233f5]63   
[6f31bfb]64    return bless $args, $class;
[6a2e5f1]65}
66
[19df400]67sub account {
68    my $self = shift;
69    my $args = shift;
70   
71    if (defined $args) {
72        $self->{repos} = undef;
73        $self->{account} = $args;
74    }
75   
76    return $self->{account};
77}
78
79sub repository {
80    my $self = shift;
81    my $args = shift;
82   
83    if (defined $args) {
84        $self->{repos} = undef;
85        $self->{repository} = $args;
86    }
87   
88    return $self->{repository};
89}
90
[6f31bfb]91sub directory {
92    my $self = shift;
93    my $args = shift;
94   
95    if (defined $args) {
96        $self->{directory} = File::Spec->rel2abs($args);
97    }
98   
99    return $self->{directory};
100}
101
[6a2e5f1]102sub repos {
103    my $self = shift;
[19233f5]104    return $self->{repos} if ($self->{repos});
[6a2e5f1]105   
[d51047d]106    $self->{repos} = [];
107   
[6a2e5f1]108    my $account = $self->account or croak "account is not set";
[c5b427c]109    my $result;
[6a2e5f1]110    if (my $repository = $self->repository) {
[c5b427c]111        $result = [ utils::json_api("/repos/$account/$repository") ];
112    }
113    else {
114        $result = utils::get("/users/$account/repos");
[6a2e5f1]115    }
116   
[fc647d5]117    foreach my $repos (@$result) {
[6a2e5f1]118        push @{$self->{repos}},
119            GitHubBackup::Repository->new({
[b8d7413]120                directory => sub {$self->directory},
[c5b427c]121                repos     => $repos,
[6a2e5f1]122            })
123        ;
124    }
125   
126    return $self->{repos};
127}
128
[19df400]129sub backup {
130    my $self = shift;
131   
132    foreach my $repos (@{$self->repos}) {
133        $repos->backup;
134    }
135   
136    return $self;
137}
138
[6a2e5f1]139
140package GitHubBackup::Repository;
141
142use strict;
143use warnings;
144use utf8;
145use Carp qw(croak);
[19233f5]146use Git::Repository;
147use File::chdir;
[6f31bfb]148use File::Spec;
149use File::Path qw(mkpath);
[49aca65]150use LWP::UserAgent;
151use JSON;
152
[b8d7413]153
[19233f5]154sub new {
155    my $class = shift;
156    my $args  = shift;
157   
[6f31bfb]158    return bless $args, $class;
[19233f5]159}
160
[c5b427c]161sub clone_url {
162    return (shift)->{repos}{clone_url};
163}
164
165sub full_name {
166    return (shift)->{repos}{full_name};
167}
168
[f38b66d]169sub has_downloads {
170    return (shift)->{repos}{has_downloads};
171}
172
173sub forks_count {
174    return (shift)->{repos}{forks_count};
175}
176
177sub has_wiki {
178    return (shift)->{repos}{has_wiki};
179}
180
181sub has_issues {
182    return (shift)->{repos}{has_issues};
183}
184
[b8d7413]185sub directory {
186    my $self = shift;
187   
[c5b427c]188    my $path = $self->full_name;
[6f31bfb]189    if (my $base = $self->{directory}->()) {
190        $path = File::Spec->catfile($base, $path);
191    }
192   
193    return $path;
[b8d7413]194}
195
[f7f894d]196sub message {
197    my $self = shift;
198    my $message  = shift;
199   
200    print $self->full_name, " $message\n";
201   
202    return $self;
203}
204
[6473c3b]205sub sync {
[19233f5]206    my $self = shift;
[6473c3b]207    my $url = shift;
208    my $dir = shift;
[19233f5]209   
210    if (-d "$dir") {
211        local $CWD = $dir;
[f7f894d]212        $self->message("++> $dir");
[19233f5]213        Git::Repository->run(fetch => '--all');
214        return $self;
215    }
216   
[f7f894d]217    $self->message("==> $dir");
[6f31bfb]218    mkpath $dir;
[6473c3b]219    Git::Repository->run(clone => '--mirror' => $url => $dir);
220   
221    return $self;
222}
223
224sub clone_git {
225    my $self = shift;
226   
227    my $dir = $self->directory . '.git';
[c5b427c]228    my $url = $self->clone_url;
[6473c3b]229   
230    $self->sync($url => $dir);
231   
[19233f5]232    return $self;
233}
234
[55d0513]235sub forks {
[682d2d9]236    my $self = shift;
237    return $self->{forks} if ($self->{forks});
238   
[c5b427c]239    $self->{forks} = utils::get("/repos/" . $self->full_name . "/forks");
[682d2d9]240   
[55d0513]241    return $self->{forks};
[682d2d9]242}
243
[19233f5]244sub set_forks {
245    my $self = shift;
[682d2d9]246   
247    my $dir = $self->directory . '.git';
248    local $CWD = $dir;
249   
250    my $remotes = Git::Repository->run(branch => '--remotes');
[6473c3b]251    my @fetch;
[55d0513]252    foreach my $fork (@{$self->forks}) {
[682d2d9]253        if ($remotes =~ /$fork->{full_name}/) {
[f7f894d]254            $self->message("--- ". $fork->{full_name});
[682d2d9]255            next;
256        }
[f7f894d]257        $self->message("+++ ". $fork->{full_name});
[682d2d9]258        Git::Repository->run(remote => add => $fork->{full_name} => $fork->{clone_url});
[6473c3b]259        push @fetch, $fork->{full_name};
[682d2d9]260    }
261   
[6473c3b]262    foreach my $fork (@fetch) {
[f7f894d]263        $self->message("--> $fork");
[6473c3b]264        Git::Repository->run(fetch => $fork);
265    }
[682d2d9]266   
267    return $self;
[19233f5]268}
269
270sub clone_wiki {
271    my $self = shift;
[6473c3b]272   
273    my $dir = $self->directory . '.wiki.git';
[c5b427c]274    my $url = 'https://github.com/' . $self->full_name . '.wiki.git';
[6473c3b]275   
[9353238]276    my $ua = LWP::UserAgent->new(max_redirect => 0);
277    my $res = $ua->head(
278        'https://github.com/' . $self->full_name . '/wiki'
279    );
280    if ($res->code != 200) {
[f7f894d]281        $self->message("wiki does not exist");
[9353238]282        return $self;
283    }
284   
[6473c3b]285    $self->sync($url => $dir);
286   
287    return $self;
[19233f5]288}
289
[55d0513]290sub issues {
291    my $self = shift;
292    return $self->{issues} if ($self->{issues});
293   
[c5b427c]294    my $open   = utils::get("/repos/" . $self->full_name . "/issues");
295    my $closed = utils::get("/repos/" . $self->full_name . "/issues", state => 'closed');
[55d0513]296   
297    if ($open)   { push @{$self->{issues}}, @$open }
298    if ($closed) { push @{$self->{issues}}, @$closed }
299   
300    return $self->{issues};
301}
302
[19233f5]303sub save_issues {
304    my $self = shift;
[55d0513]305   
[49aca65]306    my $ua = LWP::UserAgent->new;
307    my $json = JSON->new->utf8->indent;
308   
309    my $dir = $self->directory . '.issues';
310    mkpath $dir unless (-d $dir);
311    local $CWD = $dir;
312    foreach my $issue (@{$self->issues}) {
313        my $number = $issue->{number};
[f7f894d]314        $self->message("+++ issue/$number");
[49aca65]315       
316        open my $fh, ">$number.json";
317        print $fh $json->encode($issue);
318        close $fh;
319       
320        if (exists $issue->{pull_request}{patch_url}) {
321            $ua->mirror($issue->{pull_request}{patch_url} => "$number.patch");
322        }
323    }
324   
325    return $self;
[19233f5]326}
[6a2e5f1]327
[19df400]328sub backup {
329    my $self = shift;
330   
[f38b66d]331    $self->clone_git   if ($self->has_downloads eq 'true');
332    $self->set_forks   if ($self->forks_count > 0);
333    $self->clone_wiki  if ($self->has_wiki eq 'true');
334    $self->save_issues if ($self->has_issues eq 'true');
[19df400]335   
336    return $self;
337}
[6a2e5f1]338
339
3401;
341__END__
Note: See TracBrowser for help on using the repository browser.