File Coverage

blib/lib/App/cpangitify.pm
Criterion Covered Total %
statement 168 192 87.5
branch 37 58 63.7
condition 4 11 36.3
subroutine 25 28 89.2
pod 0 2 0.0
total 234 291 80.4


line stmt bran cond sub pod time code
1             package App::cpangitify;
2              
3 2     2   433742 use strict;
  2         5  
  2         52  
4 2     2   9 use warnings;
  2         4  
  2         49  
5 2     2   9 use autodie qw( :system );
  2         4  
  2         11  
6 2     2   15284 use 5.010001;
  2         13  
7 2     2   1045 use Getopt::Long qw( GetOptions );
  2         16788  
  2         12  
8 2     2   1280 use Pod::Usage qw( pod2usage );
  2         58653  
  2         220  
9 2     2   737 use Path::Class qw( file dir );
  2         26294  
  2         118  
10 2     2   778 use Git::Wrapper;
  2         21621  
  2         72  
11 2     2   15 use File::Temp qw( tempdir );
  2         4  
  2         119  
12 2     2   18 use File::chdir;
  2         4  
  2         207  
13 2     2   1231 use JSON::PP qw( decode_json );
  2         23548  
  2         210  
14 2     2   28 use URI;
  2         7  
  2         85  
15 2     2   760 use PerlX::Maybe qw( maybe );
  2         2653  
  2         103  
16 2     2   717 use File::Copy::Recursive qw( rcopy );
  2         9822  
  2         163  
17 2     2   23 use File::Basename qw( basename );
  2         5  
  2         156  
18 2     2   1060 use Archive::Extract;
  2         242729  
  2         94  
19 2     2   17 use File::Spec;
  2         4  
  2         36  
20 2     2   633 use CPAN::ReleaseHistory;
  2         96390  
  2         81  
21 2     2   47 use HTTP::Tiny;
  2         5  
  2         779  
22              
23             # ABSTRACT: Convert cpan distribution from BackPAN to a git repository
24             our $VERSION = '0.17'; # VERSION
25              
26              
27             our $ua = HTTP::Tiny->new;
28             our $opt_metacpan_url;
29              
30             sub _rm_rf
31             {
32 83     83   7370 my($file) = @_;
33            
34 83 100 66     268 if($file->is_dir && ! -l $file)
35             {
36 18         678 _rm_rf($_) for $file->children;
37             }
38            
39 83 50       1972 $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   1127 my($self,@command) = @_;
49 30         72 my @display;
50 30         186 foreach my $arg (@command)
51             {
52 58 100       215 if(ref($arg) eq 'HASH')
53             {
54 7         69 while(my($k,$v) = each %$arg)
55             {
56 28         84 push @display, "--$k";
57 28 50       254 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         175 push @display, $arg;
64             }
65             }
66 30         387 $_run_cb->($self, @display);
67 30 50       34537 say "+ git @display" if $trace;
68 30         192 $original_run->($self, @command);
69             }
70              
71             sub main
72             {
73 2     2 0 127164 my $class = shift;
74 2         20 local @ARGV = @_;
75 2     2   17 no warnings 'redefine';
  2         5  
  2         136  
76 2         28 local *Git::Wrapper::RUN = \&_run_wrapper;
77 2     2   12 use warnings;
  2         2  
  2         2543  
78            
79 2         15 my %skip;
80             my $opt_backpan_index_url;
81 2         13 my $opt_backpan_url = "http://backpan.perl.org/authors/id";
82 2         13 $opt_metacpan_url = "http://fastapi.metacpan.org/";
83 2         14 my $opt_trace = 0;
84 2         13 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       75 ) || pod2usage(1);
101              
102 2         4484 local $trace = $opt_trace;
103              
104 2         15 my @names = map { s/::/-/g; $_ } @ARGV;
  3         23  
  3         23  
105 2         11 my %names = map { $_ => 1 } @names;
  3         22  
106 2         9 my $name = $names[0];
107              
108 2 50       15 pod2usage(1) unless $name;
109              
110 2 50       30 my $dest = $opt_output ? dir($opt_output)->absolute : dir()->absolute->subdir($name);
111              
112 2 50 33     1323 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         256 say "creating/updating index...";
121 2         67 my $history = CPAN::ReleaseHistory->new(
122             maybe url => $opt_backpan_index_url
123             )->release_iterator;
124              
125 2         98460 say "searching...";
126 2         10 my @rel;
127 2         18 while(my $release = $history->next_release)
128             {
129 22 100       13025 next unless defined $release->distinfo->dist;
130 8 100       1329 next unless $names{$release->distinfo->dist};
131 7         151 push @rel, $release;
132             }
133              
134 2 50 33     150 if($@ || @rel == 0)
135             {
136 0         0 say "no releases found for $name";
137 0         0 return 2;
138             }
139              
140 2         157 say "mkdir $dest";
141 2         240 $dest->mkpath(0,0700);
142              
143 2         540 my $git = Git::Wrapper->new($dest->stringify);
144              
145 2 50       186 if($opt_resume)
146             {
147 0 0       0 if($git->status->is_dirty)
148             {
149 0         0 die "the appear to be uncommited changes";
150             }
151 0         0 $skip{$_} = 1 for $git->tag;
152             }
153             else
154             {
155 2         25 $git->init;
156             }
157              
158             sub author($)
159             {
160 7     7 0 39 state $cache = {};
161            
162 7         40 my $cpanid = shift;
163            
164 7 100       73 unless(defined $cache->{$cpanid})
165             {
166 3         69 my $uri = URI->new($opt_metacpan_url . "v1/author/" . $cpanid);
167 3         1097 my $res = $ua->get($uri);
168 3 50       16811 unless($res->{success})
169             {
170 0         0 say "error fetching $uri";
171 0         0 say $res->{reason};
172 0         0 return 2;
173             }
174             $cache->{$cpanid} = decode_json($res->{content})
175 3         32 }
176            
177 7         27942 my $email = $cache->{$cpanid}->{email};
178 7 100       57 $email = $email->[0] if ref($email) eq 'ARRAY';
179 7         293 sprintf "%s <%s>", $cache->{$cpanid}->{name}, $email;
180             }
181              
182 2         26212 foreach my $rel (@rel)
183             {
184 7         13822 my $path = $rel->path;
185 7         527 my $version = $rel->distinfo->version;
186 7         257 my $time = $rel->timestamp;
187 7         217 my $cpanid = $rel->distinfo->cpanid;
188            
189 7         11986 say "$path [ $version ]";
190            
191 7 50       92 if($skip{$version})
192             {
193 0         0 say "skipping ...";
194 0         0 next;
195             }
196            
197 7         117 my $tmp = dir( tempdir( CLEANUP => 1 ) );
198            
199 7         6460 local $CWD = $tmp->stringify;
200            
201 7         745 my $uri = URI->new(join('/', $opt_backpan_url, $path));
202 7         1214 say "fetch ... $uri";
203 7         445 my $res = $ua->get($uri);
204 7 50       13449 unless($res->{success})
205             {
206 0         0 say "error fetching $uri";
207 0         0 say $res->{reason};
208 0         0 return 2;
209             }
210            
211 7         106 do {
212 7         44 my $fn = basename $uri->path;
213            
214 7         1935 open my $fh, '>', $fn;
215 7         36 binmode $fh;
216 7         412 print $fh $res->{content};
217 7         240 close $fh;
218              
219 7         95 say "unpack... $fn";
220 7         220 my $archive = Archive::Extract->new( archive => $fn );
221 7 50       3149 $archive->extract( to => File::Spec->curdir ) || die $archive->error;
222 7         1435395 unlink $fn;
223 7 50       222 if($trace)
224             {
225 0         0 say "- extract $fn $_" for @{ $archive->files };
  0         0  
226             }
227             };
228            
229 7         22 my $source = do {
230 7         83 my @children = map { $_->absolute } dir()->children;
  7         4694  
231 7 50       1142 if(@children != 1)
232             {
233 0         0 say "archive doesn't contain exactly one child: @children";
234             }
235            
236 7         39 $CWD = $children[0]->stringify;
237 7         462 $children[0];
238             };
239            
240 7         171 say "merge...";
241            
242 7         63 foreach my $child ($dest->children)
243             {
244 67 100       15750 next if $child->basename eq '.git';
245 60         376 _rm_rf($child);
246             }
247            
248 7         600 foreach my $child ($source->children)
249             {
250 84 50       131406 next if $child->basename eq '.git';
251 84 100       1139 if(-d $child)
252             {
253 14 50       489 rcopy($child, $dest->subdir($child->basename)) || die "unable to copy $child $!";
254             }
255             else
256             {
257 70 50       3142 rcopy($child, $dest->file($child->basename)) || die "unable to copy $child $!";
258             }
259             }
260            
261 7         9373 say "commit and tag...";
262 7         262 $git->add('.');
263 7         85751 $git->add('-u');
264 7         60544 $git->commit({
265             message => "version $version",
266             date => "$time +0000",
267             author => author $cpanid,
268             'allow-empty' => 1,
269             });
270 7         101429 eval { local $ignore_error = 1; $git->tag($version) };
  7         45  
  7         179  
271 7 50       54990 warn $@ if $@;
272             }
273            
274 2         1283 return 0;
275             }
276              
277             1;
278              
279             __END__