File Coverage

blib/lib/MDV/Repsys/Remote.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             package MDV::Repsys::Remote;
2              
3 1     1   3021 use strict;
  1         3  
  1         45  
4 1     1   7 use warnings;
  1         3  
  1         35  
5 1     1   6 use Carp;
  1         2  
  1         95  
6 1     1   46 use MDV::Repsys qw(sync_source extract_srpm);
  0            
  0            
7             use Config::IniFiles;
8             use SVN::Client;
9             use Date::Parse;
10             use Date::Format;
11             use POSIX qw(getcwd);
12             use RPM4;
13             use File::Temp qw(tempfile);
14             use File::Tempdir;
15             use File::Path;
16              
17             our $VERSION = ('$Revision: 103942 $' =~ m/(\d+)/)[0];
18              
19             =head1 NAME
20              
21             MDV::Repsys::Remote
22              
23             =head1 SYNOPSYS
24              
25             Module to access and build rpm from a svn
26              
27             =head1 FUNCTIONS
28              
29             =head2 new(%options)
30              
31             Create a new MDV::Repsys::Remote object
32              
33             options:
34              
35             =over 4
36              
37             =item configfile
38              
39             Use this repsys configuration file instead /etc/repsys.conf
40              
41             =item nocommit
42              
43             Disable commit action, usefull for testing purpose
44              
45             =back
46              
47             =cut
48              
49             sub new {
50             my ($class, %options) = @_;
51              
52             my $homerepsys = (
53             $ENV{REPSYS_CONF} ?
54             $ENV{REPSYS_CONF} :
55             "$ENV{HOME}/.repsys/repsys.conf"
56             );
57              
58             my $cfg = Config::IniFiles->new(
59             (-r $homerepsys ? (-file => $homerepsys) : ()),
60             '-import' => Config::IniFiles->new(
61             -file => $options{configfile} || "/etc/repsys.conf",
62             ) || undef,
63             );
64              
65             my $home_cfg = (
66             -r $homerepsys ?
67             Config::IniFiles->new(-file => $homerepsys, '-import' => $cfg,) :
68             undef
69             ) || Config::IniFiles->new('-import' => $cfg,);
70              
71             $cfg or return undef;
72              
73             my $repsys = {
74             config => $cfg,
75             svn => SVN::Client->new(),
76             nocommit => $options{nocommit},
77             default => {
78             pkgversion => 'current',
79             revision => 'HEAD',
80             },
81             error => undef,
82             tempdir => [],
83             };
84              
85             bless($repsys, $class);
86             $repsys->set_verbosity(0);
87              
88             $repsys
89             }
90              
91             =head2 last_error
92              
93             Return the last error message after a failure.
94              
95             =cut
96              
97             sub last_error {
98             return $_[0]->{error};
99             }
100              
101             =head2 set_verbosity($level)
102              
103             Set the verbosity verbosity of the module:
104              
105             0 silent
106             1 progress message
107             2 debug message
108              
109             =cut
110              
111             sub set_verbosity {
112             my ($self, $level) = @_;
113             $self->{verbosity} = $level || 0;
114             # not 0 ? (INFO, DEBUG) : ERROR
115             RPM4::setverbosity($level ? $level + 5 : 3);
116             }
117              
118             sub _print_msg {
119             my ($self, $level, $fmt, @args) = @_;
120             $fmt or croak "No message given to _print_msg";
121             $level > 0 or croak "message cannot be < 1 ($level)";
122             return if $level > $self->{verbosity};
123             printf("$fmt\n", @args);
124             }
125              
126             =head2 get_pkgurl_parent($pkgname, %options)
127              
128             Return the parent svn url location for package named $pkgname
129              
130             =cut
131              
132             sub get_pkgurl_parent {
133             my ($self, $pkgname) = @_;
134             sprintf(
135             "%s/%s",
136             $self->{config}->val('global', 'default_parent') || "",
137             $pkgname,
138             );
139             }
140              
141             =head2 get_pkgname_from_wc
142              
143             Return the package name from current working copy
144              
145             =cut
146              
147             sub get_pkgname_from_wc {
148             my ($self) = @_;
149              
150             my $ctx = new SVN::Client();
151             our $url;
152             my $receiver = sub {
153             my( $path, $info, $pool ) = @_;
154             our $url = $info->URL;
155             };
156             eval {
157             $ctx->info(getcwd(), undef, 'WORKING', $receiver, 0);
158             };
159             if ($@) {
160             return ;
161             }
162             my $parent = $self->{config}->val('global', 'default_parent');
163             $url =~ /^\Q$parent\E\/([^\/]*)\/?.*$/;
164             return $1
165             }
166              
167             =head2 get_pkgurl($pkgname, %options)
168              
169             Return the svn url location for package named $pkgname
170              
171             =cut
172              
173             sub get_pkgurl {
174             my ($self, $pkgname, %options) = @_;
175             sprintf(
176             "%s/%s",
177             $self->get_pkgurl_parent($pkgname),
178             $options{pkgversion} || $self->{default}{pkgversion},
179             );
180             }
181              
182             =head2 checkout_pkg($pkgname, $destdir, %options)
183              
184             Checkout a package from svn into $destdir directory
185              
186             =cut
187              
188             sub checkout_pkg {
189             my ($self, $pkgname, $destdir, %options) = @_;
190              
191             $destdir ||= $pkgname;
192              
193             my $revision;
194             $self->_print_msg(1, 'Checkout package %s into %s/', $pkgname, $destdir);
195             eval {
196             $revision = $self->{svn}->checkout(
197             $self->get_pkgurl($pkgname, %options),
198             $destdir,
199             $options{revision} || $self->{default}{revision},
200             1,
201             );
202             };
203             if ($@) {
204             $self->{error} = "Can't checkout $pkgname: $@";
205             return;
206             }
207              
208             return $revision;
209             }
210              
211             =head2 get_old_changelog($pkgname, $handle, %options)
212              
213             Read old changelog entry from svn and write it into $handle.
214             If not specified, $handle is set to STDOUT.
215              
216             =cut
217              
218             sub get_old_changelog {
219             my ($self, $pkgname, $handle, %options) = @_;
220            
221             $handle ||= \*STDOUT;
222              
223             $self->_print_msg(2, 'Get old changelog for %s', $pkgname);
224             eval {
225             $self->{svn}->cat(
226             $handle,
227             sprintf(
228             "%s/%s/log",
229             $self->{config}->val('log', 'oldurl'),
230             $pkgname,
231             ),
232             $options{revision} || $self->{default}{revision},
233             );
234             };
235             if ($@) {
236             $self->{error} = "Can't get old changelog for $pkgname: $@";
237             return;
238             }
239             return 1;
240             }
241              
242             sub _old_log_pkg {
243             my ($self, $pkgname, %options) = @_;
244              
245             my $templog = File::Temp->new(UNLINK => 1);
246              
247             $self->get_old_changelog($pkgname, $templog, %options) or return;
248              
249             my @cl;
250              
251             seek($templog, 0, 0);
252              
253             while(my $line = <$templog>) {
254             chomp($line);
255             $line or next;
256             $line =~ /^%changelog/ and next;
257             if ($line =~ /^\* (\w+\s+\w+\s+\d+\s+\d+)\s+(.*)/) {
258             push(
259             @cl,
260             {
261             'time' => str2time($1, 'UTC') || 0,
262             author => $2 || '',
263             text => '',
264             }
265             );
266             } else {
267             if (!@cl) {
268             push(@cl, { header => 1, text => '', 'time' => 0 });
269             }
270             $cl[-1]->{text} .= "$line\n";
271             }
272             }
273              
274             @cl;
275             }
276              
277             sub _log_pkg {
278             my ($self, $pkgname, %options) = @_;
279            
280             my @cl;
281              
282             eval {
283             $self->{svn}->log(
284             $self->get_pkgurl($pkgname, %options, pkgversion => 'releases'),
285             $options{revision} || $self->{default}{revision},
286             0, 1, 0,
287             sub {
288             my ($changed_paths, $revision, $author, $date, $message) = @_;
289             #print "$revision, $author, $date, $message\n";
290             foreach (keys %{$changed_paths || {}}) {
291             my $info = $changed_paths->{$_};
292             $info->copyfrom_rev() > 0 or next;
293             m!releases/(?:([^:/]+):)?([^/]+)/([^/]+)! or next;
294             push(
295             @cl,
296             {
297             revision => $info->copyfrom_rev(),
298             author => '',
299             'time' => str2time($date),
300             text => '',
301             evr => "$2-$3",
302             }
303             );
304             }
305             }
306             );
307             };
308              
309             my $callback = sub {
310             my ($changed_paths, $revision, $author, $date, $message) = @_;
311             my $cltoupdate;
312             foreach my $clg (sort { $b->{revision} <=> $a->{revision} } @cl) {
313             if ($revision > $clg->{revision}) {
314             next;
315             } else {
316             $cltoupdate = $clg;
317             }
318             }
319              
320             if (!$cltoupdate) {
321             $cltoupdate = {
322             revision => $revision,
323             author => $author,
324             'time' => str2time($date),
325             text => '',
326             };
327              
328             push(@cl, $cltoupdate);
329             }
330              
331             $cltoupdate->{author} ||= $author,
332             my @gti = gmtime(str2time($date));
333              
334             my ($textentry) = grep { $_->{author} eq $author } @{$cltoupdate->{log} || []};
335              
336             if (!$textentry) {
337             $textentry = {
338             author => $author,
339             text => [],
340             };
341             push(@{$cltoupdate->{log}}, $textentry);
342             }
343              
344             push(@{$textentry->{text}}, $message);
345            
346             };
347              
348             eval {
349             $self->{svn}->log(
350             $self->get_pkgurl($pkgname, %options),
351             $options{revision} || $self->{default}{revision},
352             0, 0, 0,
353             $callback,
354             );
355             };
356             if ($@) {
357             $self->{error} = "Can't get svn log: $@";
358             return;
359             }
360              
361             @cl
362             }
363              
364             sub _fmt_cl_entry {
365             my ($self, $cl) = @_;
366             $cl->{'time'} or return $cl->{text};
367             my @gti = gmtime($cl->{'time'});
368              
369             # subversion changelog is having empty commit
370             return if not $cl->{author};
371             my $text = $cl->{text};
372             if (!$text) {
373             my $indent = '';
374             foreach my $log (@{$cl->{log} || []}) {
375             if ($log->{author} ne $cl->{author}) {
376             $text .= "- from " . $self->{config}->val('users', $log->{author}, $log->{author}) . "\n";
377             }
378             foreach my $mes (@{$log->{text}}) {
379             my $dash = '- ';
380             $mes =~ s/^(\s|-|\*)*/$indent- /;
381             foreach (split(/\n/, $mes)) {
382             chomp;
383             $_ or next;
384             s/([^%])?%([^%])?/$1%%$2/g;
385             s/^(\s|\*)*/$indent/;
386             if (!m/^-/) {
387             s/^/ /;
388             }
389             $text .= "$_\n";
390             $dash = ' ';
391             }
392             }
393             $indent = ' ';
394             }
395             }
396             $text =~ s/^\*/-/gm;
397             sprintf
398             "* %s %s%s\n%s%s\n",
399             # date
400             # author
401             # svn date + rev
402             # message
403             strftime("%a %b %d %Y", @gti), # date
404             $self->{config}->val(
405             'users',
406             $cl->{author},
407             $cl->{author}
408             ), # author
409             ($cl->{evr} ? " $cl->{evr}" : ''),
410             ($cl->{revision} ?
411             sprintf(
412             "+ %s (%s)\n",
413             # svn date
414             # revision
415             strftime("%Y-%m-%d %T", @gti), # svn date
416             $cl->{revision}, # revision
417             ) : ''
418             ), # svn date + rev
419             $text; # message
420             }
421              
422             =head2 log_pkg($pkgname, $handle, %options)
423              
424             Build a log from svn and print it into $handle.
425             If not specified, $handle is set to STDOUT.
426              
427             =cut
428              
429             sub log_pkg {
430             my ($self, $pkgname, $handle, %options) = @_;
431             $handle ||= \*STDOUT;
432             foreach my $cl ($self->_log_pkg($pkgname, %options)) {
433             print $handle $self->_fmt_cl_entry($cl);
434             }
435             1;
436             }
437              
438             =head2 build_final_changelog($pkgname, $handle, %options)
439              
440             Build the complete changelog for a package and print it into $handle.
441             If not specified, $handle is set to STDOUT.
442              
443             =cut
444              
445             sub build_final_changelog {
446             my ($self, $pkgname, $handle, %options) = @_;
447              
448             $handle ||= \*STDOUT;
449              
450             $self->_print_msg(1, 'Building final changelog for %s', $pkgname);
451             my @cls = $self->_log_pkg($pkgname, %options) or return 0;
452             push(@cls, $self->_old_log_pkg($pkgname, %options));
453            
454             print $handle "\%changelog\n";
455              
456             foreach my $cl (sort {
457             $b->{'time'} && $a->{'time'} ?
458             $b->{'time'} <=> $a->{'time'} :
459             $a->{'time'} <=> $b->{'time'}
460             } grep { $_ } @cls) {
461             print $handle $self->_fmt_cl_entry($cl);
462             }
463             1;
464             }
465              
466             =head2 get_final_spec_fd($pecfile, $fh, %options)
467              
468             Generated the final specfile from $pecfile into $fh filehandle.
469              
470             =cut
471              
472             sub get_final_spec_fd {
473             my ($self, $specfile, $dh, %options) = @_;
474              
475             my $pkgname = $options{pkgname};
476              
477             if (!$pkgname) {
478             my $spec = RPM4::specnew($specfile, undef, '/', undef, 1, 1) or do {
479             $self->{error} = "Can't parse specfile $specfile";
480             return;
481             };
482             my $h = $spec->srcheader or return; # can't happend
483             $pkgname = $h->queryformat('%{NAME}');
484             }
485              
486             if (defined(MDV::Repsys::_strip_changelog($specfile, $dh))) {
487              
488             print $dh "\n";
489             $self->build_final_changelog(
490             $pkgname,
491             $dh,
492             %options,
493             ) or return;
494             } else {
495             $self->{error} = "Can't open $specfile for reading: $!";
496             return;
497             }
498             1;
499             }
500              
501             =head2 get_final_spec($specfile, %options)
502              
503             Build the final changelog for upload from $specfile.
504              
505             $options{pkgname} is the package name, if not specified, it is evaluate
506             from the specfile.
507              
508             The new specfile will generated into $options{specfile} is specified,
509             otherwise a file with same name is create into $options{destdir}.
510              
511             The module is safe, the source and destination can be the same file,
512             the content will be replaced.
513              
514             if $options{destdir} is not specified, a temporary directory is created.
515             This directory will be trashed on MDV::Repsys::Remote object destruction.
516             So this kind of code will not work:
517              
518             my $o = MDV::Repsys::Remote->new();
519             my $newspec = $o->get_final_spec($specfile);
520             $o = undef;
521             do_something_with($newspecfile); # the directory has been deleted
522              
523             Notice this kind of code produce a warning.
524              
525             =cut
526              
527             sub get_final_spec {
528             my ($self, $specfile, %options) = @_;
529              
530             $self->_print_msg(1, 'Building final specfile from %s', $specfile);
531              
532             if (!($options{destdir} || $options{specfile})) {
533             warn "Using get_final_spec() without destdir or specfile option is unsafe, see perldoc MDV::Respsys::Remote";
534             }
535              
536             my $odir;
537             my $destfile;
538              
539             if ($options{specfile}) {
540             $destfile = $options{specfile};
541             } else {
542             $odir = File::Tempdir->new($options{destdir});
543             push(@{$self->{_temp_late_destroy}}, $odir);
544             my ($basename) = $specfile =~ m!(?:.*/)?(.*)$!;
545             $destfile = $odir->name() . "/$basename";
546             }
547              
548             # avoid race condition is source == dest
549             my $tempfh = File::Temp->new(UNLINK => 1);
550             $self->get_final_spec_fd($specfile, $tempfh, %options);
551              
552             if (open(my $dh, ">", $destfile)) {
553             seek($tempfh, 0, 0);
554             while (<$tempfh>) {
555             print $dh $_;
556             }
557             close($dh);
558             } else {
559             $self->{error} = "Can't open temporary file for writing: $!";
560             return;
561             }
562             close($tempfh);
563              
564             return $destfile;
565             }
566              
567             =head2 get_pkg_lastrev($pkgname, %options)
568              
569             Return the real last revision change for a package.
570              
571             =cut
572              
573             sub get_pkg_lastrev {
574             my ($self, $pkgname, %options) = @_;
575             my $url = $self->get_pkgurl($pkgname, %options);
576             my $leafs;
577              
578             eval {
579             $leafs = $self->{svn}->ls(
580             $url,
581             $options{revision} || $self->{default}{revision},
582             1,
583             );
584             };
585             if ($@) {
586             $self->{error} = "Can't get information from $url: $@";
587             return;
588             }
589             my $revision = 0;
590             foreach my $leaf (%{$leafs || {}}) {
591             defined($leafs->{$leaf}) or next;
592             if ($leafs->{$leaf}->created_rev > $revision) {
593             $revision = $leafs->{$leaf}->created_rev;
594             }
595             }
596            
597             $revision;
598             }
599              
600             =head2 get_dir_lastrev($dir, %options)
601              
602             Return the real last revision change for package checkout into $dir.
603              
604             =cut
605              
606             sub get_dir_lastrev {
607             my ($self, $dir, %options) = @_;
608              
609             $self->_print_msg(2, 'Finding last rev from %s', $dir);
610             my $revision = 0;
611             eval {
612             $self->{svn}->status(
613             $dir,
614             $options{revision} || $self->{default}{revision},
615             sub {
616             my ($path, $status) = @_;
617             my $entry = $status->entry() or return;
618             $revision = $entry->cmt_rev if($revision < $entry->cmt_rev);
619             },
620             1, # recursive
621             1, # get_all
622             0, # update
623             0, # no_ignore
624             );
625             };
626             if ($@) {
627             $self->{error} = "can't get status of $dir: $@";
628             return;
629             }
630              
631             $revision
632             }
633              
634             =head2 get_srpm($pkgname, %options)
635              
636             Build the final src.rpm from the svn. Return the svn revision and
637             the src.rpm location.
638              
639             =cut
640              
641             sub get_srpm {
642             my ($self, $pkgname, %options) = @_;
643              
644             my $odir = File::Tempdir->new($options{destdir});
645              
646             $self->checkout_pkg($pkgname, $odir->name(), %options) or return 0;
647              
648             my $revision = $self->get_dir_lastrev($odir->name(), %options) or return;
649              
650             MDV::Repsys::set_rpm_dirs($odir->name());
651             RPM4::add_macro("_srcrpmdir " . ($options{destdir} || getcwd()));
652            
653             my $specfile = $self->get_final_spec(
654             $odir->name() . "/SPECS/$pkgname.spec",
655             %options,
656             pkgname => $pkgname,
657             destdir => $odir->name(),
658             );
659              
660             my $spec = RPM4::specnew($specfile, undef, '/', undef, 1, 0) or do {
661             $self->{error} = "Can't parse specfile $specfile";
662             return 0;
663             };
664              
665             RPM4::setverbosity(0) unless($self->{verbosity});
666             RPM4::del_macro("_signature");
667             $spec->build([ qw(PACKAGESOURCE) ]);
668             return ($revision, $spec->srcrpm());
669              
670             1;
671             }
672              
673             =head2 create_pkg($pkgname)
674              
675             Create a package directory on the svn.
676              
677             =cut
678              
679             sub create_pkg {
680             my ($self, $pkgname, %options) = @_;
681              
682             my $pkgurl_parent = $self->get_pkgurl_parent($pkgname, %options);
683             my $pkgurl = $self->get_pkgurl($pkgname, %options);
684              
685             if ($self->_check_url_exists($pkgurl, %options)) {
686             $self->{error} = "$pkgname is already inside svn";
687             return;
688             }
689              
690             my $message = $options{message} || "Create $pkgname";
691             $self->{svn}->log_msg(sub {
692             $_[0] = \$message;
693             return 0;
694             });
695             $self->_print_msg(1, "Creating %s", $pkgname);
696             $self->{svn}->mkdir([ $pkgurl_parent, $pkgurl, "$pkgurl/SOURCES", "$pkgurl/SPECS" ], );
697             $self->{svn}->log_msg(undef);
698              
699             1;
700             }
701              
702             =head2 import_pkg($rpmfile, %options)
703              
704             Import a source package into the svn.
705              
706             =cut
707              
708             sub import_pkg {
709             my ($self, $rpmfile, %options) = @_;
710              
711             my $h = RPM4::rpm2header($rpmfile) or do {
712             $self->{error} = "Can't read rpm file $rpmfile";
713             return;
714             };
715             if($h->hastag('SOURCERPM')) {
716             $self->{error} = "$rpmfile is not a source package";
717             return;
718             }
719             my $pkgname = $h->queryformat('%{NAME}');
720              
721             if ($self->_check_url_exists($self->get_pkgurl($pkgname), %options)) {
722             $self->{error} = "$pkgname is already inside svn";
723             return;
724             }
725              
726             my $odir = File::Tempdir->new($options{destdir});
727              
728             eval {
729             $self->{svn}->checkout(
730             $self->{config}->val('global', 'default_parent') || '',
731             $odir->name(),
732             'HEAD', # What else ??
733             0, # Don't be recursive !!
734             );
735             };
736             if ($@) {
737             $self->{error} = "Can't checkout " . $self->{config}->val('global', 'default_parent') . ": $@";
738             return;
739             }
740              
741             my $pkgdir = $odir->name() . "/$pkgname";
742              
743             $self->{svn}->update(
744             $pkgdir,
745             'HEAD',
746             0,
747             );
748              
749             if (-d $pkgdir) {
750             $self->{error} = "$pkgname is already inside svn";
751             return;
752             }
753              
754             $self->{svn}->mkdir($pkgdir);
755             $self->{svn}->mkdir("$pkgdir/current");
756              
757             $self->_print_msg(1, 'Importing %s', $rpmfile);
758             MDV::Repsys::set_rpm_dirs("$pkgdir/current");
759             my ($specfile, $cookie) = MDV::Repsys::extract_srpm(
760             $rpmfile,
761             "$pkgdir/current",
762             ) or do {
763             $self->{error} = MDV::Repsys::repsys_error();
764             return 0;
765             };
766            
767             MDV::Repsys::set_rpm_dirs("$pkgdir/current");
768             MDV::Repsys::sync_source("$pkgdir/current", $specfile) or do {
769             $self->{error} = MDV::Repsys::repsys_error();
770             return;
771             };
772              
773             return if(!$self->splitchangelog(
774             $specfile,
775             %options,
776             pkgname => $pkgname,
777             ));
778            
779             $self->_commit(
780             $pkgdir,
781             %options,
782             pkgname => $pkgname,
783             message => $options{message} || "Import $pkgname",
784             );
785             }
786              
787             sub _commit {
788             my ($self, $dir, %options) = @_;
789             my $pkgname = $options{pkgname} || $dir;
790              
791             my $message = $options{message};
792              
793             $self->{svn}->log_msg(
794             $message ?
795             sub {
796             $_[0] = \$message;
797             return 0;
798             } :
799             sub {
800             MDV::Repsys::_commit_editor($_[0])
801             }
802             );
803             $self->_print_msg(1, "Committing %s", $pkgname);
804             my $revision = -1;
805             if (!$self->{nocommit}) {
806             my $info = $self->{svn}->commit($dir, 0) unless($self->{nocommit});
807             $revision = $info->revision() if ($info);
808             }
809             $self->{svn}->log_msg(undef);
810              
811             $revision;
812             }
813              
814              
815             =head2 splitchangelog($specfile, %options)
816              
817             Strip the changelog from a specfile and commit it into the svn.
818              
819             =cut
820              
821             sub splitchangelog {
822             my ($self, $specfile, %options) = @_;
823              
824             my ($basename) = $specfile =~ m!(?:.*/)?(.*)$!;
825            
826             my $pkgname = $options{pkgname};
827              
828             if (!$pkgname) {
829             my $spec = RPM4::specnew($specfile, undef, '/', undef, 1, 0) or do {
830             $self->{error} = "Can't parse specfile $specfile";
831             return;
832             };
833             my $h = $spec->srcheader or return; # can't happend
834             $pkgname = $h->queryformat('%{NAME}');
835             }
836              
837             my ($changelog, $newspec) = MDV::Repsys::_strip_changelog($specfile);
838              
839             if (!$changelog) {
840             return -1;
841             }
842             my $revision = -1;
843              
844             my $odir = File::Tempdir->new();
845              
846             my $resyslog = $self->{config}->val('log', 'oldurl');
847             if ($resyslog) {
848             my $oldchangelogurl = "$resyslog/$pkgname";
849             eval {
850             $self->{svn}->checkout(
851             $resyslog,
852             $odir->name(),
853             'HEAD',
854             0,
855             );
856             };
857             if ($@) {
858             $self->{error} = "Can't checkout $resyslog: $@";
859             return;
860             }
861             $self->{svn}->update(
862             $odir->name() . "/$pkgname",
863             'HEAD',
864             1
865             );
866             if (! -d $odir->name() . "/$pkgname") {
867             $self->{svn}->mkdir($odir->name() . "/$pkgname");
868             }
869             if (-f $odir->name() . "/$pkgname/log") {
870             $self->{error} = "An old changelog file already exists for $pkgname, please fix";
871             return;
872             }
873             if (open(my $logh, ">", $odir->name() . "/$pkgname/log")) {
874             print $logh $changelog;
875             close($logh);
876             } else {
877             $self->{error} = "Can't open new log file";
878             return 0;
879             }
880             $self->{svn}->add($odir->name() . "/$pkgname/log", 0);
881             my $message = $options{message} || "import old changelog for $pkgname";
882             $self->{svn}->log_msg(sub {
883             $_[0] = \$message;
884             return 0;
885             });
886             $self->_print_msg(1, "Committing %s/log", $pkgname);
887             if (!$self->{nocommit}) {
888             my $info;
889             eval {
890             $info = $self->{svn}->commit($odir->name(), 0);
891             };
892             if ($@) {
893             $self->{error} = "Error while commiting changelog: $@";
894             return;
895             }
896             $revision = $info->revision();
897             }
898              
899             $self->{svn}->log_msg(undef);
900             }
901              
902             seek($newspec, 0, 0);
903             if (open(my $oldspec, ">", $specfile)) {
904             while (<$newspec>) {
905             print $oldspec $_;
906             }
907             close($oldspec);
908             } else {
909             $self->{error} = "Can't open $specfile for writing: $!";
910             return;
911             }
912             $revision;
913             }
914              
915             =head2 commit($dir, %options)
916              
917             Synchronize sources found into the spec and commit files into the svn.
918              
919             =cut
920              
921             sub commit {
922             my ($self, $dir, %options) = @_;
923             my $specfile = (glob('SPECS/*.spec'))[0];
924              
925             MDV::Repsys::set_rpm_dirs($dir);
926             my ($toadd, $todel) = MDV::Repsys::_find_unsync_source(
927             working_dir => $dir,
928             specfile => $specfile,
929             svn => $self->{svn},
930              
931             ) or do {
932             $self->{error} = MDV::Repsys::repsys_error();
933             return;
934             };
935              
936             my $callback = $options{callback} || sub { 1; };
937             if (@{$toadd || []} + @{$todel || []}) {
938             if ($callback->($toadd, $todel)) {
939             MDV::Repsys::_sync_source(
940             svn => $self->{svn},
941             needadd => $toadd,
942             needdel => $todel,
943             );
944             }
945             }
946              
947             $self->_commit(
948             $dir,
949             %options,
950             );
951             }
952              
953             sub _check_url_exists {
954             my ($self, $url, %options) = @_;
955             my ($parent, $leaf) = $url =~ m!(.*)?/+([^/]*)/*$!;
956              
957             my $leafs;
958              
959             eval {
960             $leafs = $self->{svn}->ls(
961             $parent,
962             $options{revision} || $self->{default}{revision},
963             0,
964             );
965             };
966             if ($@) {
967             $self->{error} = "Can't list $parent: $@";
968             return;
969             }
970             exists($leafs->{$leaf})
971             }
972              
973             =head2 tag_pkg($pkgname, %options)
974              
975             TAG a package into the svn, aka copy the current tree into
976             VERSION/RELEASE/. The operation is done directly into the svn.
977              
978             =cut
979              
980             sub tag_pkg {
981             my ($self, $pkgname, %options) = @_;
982              
983             my ($handle, $tempspecfile) = tempfile();
984              
985             eval {
986             $self->{svn}->cat(
987             $handle,
988             $self->get_pkgurl($pkgname) . "/SPECS/$pkgname.spec",
989             $options{revision} || $self->{default}{revision},
990             );
991             };
992             if ($@) {
993             $self->{error} = "Can't get specfile " . $self->get_pkgurl($pkgname) . "/SPECS/$pkgname.spec: $@";
994             return;
995             }
996              
997             close($handle);
998              
999             my $spec = RPM4::specnew($tempspecfile, undef, '/', undef, 1, 1) or do {
1000             $self->{error} = "Can't parse $tempspecfile";
1001             return 0;
1002             };
1003             my $header = $spec->srcheader or return 0;
1004              
1005             my $ev = $header->queryformat('%|EPOCH?{%{EPOCH}:}:{}|%{VERSION}');
1006             my $re = $header->queryformat('%{RELEASE}');
1007              
1008             my $tagurl = $self->get_pkgurl($pkgname, pkgversion => 'releases');
1009             my $pristineurl = $self->get_pkgurl($pkgname, pkgversion => 'pristine');
1010              
1011             if (!$self->_check_url_exists($tagurl)) {
1012             $self->{svn}->mkdir($tagurl);
1013             }
1014              
1015             if (!$self->_check_url_exists("$tagurl/$ev")) {
1016             $self->{svn}->mkdir("$tagurl/$ev");
1017             }
1018              
1019             if ($self->_check_url_exists("$tagurl/$ev/$re")) {
1020             $self->{error} = "$tagurl/$ev/$re already exists";
1021             return;
1022             }
1023              
1024             my $message = "Tag release $ev-$re";
1025             $self->{svn}->log_msg(
1026             sub {
1027             $_[0] = \$message;
1028             return 0;
1029             }
1030             );
1031             $self->_print_msg(1, 'Tagging %s to %s/%s', $pkgname, $ev, $re);
1032             $self->{svn}->copy(
1033             $self->get_pkgurl($pkgname),
1034             $options{revision} || $self->{default}{revision},
1035             "$tagurl/$ev/$re",
1036             );
1037             eval {
1038             $self->{svn}->delete($pristineurl, 1);
1039             };
1040             $self->_print_msg(1, 'Tagging %s to pristine', $pkgname);
1041             $self->{svn}->copy(
1042             $self->get_pkgurl($pkgname),
1043             $options{revision} || $self->{default}{revision},
1044             $pristineurl
1045             );
1046             $self->{svn}->log_msg(undef);
1047            
1048             1;
1049             }
1050              
1051             =head2 get_pkg_info($pkgname, %options)
1052              
1053             Return a hash containing usefull information about $pkgname:
1054              
1055             =over 4
1056              
1057             =item pkgname
1058              
1059             The name of the package
1060              
1061             =item size
1062              
1063             The size of the package (sum of files size)
1064              
1065             =item last_rev
1066              
1067             The revision of the last changed
1068              
1069             =item last_author
1070              
1071             The author of the last change
1072              
1073             =item last_time
1074              
1075             The time of last change (integer value, use loacaltime to have a human
1076             readable value)
1077              
1078             =back
1079              
1080             =cut
1081              
1082             sub get_pkg_info {
1083             my ($self, $pkgname, %options) = @_;
1084             my $url = $self->get_pkgurl($pkgname, %options);
1085             my $leafs;
1086              
1087             eval {
1088             $leafs = $self->{svn}->ls(
1089             $url,
1090             $options{revision} || $self->{default}{revision},
1091             1,
1092             );
1093             };
1094             if ($@) {
1095             $self->{error} = "Can't get information from $url: $@";
1096             return;
1097             }
1098             my %info = (
1099             pkgname => $pkgname,
1100             last_rev => 0,
1101             size => 0,
1102             );
1103             foreach my $leaf (%{$leafs || {}}) {
1104             defined($leafs->{$leaf}) or next;
1105             $info{size} += $leafs->{$leaf}->size;
1106             if ($leafs->{$leaf}->created_rev > $info{last_rev}) {
1107             $info{last_rev} = $leafs->{$leaf}->created_rev();
1108             $info{last_time} = $leafs->{$leaf}->time();
1109             $info{last_author} = $leafs->{$leaf}->last_author();
1110             }
1111             }
1112              
1113             %info;
1114             }
1115              
1116             =head2 submit($pkgname, %options)
1117              
1118             Submit the package on the build host.
1119              
1120             =cut
1121              
1122             sub submit {
1123             my ($self, $pkgname, %options) = @_;
1124            
1125             my $pkgurl_parent = $self->get_pkgurl_parent($pkgname, %options);
1126             if (!$self->_check_url_exists($pkgurl_parent, %options)) {
1127             $self->{error} = "$pkgname is not in svn";
1128             return;
1129             }
1130              
1131             my $host = $self->{config}->val('global', 'default_parent');
1132             $host = (split("/", $host))[2];
1133              
1134             my $createsrpm = $self->{config}->val('helper', 'create-srpm');
1135            
1136             # back to default
1137             $options{'target'} ||= $self->{config}->val('submit', 'default');
1138            
1139             # TODO we can also use xml-rpc, even if not implemented on the server side
1140             my @command = (
1141             'ssh',
1142             $host,
1143             $createsrpm,
1144             $pkgurl_parent,
1145             '-r', $options{'revision'},
1146             '-t', $options{'target'}
1147             );
1148             system(@command) == 0;
1149             }
1150              
1151             =head2 cleanup
1152              
1153             This module creates a number of temporary directories; all are deleted when
1154             the program terminates, but with this function you can force a removal of
1155             these directories.
1156              
1157             =cut
1158              
1159             sub cleanup { $_[0]->{_temp_late_destroy} = []; 1; }
1160              
1161             sub DESTROY { goto &cleanup }
1162              
1163             1;
1164              
1165             __END__