File Coverage

blib/lib/Git/CPAN/Patch/Command/Import.pm
Criterion Covered Total %
statement 104 148 70.2
branch 10 28 35.7
condition 5 15 33.3
subroutine 26 29 89.6
pod n/a
total 145 220 65.9


line stmt bran cond sub pod time code
1             package Git::CPAN::Patch::Command::Import;
2             our $AUTHORITY = 'cpan:YANICK';
3             #ABSTRACT: Import a module into a git repository
4             $Git::CPAN::Patch::Command::Import::VERSION = '2.3.1';
5 1     1   732 use 5.10.0;
  1         4  
6              
7 1     1   9 use strict;
  1         3  
  1         23  
8 1     1   5 use warnings;
  1         3  
  1         30  
9 1     1   6 use File::Temp qw/ tempdir /;
  1         2  
  1         77  
10 1     1   6 use Method::Signatures::Simple;
  1         2  
  1         7  
11 1     1   748 use Git::Repository;
  1         15769  
  1         5  
12 1     1   400 use Git::CPAN::Patch::Import;
  1         5  
  1         35  
13 1     1   8 use File::chdir;
  1         3  
  1         101  
14 1     1   404 use Git::CPAN::Patch::Release;
  1         3  
  1         68  
15 1     1   10 use Path::Class qw/ dir /;
  1         2  
  1         66  
16 1     1   598 use MetaCPAN::Client;
  1         187042  
  1         33  
17              
18             # TODO Path::Class => Path::Tiny
19              
20 1     1   10 use MooseX::App::Command;
  1         3  
  1         14  
21              
22             extends 'Git::CPAN::Patch';
23             with 'Git::CPAN::Patch::Role::Git';
24              
25             has tmpdir => (
26             is => 'ro',
27             isa => 'Path::Tiny',
28             lazy => 1,
29             default => sub {
30             return Path::Tiny->tempdir();
31             }
32             );
33              
34 1     1   13211 use experimental qw(smartmatch);
  1         897  
  1         5  
35              
36             our $PERL_GIT_URL = 'git://perl5.git.perl.org/perl.git';
37              
38             option 'norepository' => (
39             is => 'ro',
40             isa => 'Bool',
41             default => 0,
42             documentation => "don't clone git repository",
43             );
44              
45             option 'latest' => (
46             is => 'ro',
47             isa => 'Bool',
48             default => 0,
49             documentation => 'only pick latest release, if clone from CPAN',
50             );
51              
52             option check => (
53             is => 'ro',
54             isa => 'Bool',
55             default => 1,
56             documentation => q{Verifies that the imported version is greater than what is already imported},
57             );
58              
59             option parent => (
60             is => 'ro',
61             isa => 'ArrayRef',
62             default => sub { [] },
63             documentation => q{Parent of the imported release (can have more than one)},
64             );
65              
66             parameter thing_to_import => (
67             is => 'rw',
68             isa => 'Str',
69             required => 0,
70             );
71              
72             has metacpan => (
73             is => 'ro',
74             default => sub {
75             MetaCPAN::Client->new;
76             },
77             );
78              
79             option author_name => (
80             is => 'ro',
81             documentation => "explicitly set the author's name",
82             );
83              
84             option author_email => (
85             is => 'ro',
86             documentation => "explicitly set the author's email",
87             );
88              
89 1     1   674 method get_releases_from_url($url) {
  0     0   0  
  0         0  
  0         0  
90 0         0 require LWP::Simple;
91              
92 0         0 ( my $name = $url ) =~ s#^.*/##;
93 0         0 my $destination = $self->tmpdir . '/'.$name;
94              
95 0         0 say "copying '$url' to '$destination'";
96              
97 0 0       0 LWP::Simple::mirror( $url => $destination )
98             or die "Failed to mirror $url\n";
99              
100 0         0 return Git::CPAN::Patch::Release->new(
101             metacpan => $self->metacpan,
102             tarball => $destination
103             );
104             }
105              
106 1     1   601 method get_releases_from_local_file($path) {
  0     0   0  
  0         0  
  0         0  
107 0         0 return Git::CPAN::Patch::Release->new( metacpan => $self->metacpan, tarball => $path );
108             }
109              
110 1     1   452 method clone_git_repo($release,$url) {
  0     0   0  
  0         0  
  0         0  
111 0         0 $self->git_run( 'remote', 'add', 'cpan', $url );
112             {
113             # git will output the tags on STDERR
114 0         0 local *STDERR;
  0         0  
115 0         0 open STDERR, '>', \my $err;
116 0         0 $self->git_run( 'fetch', 'cpan' );
117 0         0 say $err;
118             }
119 0         0 $self->git_run( config => 'cpan.module-name', $release->dist_name );
120             }
121              
122             sub looks_like_git {
123 1 50   1   8 my $repo = shift or return;
124              
125 0 0       0 return 1 if $repo->{type} eq 'git';
126              
127 0         0 return $repo->{url} =~ /github\.com|\.git$/;
128             }
129              
130 1     1   555 method get_releases_from_cpan($dist_or_module) {
  1     1   114  
  1         11  
  1         5  
131              
132             # is it a module belonging to a distribution?
133 1   33     5 my $dist = eval{ $self->metacpan->module($dist_or_module)->data->{distribution}
134             } || $dist_or_module;
135              
136 1 50       267 if ( $dist eq 'perl' ) {
137 0         0 die "$dist_or_module is a core modules, ",
138             "clone perl from $PERL_GIT_URL instead.\n";
139             }
140              
141 1 50 33     43 if( my $latest_release = !$self->norepository && $self->metacpan->release($dist)) {
142 1         182 my $repo = $latest_release->data->{metadata}{resources}{repository};
143 1 50       73 if ( looks_like_git($repo) ) {
144 0         0 say "Git repository found: ", $repo->{url};
145             $self->clone_git_repo(Git::CPAN::Patch::Release->new(
146             metacpan => $self->metacpan,
147             dist_name => $dist,
148             meta_info => $latest_release,
149 0         0 ),$repo->{url});
150 0         0 return;
151             }
152             }
153              
154 1 50       36 if ( $self->latest ) {
155 0         0 my $rel = $self->metacpan->release($dist);
156             return Git::CPAN::Patch::Release->new(
157             metacpan => $self->metacpan,
158             meta_info => $rel->data,
159 0         0 map { $_ => $rel->data->{$_} } qw/ name author date download_url version /
  0         0  
160             );
161             }
162              
163 1 50       30 my $releases = $self->metacpan->release( {
164             distribution => $dist
165             }) or die "could not find release for '$dist_or_module' on metacpan\n";
166              
167 1         225 my @releases;
168              
169 1         16 while( my $r = $releases->next ) {
170 1         159 push @releases, Git::CPAN::Patch::Release->new(
171             metacpan => $self->metacpan,
172             meta_info => $r->data
173             );
174             }
175              
176 1         2227 return sort { $a->date cmp $b->date } @releases;
  0         0  
177             }
178              
179 1     1   719 method releases_to_import {
  1     1   4  
  1         4  
180 1         60 given ( $self->thing_to_import ) {
181 1         19 when ( qr/^(?:https?|file|ftp)::/ ) {
182 0         0 return $self->get_releases_from_url( $_ );
183             }
184 1         39 when ( -f $_ ) {
185 0         0 return $self->get_releases_from_local_file( $_ );
186             }
187 1         4 default {
188 1         8 return $self->get_releases_from_cpan($_);
189             }
190             }
191             }
192              
193 1     1   518 method import_release($release) {
  1     1   3  
  1         3  
  1         2  
194 1         34 my $import_version = $release->dist_version;
195              
196 1 50 33     27 if ( $self->check and $self->last_imported_version ) {
197 0 0       0 return say $release->dist_name . " $import_version has already been imported\n"
198             if $import_version == $self->last_imported_version;
199              
200 0 0       0 return say sprintf "last imported version %s is more recent than %s"
201             . ", can't import",
202             $self->last_imported_version, $import_version
203             if $import_version <= $self->last_imported_version;
204             }
205              
206             # create a tree object for the CPAN module
207             # this imports the source code without touching the user's working directory or
208             # index
209              
210 1         26297 my $tree = do {
211             # don't overwrite the user's index
212 1         111 local $ENV{GIT_INDEX_FILE} = $self->tmpdir . "/temp_git_index";
213 1         47 local $ENV{GIT_DIR} = dir($self->root . '/.git')->absolute->stringify;
214 1         362 local $ENV{GIT_WORK_TREE} = $release->extracted_dir;
215              
216 1         32 local $CWD = $release->extracted_dir;
217              
218 1         75 my $write_tree_repo = Git::Repository->new( work_tree => $CWD );
219              
220 1         76684 $write_tree_repo->run( qw(add -v --all --force .) );
221 1         32158 $write_tree_repo->run( "write-tree" );
222             };
223              
224             # create a commit for the imported tree object and write it into
225             # refs/heads/cpan/master
226             {
227 1         26966 local %ENV = %ENV;
  1         66  
228              
229             # TODO authors and author_date
230              
231             # create the commit object
232 1   33     94 $ENV{GIT_AUTHOR_NAME} = $self->author_name || $release->author_name || $ENV{GIT_AUTHOR_NAME};
233 1   33     41 $ENV{GIT_AUTHOR_EMAIL} = $self->author_email || $release->author_email || $ENV{GIT_AUTHOR_EMAIL};
234 1 50       38 $ENV{GIT_AUTHOR_DATE} = $release->date if $release->date;
235              
236 1         15 my @parents = grep { $_ } $self->last_commit, @{ $self->parent };
  0         0  
  1         24910  
237              
238 1 50       13 my $message = sprintf "%s %s %s\n",
239             ( $self->first_import ? 'initial import of' : 'import' ),
240             $release->dist_name, $release->dist_version;
241              
242 1     1   273 no warnings 'uninitialized';
  1         2  
  1         133  
243 1         8 $message .= <<"END";
244              
245 1         29 git-cpan-module: @{[ $release->dist_name ]}
246 1         29 git-cpan-version: @{[ $release->dist_version ]}
247 1         54 git-cpan-authorid: @{[ $release->author_cpan ]}
248              
249             END
250              
251             my $commit = $self->git_run(
252             { input => $message },
253 1         15 'commit-tree', $tree, map { ( -p => $_ ) } @parents );
  0         0  
254              
255             # finally, update the fake remote branch and create a tag for convenience
256              
257 0         0 print $self->git_run('update-ref', '-m' => "import " . $release->dist_name, 'refs/remotes/cpan/master', $commit );
258              
259 0         0 print $self->git_run( tag => 'v'.$release->dist_version, $commit );
260              
261 0         0 say "created tag '@{[ 'v'.$release->dist_version ]}' ($commit)";
  0         0  
262             }
263              
264 0         0 $self->git_run( config => 'cpan.module-name', $release->dist_name );
265             }
266              
267 1     1   404 method run {
  1     1   15  
  1         3  
268 1         10 my @releases = $self->releases_to_import;
269              
270 1         62 for my $r ( @releases ) {
271 1         3 eval { $self->import_release($r) };
  1         9  
272 1 50       28446 if ( $@ ) {
273 1         99 warn "failed to import release, skipping...\n$@\n";
274             }
275             }
276             }
277              
278             __PACKAGE__->meta->make_immutable;
279              
280             1;
281              
282             __END__
283              
284             =pod
285              
286             =encoding UTF-8
287              
288             =head1 NAME
289              
290             Git::CPAN::Patch::Command::Import - Import a module into a git repository
291              
292             =head1 VERSION
293              
294             version 2.3.1
295              
296             =head1 SYNOPSIS
297              
298             # takes any string CPANPLUS handles:
299              
300             % git-cpan import Foo::Bar
301             % git-cpan import A/AU/AUTHORID/Foo-Bar-0.03.tar.gz
302             % git-cpan import http://backpan.cpan.org/authors/id/A/AU/AUTHORID/Foo-Bar-0.03.tar.gz
303              
304             # If the repository is already initialized, can be run with no arguments to
305             # import the latest version
306             git-cpan import
307              
308             =head1 DESCRIPTION
309              
310             This command is used internally by C<git-cpan-init>, C<git-cpan-update> and
311             C<git-backpan-init>.
312              
313             This command takes a tarball, extracts it, and imports it into the repository.
314              
315             It is only possible to update to a newer version of a module.
316              
317             The module history is tracked in C<refs/remotes/cpan/master>.
318              
319             Tags are created for each version of the module.
320              
321             This command does not touch the working directory, and is safe to run even if
322             you have pending work.
323              
324             =head1 OPTIONS
325              
326             =over
327              
328             =item --check, --nocheck
329              
330             Explicitly enables/disables version checking. If version checking is
331             enabled, which is the default, git-cpan-import will refuse to import a
332             version of the package
333             that has a smaller version number than the HEAD of the branch I<cpan/master>.
334              
335             =item --parent
336              
337             Allows adding extra parents when
338             importing, so that when a patch has been incorporated into an upstream
339             version the generated commit is like a merge commit, incorporating both
340             the CPAN history and the user's local history.
341              
342             For example, this will set the current HEAD of the master branch as a parent of
343             the imported CPAN package:
344              
345             $ git checkout master
346             $ git-cpan import --parent HEAD My-Module
347              
348             More than one '--parent' can be specified.
349              
350             =item --author_name
351              
352             Forces the author name to the given value, instead of trying to resolve it from
353             the release metadata.
354              
355             =item --author_email
356              
357             Forces the author email to the given value, instead of trying to resolve it from
358             the release metadata.
359              
360             =back
361              
362             =head1 AUTHORS
363              
364             Yuval Kogman C<< <nothingmuch@woobling.org> >>
365              
366             Yanick Champoux C<< <yanick@cpan.org> >>
367              
368             =head1 SEE ALSO
369              
370             L<Git::CPAN::Patch>
371              
372             =head1 AUTHOR
373              
374             Yanick Champoux <yanick@cpan.org>
375              
376             =head1 COPYRIGHT AND LICENSE
377              
378             This software is copyright (c) 2017 by Yanick Champoux.
379              
380             This is free software; you can redistribute it and/or modify it under
381             the same terms as the Perl 5 programming language system itself.
382              
383             =cut