/
Backup.pm
317 lines (226 loc) · 6.99 KB
/
Backup.pm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
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.03';
# 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 my $key (qw/api_user token dir/){
if (! $self->{$key}){
croak "ERROR: Missing mandatory parameter [$key].\n";
}
}
my $ua = LWP::UserAgent->new;
if ($self->proxy){
$ENV{http_proxy} = $self->proxy;
$ENV{https_proxy} = $self->proxy;
$ua->env_proxy;
}
my $gh = Pithub->new(
ua => $ua,
user => $self->api_user,
token => $self->token
);
$self->stg($self->dir . '.stg');
$self->gh($gh);
$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}){
Git::Repository->run(
clone => $repo->{clone_url} => $stg,
{quiet => 1}
);
}
}
else {
Git::Repository->run(
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;
}
}
}
sub DESTROY {
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...$!";
}
}
1;
__END__
=head1 NAME
Github::Backup - Back up your Github repositories and/or issues locally
=head1 SYNOPSIS
github_backup \
--user stevieb9 \
--token 003e12e0780025889f8da286d89d144323c20c1ff7 \
--dir /home/steve/github_backup \
--repos \
--issues
# 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
=head1 DESCRIPTION
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.
=head1 COMMAND LINE USAGE
=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
variable.
=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:
backup_dir/
- 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
format.
=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<https://proxy.example.com:PORT> and
we'll use this to do our fetching.
=head2 -h | --help
Display the usage information page.
=head1 MODULE METHODS
=head2 new
Instantiates and returns a new L<Github::Backup> object.
Parameters:
=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<https://proxy.example.com:PORT> 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.
=head1 FUTURE DIRECTION
- 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 cpan.org> >>
=head1 LICENSE AND COPYRIGHT
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<http://dev.perl.org/licenses/> for more information.