File Coverage

blib/lib/Cvs/Trigger.pm
Criterion Covered Total %
statement 42 246 17.0
branch 0 54 0.0
condition 0 11 0.0
subroutine 14 34 41.1
pod 3 6 50.0
total 59 351 16.8


line stmt bran cond sub pod time code
1             ###########################################
2             package Cvs::Trigger;
3             ###########################################
4              
5 4     4   352582 use strict;
  4         13  
  4         136  
6 4     4   22 use warnings;
  4         8  
  4         121  
7 4     4   21 use File::Spec;
  4         19  
  4         113  
8 4     4   20 use Log::Log4perl qw(:easy);
  4         6  
  4         22  
9 4     4   6499 use Data::Dumper;
  4         42760  
  4         292  
10 4     4   3329 use Cache::FileCache;
  4         209987  
  4         207  
11 4     4   37 use Storable qw(freeze thaw);
  4         12  
  4         218  
12 4     4   3531 use POSIX;
  4         30980  
  4         33  
13              
14             our $VERSION = "0.04";
15              
16             ###########################################
17             sub new {
18             ###########################################
19 0     0 0   my($class, %options) = @_;
20              
21 0           my $self = {
22             routines => { 'commitinfo' => \&commitinfo,
23             'loginfo' => \&loginfo,
24             'verifymsg' => \&verifymsg,
25             },
26             cache_default_expires_in => 3600,
27             cache_auto_purge_interval => 1800,
28             cache_namespace => "cvs",
29             %options,
30             };
31              
32 0 0         if($self->{cache}) {
33 0           $self->{file_cache} = Cache::FileCache->new({
34             namespace => $self->{cache_namespace},
35             default_expires_in => $self->{cache_default_expires_in},
36             auto_purge_interval => $self->{cache_auto_purge_interval},
37             });
38             }
39              
40 0           bless $self, $class;
41             }
42              
43             ###########################################
44             sub parse {
45             ###########################################
46 0     0 0   my($self, $type, $opts) = @_;
47              
48 0 0         $type = $self->{type} unless defined $type;
49 0 0         LOGDIE "No type defined" unless defined $type;
50              
51 0 0         if(exists $self->{routines}->{$type}) {
52 0           DEBUG "Running $type (pid=$$ ppid=", getppid(), ")";
53 0           $self->{routines}->{$type}->($self, $opts);
54             } else {
55 0           LOGDIE "Unknown type: $type";
56             }
57             }
58              
59             ###########################################
60             sub commitinfo {
61             ###########################################
62 0     0 1   my($self, $opts) = @_;
63              
64 0   0       $opts ||= {};
65 0   0       my $n_opt_args = ($opts->{n_opt_args} || 0);
66 0           my $trigger = "commitinfo";
67 0           my @nargv = @ARGV[$n_opt_args .. $#ARGV];
68              
69 0 0         if(@nargv < 2) {
70 0           LOGDIE "Argument error: $trigger expects at least 2 parameters ",
71             "(got @nargv)";
72             }
73              
74 0           my($repo_dir, @files) = @nargv;
75 0           my @opts = @ARGV[1 .. $n_opt_args-2];
76              
77 0           my $res = {
78             repo_dir => $repo_dir,
79             files => \@files,
80             opts => \@opts,
81             trigger => $trigger,
82             argv => \@nargv,
83             };
84              
85 0 0         if($self->{file_cache}) {
86 0           $self->_cache_set($repo_dir, @files);
87             }
88              
89 0           DEBUG "$trigger return parameters: ", Dumper($res);
90              
91 0           return $res;
92             }
93              
94             ###########################################
95             sub verifymsg {
96             ###########################################
97 0     0 1   my($self) = @_;
98              
99 0           DEBUG "Running verifymsg ($$ ", getppid(), ")";
100              
101 0 0         if(@ARGV < 1) {
102 0           LOGDIE "Argument error: verifymsg expects at least 1 parameter";
103             }
104              
105 0           my $tmp_file = $ARGV[-1];
106              
107 0           my $data = _slurp($tmp_file);
108              
109 0           my @opts = ();
110 0 0         @opts = @ARGV[1 .. $#ARGV-1] if @ARGV > 1;
111              
112 0           my $res = {
113             opts => \@opts,
114             message => $data,
115             };
116              
117 0 0         if($self->{cache}) {
118 0           $res->{cache} = $self->_cache_get();
119              
120 0           my $ttl = $self->_cache_ttl_dec();
121              
122 0 0         if($ttl < 1) {
123 0           DEBUG "ttl=$ttl: Removing the cache for ", getppid();
124 0           $self->{file_cache}->remove(getppid());
125             } else {
126             # Don't remove it yet, this could be a multi-dir check-in and
127             # another verifymsg might be following and still rely on the cache.
128 0           DEBUG "ttl=$ttl: Keeping the cache";
129             }
130             }
131              
132 0           DEBUG "verifymsg parameters: ", Dumper($res);
133 0           return $res;
134             }
135              
136             #2006/04/08 13:29:22 argv=verifymsg /tmp/cvsDYgcCY
137             #2006/04/08 13:29:22 Slurping data from /tmp/cvsDYgcCY
138             #2006/04/08 13:29:22 Read (7)[foobar.] from /tmp/cvsDYgcCY
139             #2006/04/08 13:29:22 data=foobar
140             #2006/04/08 13:29:22 Slurping data from /tmp/cvsDYgcCY
141             #2006/04/08 13:29:22 Read (7)[foobar.] from /tmp/cvsDYgcCY
142             #2006/04/08 13:29:22 msg=foobar
143             #2006/04/08 13:29:22 pid=20651 ppid=20644
144              
145             ###########################################
146             sub loginfo {
147             ###########################################
148 0     0 1   my($self, $opts) = @_;
149              
150 0   0       $opts ||= {};
151 0   0       my $rev_fmt = ($opts->{rev_fmt} || undef);
152              
153 0           my @opts = @ARGV;
154              
155 0           my $data = join '', ;
156              
157 0           DEBUG "Running loginfo ($$ ", getppid(), "): argv=[@ARGV] data=[$data]";
158              
159 0           my $res = {
160             opts => \@opts,
161             };
162              
163 0           $self->loginfo_message_parse($data, $res, $rev_fmt);
164              
165 0           DEBUG "loginfo returns ", Dumper($res);
166              
167 0           return $res;
168             }
169              
170             ###########################################
171             sub loginfo_message_parse {
172             ###########################################
173 0     0 0   my($self, $data, $res, $rev_fmt) = @_;
174              
175 0           DEBUG "Parsing $data";
176              
177 0 0         if(defined $rev_fmt) {
178 0 0         if($rev_fmt ne "sVv") {
179 0           LOGDIE "For now, only 'sVv' is supported ",
180             "as a revision info format (got '$rev_fmt')";
181             }
182             # Extract/remove first line
183 0           my($line) = ($data =~ /(.*)/);
184 0           $data =~ s/(.*)\n//;
185              
186 0           DEBUG "Extracted revision line $line";
187              
188 0           my($path, @fields) = split ' ', $line;
189 0           for(@fields) {
190 0           my($file, $rev1, $rev2) = split /,/, $_;
191 0           $res->{revs}->{$file} = [$rev1, $rev2];
192             }
193             }
194              
195 0 0         if($data =~
196             m#Update\sof\s(.*)\n
197             In\sdirectory\s(.*?):(.*)\n\n
198             #x) {
199 0           $res->{repo_dir} = $1;
200 0           $res->{host} = $2;
201 0           $res->{local_dir} = $3;
202             }
203              
204 0 0         if($data =~ m#Modified\sFiles:\n#gx) {
205 0           while($data =~ /^\s+(.*)/mg) {
206 0           my @files = split ' ', $1;
207 0           push @{ $res->{files} }, @files;
  0            
208             }
209             }
210              
211 0 0         if($data =~ m#Log\sMessage:\n(.*)#sgx) {
212 0           $res->{message} = $1;
213             }
214              
215 0           return $res;
216             }
217              
218             #Update of /tmp/vHmsem4xFV/cvsroot/m/a
219             #In directory mybox:/tmp/vHmsem4xFV/local_root/m/a
220             #
221             #Modified Files:
222             # a1.txt
223             #Log Message:
224             #m/a/a1.txt-check-in-message
225              
226             #2006/04/08 13:29:22 argv=loginfo
227             #2006/04/08 13:29:22 pid=20656 ppid=20653
228             #2006/04/08 13:29:22 stdin: a txt,1.20,1.21
229             #2006/04/08 13:29:22 stdin: Update of /home/mschilli/testcvs/a
230             #2006/04/08 13:29:22 stdin: In directory mybox:/mnt/big2/mschilli.do.not.delete/tmp/a
231             #2006/04/08 13:29:22 stdin:
232             #2006/04/08 13:29:22 stdin: Modified Files:
233             #2006/04/08 13:29:22 stdin: txt
234             #2006/04/08 13:29:22 stdin: Log Message:
235             #2006/04/08 13:29:22 stdin: foobar
236             #2006/04/08 13:29:22 stdin:
237              
238             ###########################################
239             sub _slurp {
240             ###########################################
241 0     0     my($file) = @_;
242              
243 0           local $/ = undef;
244              
245 0 0         open FILE, "<$file" or
246             LOGDIE "Cannot open $file ($!)";
247 0           my $data = ;
248 0           close FILE;
249              
250 0           return $data;
251             }
252              
253             #2006/04/08 13:29:11 argv=commitinfo /home/mschilli/testcvs/a txt
254             #2006/04/08 13:29:11 Slurping data from /home/mschilli/testcvs/a
255             #2006/04/08 13:29:11 Read (0)[] from /home/mschilli/testcvs/a
256             #2006/04/08 13:29:11 data=
257             #2006/04/08 13:29:11 pid=20645 ppid=20644
258             #
259              
260             ###########################################
261             sub _cache_set {
262             ###########################################
263 0     0     my($self, $repo_dir, @files) = @_;
264              
265 0           my $ppid = getppid();
266              
267 0           my $cdata = $self->_cache_get();
268              
269 0           for my $file (@files) {
270 0           DEBUG "Caching $repo_dir/$file under ppid=$ppid";
271              
272 0           push @{ $cdata->{$repo_dir} }, $file;
  0            
273             }
274 0           $cdata->{_ttl} += 1;
275 0           DEBUG "Setting $ppid cache to ", Dumper($cdata);
276 0           $self->{file_cache}->set($ppid, freeze $cdata);
277             }
278              
279             ###########################################
280             sub _cache_ttl_dec {
281             ###########################################
282 0     0     my($self) = @_;
283              
284 0           my $ppid = getppid();
285              
286 0           my $cdata = $self->_cache_get();
287 0           $cdata->{_ttl}--;
288              
289 0           $self->{file_cache}->set($ppid, freeze $cdata);
290              
291 0           return $cdata->{_ttl};
292             }
293              
294             ###########################################
295             sub _cache_get {
296             ###########################################
297 0     0     my($self) = @_;
298              
299 0           my $ppid = getppid();
300              
301 0           my $cdata;
302              
303 0 0         if(my $c = $self->{file_cache}->get($ppid)) {
304 0           DEBUG "Cache hit on ppid=$ppid";
305 0           $cdata = thaw $c;
306             } else {
307 0           DEBUG "Cache miss on ppid=$ppid";
308 0           $cdata = { _ttl => 0 };
309             }
310              
311 0           return $cdata;
312             }
313              
314             ###########################################
315             package Cvs::Temp;
316             ###########################################
317 4     4   19253 use strict;
  4         9  
  4         144  
318 4     4   61 use warnings;
  4         7  
  4         159  
319 4     4   18 use File::Temp qw(tempdir);
  4         7  
  4         221  
320 4     4   4375 use Sysadm::Install qw(:all);
  4         90709  
  4         39  
321 4     4   1254 use Log::Log4perl qw(:easy);
  4         6  
  4         39  
322 4     4   2401 use Cwd;
  4         5  
  4         10556  
323              
324             ###########################################
325             sub new {
326             ###########################################
327 0     0     my($class, %options) = @_;
328              
329 0           my $self = {
330             dir => tempdir(CLEANUP => 1),
331             %options,
332             };
333              
334 0           $self->{cvsroot} = "$self->{dir}/cvsroot";
335 0           $self->{local_root} = "$self->{dir}/local_root";
336 0           $self->{out_dir} = "$self->{dir}/out_dir";
337 0           $self->{bin_dir} = "$self->{dir}/bin";
338              
339 0           mkd $self->{local_root};
340 0           mkd $self->{out_dir};
341 0           mkd $self->{bin_dir};
342              
343 0           DEBUG "tempdir = $self->{dir}";
344              
345 0 0         $self->{cvs_bin} = bin_find("cvs") unless defined $self->{cvs_bin};
346 0 0         $self->{perl_bin} = bin_find("perl") unless
347             defined $self->{perl_bin};
348              
349 0 0         if(! defined $self->{cvs_bin}) {
350 0           LOGDIE "Cannot find 'cvs' binary in your PATH.";
351             }
352              
353 0           my($stdout, $stderr, $rc) = tap $self->{cvs_bin}, "-v";
354 0 0 0       if($rc == 0 and $stdout =~ /(\d+\.\d+)/) {
355 0           $self->{cvs_version} = $1;
356             } else {
357 0           LOGDIE "Cannot determine CVS version ($stderr)";
358             }
359              
360 0           bless $self, $class;
361              
362 0           return $self;
363             }
364              
365             ###########################################
366             sub init {
367             ###########################################
368 0     0     my($self) = @_;
369              
370 0           $self->cvs_cmd("init");
371 0           DEBUG "New cvs created in $self->{cvsroot}";
372              
373 0           cd $self->{local_root};
374 0           $self->cvs_cmd("co", "CVSROOT");
375 0           cdback;
376             }
377              
378             ###########################################
379             sub test_trigger_code {
380             ###########################################
381 0     0     my($self, $type, $cache, $parse_opt) = @_;
382              
383 0           my $script = <<'EOT';
384             _shebang_
385             use lib '_cwd_/blib/lib';
386             use lib '_cwd_/blib/arch';
387             use Cvs::Trigger qw(:all);
388             use YAML qw(DumpFile);
389             use Log::Log4perl qw(:easy);
390             Log::Log4perl->easy_init({ level => $DEBUG, file => ">>_logfile_",
391             layout => "%F{1}-%L: %m%n" });
392             DEBUG "_type_ trigger starting @ARGV";
393             my $c = Cvs::Trigger->new(_cache_);
394             my $ret = $c->parse("_type_", _parse_opt_);
395             my $count = 1;
396             while(-f "_tmpfile_.$count") {
397             $count++;
398             }
399             DEBUG "Creating _tmpfile_.$count";
400             DumpFile "_tmpfile_.$count", $ret;
401             EOT
402              
403 0           my $shebang = "#!" . $self->{perl_bin};
404 0           $script =~ s/_shebang_/$shebang/g;
405              
406 0           $script =~ s#_tmpfile_#$self->{out_dir}/trigger.yml#g;
407 0           $script =~ s#_cwd_#cwd()#ge;
  0            
408 0           $script =~ s#_logfile_#$self->{out_dir}/log#g;
409 0           $script =~ s/_type_/$type/g;
410 0 0         if($cache) {
411 0           $script =~ s/_cache_/cache => 1/g;
412             } else {
413 0           $script =~ s/_cache_//g;
414             }
415              
416 0 0         if($parse_opt) {
417 0           $script =~ s/_parse_opt_/$parse_opt/g;
418             } else {
419 0           $script =~ s/_parse_opt_/undef/g;
420             }
421              
422 0           DEBUG "Test trigger code: $script";
423              
424 0           return $script;
425             }
426              
427             ###########################################
428             sub module_import {
429             ###########################################
430 0     0     my($self) = @_;
431              
432 0           DEBUG "Importing module";
433              
434 0           cd $self->{local_root};
435              
436 0           mkd "m/a/b";
437 0           blurt "a1text", "m/a/a1.txt";
438 0           blurt "a2text", "m/a/a2.txt";
439 0           blurt "btext", "m/a/b/b.txt";
440              
441 0           cd "m";
442 0           $self->cvs_cmd("import", "-m", "msg", "m", "tag1", "tag2");
443 0           cdback;
444              
445 0           cd $self->{local_root};
446 0           rmf "m";
447 0           cdback;
448              
449 0           $self->cvs_cmd("co", "m");
450 0           cdback;
451             }
452              
453             ###########################################
454             sub files_commit {
455             ###########################################
456 0     0     my($self, @files) = @_;
457              
458 0           my $dir = $self->{local_root};
459 0           cd $dir;
460              
461 0           for my $file (@files) {
462 0           blurt rand(1E10), $file;
463             }
464 0           $self->cvs_cmd("commit", "-m", "@files-check-in-message", @files);
465              
466 0           cdback;
467             }
468              
469             ###########################################
470             sub single_file_commit {
471             ###########################################
472 0     0     my($self, $content, $file, $message) = @_;
473              
474 0           my $dir = $self->{local_root};
475 0           cd $dir;
476              
477 0           blurt $content, $file;
478 0           $self->cvs_cmd("commit", "-m", $message, $file);
479              
480 0           cdback;
481             }
482              
483             ###########################################
484             sub admin_rebuild {
485             ###########################################
486 0     0     my($self) = @_;
487              
488 0           my $dir = "$self->{local_root}/CVSROOT";
489 0           cd $dir;
490              
491 0           $self->cvs_cmd("commit", "-m", "admin rebuild", ".");
492              
493 0           cdback;
494             }
495              
496             ###########################################
497             sub cvs_cmd {
498             ###########################################
499 0     0     my($self, @cmd) = @_;
500              
501 0           unshift @cmd, $self->{cvs_bin}, "-d", $self->{cvsroot};
502 0           DEBUG "Running CVS command @cmd";
503              
504 0           my($stdout, $stderr, $rc) = tap @cmd;
505              
506 0 0         if($rc) {
507 0           LOGDIE "@cmd failed: $stderr";
508             }
509              
510 0 0         if($stderr) {
511 0           ERROR "cvs cmd warning $stderr";
512             }
513              
514 0           DEBUG "@cmd succeeded: $stdout";
515             }
516              
517              
518             ###########################################
519             sub loginfo_line {
520             ###########################################
521 0     0     my($self, $script) = @_;
522              
523             # The CVS folks had the glorious idea to change the loginfo format
524             # in 1.12 in a non-backward-compatible way. What were they thinking?
525 0 0         if($self->{cvs_version} < 1.12) {
526 0           return "DEFAULT ((echo %{sVv}; cat) | $script)";
527             }
528              
529 0           return "DEFAULT ((echo %1{sVv}; cat) | $script)";
530             }
531              
532             ###########################################
533             sub latest_yml {
534             ###########################################
535 0     0     my($self, $index) = @_;
536              
537 0           my $dir = $self->{out_dir};
538 0           my @ymls = sort { -M $b <=> -M $a } <$dir/trigger.yml.*>;
  0            
539              
540 0 0         $index = -1 unless defined $index;
541              
542 0           return $ymls[$index];
543             }
544              
545             1;
546              
547             __END__