-
Notifications
You must be signed in to change notification settings - Fork 19
Expand file tree
/
Copy pathImport.pm
More file actions
384 lines (280 loc) · 10.2 KB
/
Import.pm
File metadata and controls
384 lines (280 loc) · 10.2 KB
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
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
package Git::CPAN::Patch::Command::Import;
our $AUTHORITY = 'cpan:YANICK';
#ABSTRACT: Import a module into a git repository
$Git::CPAN::Patch::Command::Import::VERSION = '2.5.0';
use 5.20.0;
use strict;
use warnings;
use File::Temp qw/ tempdir /;
use Git::Repository;
use Git::CPAN::Patch::Import;
use File::chdir;
use Git::CPAN::Patch::Release;
use Path::Class qw/ dir /;
use MetaCPAN::Client;
# TODO Path::Class => Path::Tiny
use MooseX::App::Command;
extends 'Git::CPAN::Patch';
with 'Git::CPAN::Patch::Role::Git';
has tmpdir => (
is => 'ro',
isa => 'Path::Tiny',
lazy => 1,
default => sub {
return Path::Tiny->tempdir();
}
);
use experimental qw(smartmatch signatures);
our $PERL_GIT_URL = 'git://perl5.git.perl.org/perl.git';
option 'norepository' => (
is => 'ro',
isa => 'Bool',
default => 0,
documentation => "don't clone git repository",
);
option 'latest' => (
is => 'ro',
isa => 'Bool',
default => 0,
documentation => 'only pick latest release, if clone from CPAN',
);
option check => (
is => 'ro',
isa => 'Bool',
default => 1,
cmd_negate => 'nocheck',
documentation => q{Verifies that the imported version is greater than what is already imported},
);
option parent => (
is => 'ro',
isa => 'ArrayRef',
default => sub { [] },
documentation => q{Parent of the imported release (can have more than one)},
);
parameter thing_to_import => (
is => 'rw',
isa => 'Str',
required => 0,
);
has metacpan => (
is => 'ro',
default => sub {
MetaCPAN::Client->new;
},
);
option author_name => (
is => 'ro',
documentation => "explicitly set the author's name",
);
option author_email => (
is => 'ro',
documentation => "explicitly set the author's email",
);
sub get_releases_from_url($self,$url) {
require LWP::Simple;
( my $name = $url ) =~ s#^.*/##;
my $destination = $self->tmpdir . '/'.$name;
say "copying '$url' to '$destination'";
LWP::Simple::mirror( $url => $destination )
or die "Failed to mirror $url\n";
return Git::CPAN::Patch::Release->new(
metacpan => $self->metacpan,
tarball => $destination
);
}
sub get_releases_from_local_file($self,$path) {
return Git::CPAN::Patch::Release->new( metacpan => $self->metacpan, tarball => $path );
}
sub clone_git_repo($self,$release,$url) {
$self->git_run( 'remote', 'add', 'cpan', $url );
{
# git will output the tags on STDERR
local *STDERR;
open STDERR, '>', \my $err;
$self->git_run( 'fetch', 'cpan' );
say $err;
}
$self->git_run( config => 'cpan.module-name', $release->dist_name );
}
sub looks_like_git {
my $repo = shift or return;
return 1 if $repo->{type} eq 'git';
return $repo->{url} =~ /github\.com|\.git$/;
}
sub get_releases_from_cpan($self,$dist_or_module) {
# is it a module belonging to a distribution?
my $dist = eval{ $self->metacpan->module($dist_or_module)->data->{distribution}
} || $dist_or_module;
if ( $dist eq 'perl' ) {
die "$dist_or_module is a core modules, ",
"clone perl from $PERL_GIT_URL instead.\n";
}
if( my $latest_release = !$self->norepository && $self->metacpan->release($dist)) {
my $repo = $latest_release->data->{metadata}{resources}{repository};
if ( looks_like_git($repo) ) {
say "Git repository found: ", $repo->{url};
$self->clone_git_repo(Git::CPAN::Patch::Release->new(
metacpan => $self->metacpan,
dist_name => $dist,
meta_info => $latest_release,
),$repo->{url});
return;
}
}
if ( $self->latest ) {
my $rel = $self->metacpan->release($dist);
return Git::CPAN::Patch::Release->new(
metacpan => $self->metacpan,
meta_info => $rel->data,
map { $_ => $rel->data->{$_} } qw/ name author date download_url version /
);
}
my $releases = $self->metacpan->release( {
distribution => $dist
}) or die "could not find release for '$dist_or_module' on metacpan\n";
my @releases;
while( my $r = $releases->next ) {
push @releases, Git::CPAN::Patch::Release->new(
metacpan => $self->metacpan,
meta_info => $r->data
);
}
return sort { $a->date cmp $b->date } @releases;
}
sub releases_to_import ($self) {
given ( $self->thing_to_import ) {
when ( qr/^(?:https?|file|ftp)::/ ) {
return $self->get_releases_from_url( $_ );
}
when ( -f $_ ) {
return $self->get_releases_from_local_file( $_ );
}
default {
return $self->get_releases_from_cpan($_);
}
}
}
sub import_release($self,$release) {
my $import_version = $release->dist_version;
if ( $self->check and $self->last_imported_version ) {
return say $release->dist_name . " $import_version has already been imported\n"
if $import_version == $self->last_imported_version;
return say sprintf "last imported version %s is more recent than %s"
. ", can't import",
$self->last_imported_version, $import_version
if $import_version <= $self->last_imported_version;
}
# create a tree object for the CPAN module
# this imports the source code without touching the user's working directory or
# index
my $tree = do {
# don't overwrite the user's index
local $ENV{GIT_INDEX_FILE} = $self->tmpdir . "/temp_git_index";
local $ENV{GIT_DIR} = dir($self->root . '/.git')->absolute->stringify;
local $ENV{GIT_WORK_TREE} = $release->extracted_dir;
local $CWD = $release->extracted_dir;
my $write_tree_repo = Git::Repository->new( work_tree => $CWD );
$write_tree_repo->run( qw(add -v --all --force .) );
$write_tree_repo->run( "write-tree" );
};
# create a commit for the imported tree object and write it into
# refs/heads/cpan/master
{
local %ENV = %ENV;
# TODO authors and author_date
# create the commit object
$ENV{GIT_AUTHOR_NAME} = $self->author_name || $release->author_name || $ENV{GIT_AUTHOR_NAME};
$ENV{GIT_AUTHOR_EMAIL} = $self->author_email || $release->author_email || $ENV{GIT_AUTHOR_EMAIL};
$ENV{GIT_AUTHOR_DATE} = $release->date if $release->date;
my @parents = grep { $_ } $self->last_commit, @{ $self->parent };
my $message = sprintf "%s %s %s\n",
( $self->first_import ? 'initial import of' : 'import' ),
$release->dist_name, $release->dist_version;
no warnings 'uninitialized';
$message .= <<"END";
git-cpan-module: @{[ $release->dist_name ]}
git-cpan-version: @{[ $release->dist_version ]}
git-cpan-authorid: @{[ $release->author_cpan ]}
END
my $commit = $self->git_run(
{ input => $message },
'commit-tree', $tree, map { ( -p => $_ ) } @parents );
# finally, update the fake remote branch and create a tag for convenience
print $self->git_run('update-ref', '-m' => "import " . $release->dist_name, 'refs/remotes/cpan/master', $commit );
my $tag_name = $release->dist_version =~ /^v/ ? $release->dist_version : 'v'.$release->dist_version;
print $self->git_run( tag => $tag_name, '--no-sign', $commit );
say "created tag '@{[ $tag_name ]}' ($commit)";
}
$self->git_run( config => 'cpan.module-name', $release->dist_name );
}
sub run ($self) {
my @releases = $self->releases_to_import;
for my $r ( @releases ) {
eval { $self->import_release($r) };
if ( $@ ) {
warn "failed to import release, skipping...\n$@\n";
}
}
}
__PACKAGE__->meta->make_immutable;
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
Git::CPAN::Patch::Command::Import - Import a module into a git repository
=head1 VERSION
version 2.5.0
=head1 SYNOPSIS
# takes any string CPANPLUS handles:
% git-cpan import Foo::Bar
% git-cpan import A/AU/AUTHORID/Foo-Bar-0.03.tar.gz
% git-cpan import http://backpan.cpan.org/authors/id/A/AU/AUTHORID/Foo-Bar-0.03.tar.gz
# If the repository is already initialized, can be run with no arguments to
# import the latest version
git-cpan import
=head1 DESCRIPTION
This command is used internally by C<git-cpan-init>, C<git-cpan-update> and
C<git-backpan-init>.
This command takes a tarball, extracts it, and imports it into the repository.
It is only possible to update to a newer version of a module.
The module history is tracked in C<refs/remotes/cpan/master>.
Tags are created for each version of the module.
This command does not touch the working directory, and is safe to run even if
you have pending work.
=head1 OPTIONS
=over
=item --check, --nocheck
Explicitly enables/disables version checking. If version checking is
enabled, which is the default, git-cpan-import will refuse to import a
version of the package
that has a smaller version number than the HEAD of the branch I<cpan/master>.
=item --parent
Allows adding extra parents when
importing, so that when a patch has been incorporated into an upstream
version the generated commit is like a merge commit, incorporating both
the CPAN history and the user's local history.
For example, this will set the current HEAD of the master branch as a parent of
the imported CPAN package:
$ git checkout master
$ git-cpan import --parent HEAD My-Module
More than one '--parent' can be specified.
=item --author_name
Forces the author name to the given value, instead of trying to resolve it from
the release metadata.
=item --author_email
Forces the author email to the given value, instead of trying to resolve it from
the release metadata.
=back
=head1 AUTHORS
Yuval Kogman C<< <nothingmuch@woobling.org> >>
Yanick Champoux C<< <yanick@cpan.org> >>
=head1 SEE ALSO
L<Git::CPAN::Patch>
=head1 AUTHOR
Yanick Champoux <yanick@cpan.org>
=head1 COPYRIGHT AND LICENSE
This software is copyright (c) 2022, 2021, 2018, 2017, 2016, 2015, 2014, 2013, 2012, 2011, 2010, 2009 by Yanick Champoux.
This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.
=cut