File Coverage

blib/lib/App/cpangitify.pm
Criterion Covered Total %
statement 165 189 87.3
branch 33 54 61.1
condition 4 11 36.3
subroutine 25 28 89.2
pod 0 2 0.0
total 227 284 79.9


line stmt bran cond sub pod time code
1             package App::cpangitify;
2              
3 3     3   180841 use strict;
  3         10  
  3         105  
4 3     3   20 use warnings;
  3         7  
  3         114  
5 3     3   1654 use autodie qw( :system );
  3         45240  
  3         19  
6 3     3   53765 use 5.010001;
  3         21  
7 3     3   2366 use Getopt::Long qw( GetOptions );
  3         31486  
  3         17  
8 3     3   2451 use Pod::Usage qw( pod2usage );
  3         133285  
  3         287  
9 3     3   1399 use Path::Class qw( file dir );
  3         62075  
  3         228  
10 3     3   1723 use Git::Wrapper;
  3         36741  
  3         105  
11 3     3   28 use File::Temp qw( tempdir );
  3         21  
  3         172  
12 3     3   23 use File::chdir;
  3         4  
  3         249  
13 3     3   1966 use JSON::PP qw( decode_json );
  3         29823  
  3         237  
14 3     3   1630 use URI;
  3         11671  
  3         100  
15 3     3   1227 use PerlX::Maybe qw( maybe );
  3         3991  
  3         141  
16 3     3   1272 use File::Copy::Recursive qw( rcopy );
  3         13859  
  3         214  
17 3     3   28 use File::Basename qw( basename );
  3         6  
  3         185  
18 3     3   1675 use Archive::Extract;
  3         417462  
  3         111  
19 3     3   25 use File::Spec;
  3         6  
  3         49  
20 3     3   1283 use CPAN::ReleaseHistory;
  3         233205  
  3         131  
21 3     3   28 use HTTP::Tiny;
  3         7  
  3         1395  
22              
23             # ABSTRACT: Convert cpan distribution from BackPAN to a git repository
24             our $VERSION = '0.16'; # VERSION
25              
26              
27             our $ua = HTTP::Tiny->new;
28             our $opt_metacpan_url;
29              
30             sub _rm_rf
31             {
32 83     83   12929 my($file) = @_;
33            
34 83 100 66     369 if($file->is_dir && ! -l $file)
35             {
36 18         1030 _rm_rf($_) for $file->children;
37             }
38            
39 83 50       3790 $file->remove || die "unable to delete $file";
40             }
41              
42             our $_run_cb = sub {};
43             our $original_run = \&Git::Wrapper::RUN;
44             our $ignore_error = 0;
45             our $trace = 0;
46             sub _run_wrapper
47             {
48 30     30   1063 my($self,@command) = @_;
49 30         562 my @display;
50 30         608 foreach my $arg (@command)
51             {
52 58 100       236 if(ref($arg) eq 'HASH')
53             {
54 7         79 while(my($k,$v) = each %$arg)
55             {
56 28         245 push @display, "--$k";
57 28 50       312 push @display, $v =~ /\s/ ? "'$v'" : $v
    100          
58             if $v ne '1'; # yes there is a weird exception for this :P
59             }
60             }
61             else
62             {
63 51         190 push @display, $arg;
64             }
65             }
66 30         371 $_run_cb->($self, @display);
67 30 50       35235 say "+ git @display" if $trace;
68 30         190 $original_run->($self, @command);
69             }
70              
71             sub main
72             {
73 2     2 0 69274 my $class = shift;
74 2         16 local @ARGV = @_;
75 3     3   58 no warnings 'redefine';
  3         8  
  3         173  
76 2         27 local *Git::Wrapper::RUN = \&_run_wrapper;
77 3     3   18 use warnings;
  3         10  
  3         4603  
78            
79 2         16 my %skip;
80             my $opt_backpan_index_url;
81 2         16 my $opt_backpan_url = "http://backpan.perl.org/authors/id";
82 2         17 $opt_metacpan_url = "http://fastapi.metacpan.org/";
83 2         10 my $opt_trace = 0;
84 2         10 my $opt_output;
85             my $opt_resume;
86              
87             GetOptions(
88             'backpan_index_url=s' => \$opt_backpan_index_url,
89             'backpan_url=s' => \$opt_backpan_url,
90             'metacpan_url=s' => \$opt_metacpan_url,
91             'trace' => \$opt_trace,
92 0     0   0 'skip=s' => sub { $skip{$_} = 1 for split /,/, $_[1] },
93             'resume' => \$opt_resume,
94             'output|o=s' => \$opt_output,
95 0     0   0 'help|h' => sub { pod2usage({ -verbose => 2}) },
96             'version' => sub {
97 0   0 0   0 say 'cpangitify version ', ($App::cpangitify::VERSION // 'dev');
98 0         0 exit 1;
99             },
100 2 50       63 ) || pod2usage(1);
101              
102 2         3353 local $trace = $opt_trace;
103              
104 2         16 my @names = map { s/::/-/g; $_ } @ARGV;
  3         20  
  3         18  
105 2         13 my %names = map { $_ => 1 } @names;
  3         24  
106 2         9 my $name = $names[0];
107              
108 2 50       11 pod2usage(1) unless $name;
109              
110 2 50       44 my $dest = $opt_output ? dir($opt_output)->absolute : dir()->absolute->subdir($name);
111              
112 2 50 33     694 if(-e $dest && ! $opt_resume)
113             {
114 0         0 say "already exists: $dest";
115 0         0 say "you may be able to update with the --resume option";
116 0         0 say "but any local changes to your repository will be overwritten by upstream";
117 0         0 return 2;
118             }
119              
120 2         251 say "creating/updating index...";
121 2         63 my $history = CPAN::ReleaseHistory->new(
122             maybe url => $opt_backpan_index_url
123             )->release_iterator;
124              
125 2         32533 say "searching...";
126 2         10 my @rel;
127 2         15 while(my $release = $history->next_release)
128             {
129 22 100       12999 next unless $names{$release->distinfo->dist};
130 7         967 push @rel, $release;
131             }
132              
133 2 50 33     228 if($@ || @rel == 0)
134             {
135 0         0 say "no releases found for $name";
136 0         0 return 2;
137             }
138              
139 2         143 say "mkdir $dest";
140 2         160 $dest->mkpath(0,0700);
141              
142 2         503 my $git = Git::Wrapper->new($dest->stringify);
143              
144 2 50       176 if($opt_resume)
145             {
146 0 0       0 if($git->status->is_dirty)
147             {
148 0         0 die "the appear to be uncommited changes";
149             }
150 0         0 $skip{$_} = 1 for $git->tag;
151             }
152             else
153             {
154 2         23 $git->init;
155             }
156              
157             sub author($)
158             {
159 7     7 0 36 state $cache = {};
160            
161 7         45 my $cpanid = shift;
162            
163 7 100       70 unless(defined $cache->{$cpanid})
164             {
165 3         85 my $uri = URI->new($opt_metacpan_url . "v1/author/" . $cpanid);
166 3         697 my $res = $ua->get($uri);
167 3 50       31 unless($res->{success})
168             {
169 0         0 say "error fetching $uri";
170 0         0 say $res->{reason};
171 0         0 return 2;
172             }
173             $cache->{$cpanid} = decode_json($res->{content})
174 3         39 }
175            
176 7         2548 sprintf "%s <%s>", $cache->{$cpanid}->{name}, $cache->{$cpanid}->{email}->[0];
177             }
178              
179 2         26096 foreach my $rel (@rel)
180             {
181 7         5448 my $path = $rel->path;
182 7         452 my $version = $rel->distinfo->version;
183 7         408 my $time = $rel->timestamp;
184 7         244 my $cpanid = $rel->distinfo->cpanid;
185            
186 7         364 say "$path [ $version ]";
187            
188 7 50       95 if($skip{$version})
189             {
190 0         0 say "skipping ...";
191 0         0 next;
192             }
193            
194 7         115 my $tmp = dir( tempdir( CLEANUP => 1 ) );
195            
196 7         6103 local $CWD = $tmp->stringify;
197            
198 7         751 my $uri = URI->new(join('/', $opt_backpan_url, $path));
199 7         1323 say "fetch ... $uri";
200 7         458 my $res = $ua->get($uri);
201 7 50       48 unless($res->{success})
202             {
203 0         0 say "error fetching $uri";
204 0         0 say $res->{reason};
205 0         0 return 2;
206             }
207            
208 7         21 do {
209 7         65 my $fn = basename $uri->path;
210            
211 7         1410 open my $fh, '>', $fn;
212 7         44 binmode $fh;
213 7         509 print $fh $res->{content};
214 7         205 close $fh;
215              
216 7         269 say "unpack... $fn";
217 7         147 my $archive = Archive::Extract->new( archive => $fn );
218 7 50       3999 $archive->extract( to => File::Spec->curdir ) || die $archive->error;
219 7         1595310 unlink $fn;
220 7 50       235 if($trace)
221             {
222 0         0 say "- extract $fn $_" for @{ $archive->files };
  0         0  
223             }
224             };
225            
226 7         24 my $source = do {
227 7         78 my @children = map { $_->absolute } dir()->children;
  7         6105  
228 7 50       1445 if(@children != 1)
229             {
230 0         0 say "archive doesn't contain exactly one child: @children";
231             }
232            
233 7         42 $CWD = $children[0]->stringify;
234 7         522 $children[0];
235             };
236            
237 7         179 say "merge...";
238            
239 7         124 foreach my $child ($dest->children)
240             {
241 67 100       27185 next if $child->basename eq '.git';
242 60         866 _rm_rf($child);
243             }
244            
245 7         980 foreach my $child ($source->children)
246             {
247 84 50       175855 next if $child->basename eq '.git';
248 84 100       1262 if(-d $child)
249             {
250 14 50       624 rcopy($child, $dest->subdir($child->basename)) || die "unable to copy $child $!";
251             }
252             else
253             {
254 70 50       4144 rcopy($child, $dest->file($child->basename)) || die "unable to copy $child $!";
255             }
256             }
257            
258 7         12647 say "commit and tag...";
259 7         195 $git->add('.');
260 7         119404 $git->add('-u');
261 7         81804 $git->commit({
262             message => "version $version",
263             date => "$time +0000",
264             author => author $cpanid,
265             'allow-empty' => 1,
266             });
267 7         121784 eval { local $ignore_error = 1; $git->tag($version) };
  7         67  
  7         299  
268 7 50       78791 warn $@ if $@;
269             }
270            
271 2         2419 return 0;
272             }
273              
274             1;
275              
276             __END__