Software Freedom Law Center

root/trunk/dev-tools/update-loblaw-test.plx

Revision 45, 16.0 kB (checked in by bkuhn, 1 year ago)
  • Added update/merge/update sequence for Mercurial
  • add -f option to push
Line 
1 #!/usr/bin/perl
2
3 # This script is designed to be run launched by svn's post hooks under the
4 # uid of the Mercurial repository.  It generally lives in ~svn/bin
5 # and called from the repository post hooks
6
7 # This program might be of interest to people who want to do some sort of
8 # operation on files after an SVN commit that requires some set of
9 # commands to be run on the repository's local system.  In other words, if
10 # you really wish that when an SVN commit happened, you could log into the
11 # server and run a bunch of commands against a local checkout of what was
12 # just committed, then this script is for you.
13
14 # The whole thing is a hack -- no question.  If there's a better way to do
15 # this job, I'd love to hear about it.
16
17 # The sub's "safe_read_from_pipe" and read_from_process are:
18 # ====================================================================
19 # Copyright (c) 2000-2004 CollabNet.  All rights reserved.
20 #
21 # This software is licensed as described in the file COPYING, which
22 # you should have received as part of this distribution.  The terms
23 # are also available at http://subversion.tigris.org/license-1.html.
24 # If newer versions of this license are posted there, you may use a
25 # newer version instead, at your option.
26 #
27 # This software consists of voluntary contributions made by many
28 # individuals.  For exact contribution history, see the revision
29 # history and logs, available at http://subversion.tigris.org/.
30 # ====================================================================
31 #
32 #
33 # The rest of the file is:
34 #
35 # Copyright (c) 2006, 2007 Software Freedom Law Center, Inc.
36 #
37 # This software gives you freedom; it is licensed to you under version 3
38 # of the GNU General Public License.
39 #
40 # This software is distributed WITHOUT ANY WARRANTY, without even the
41 # implied warranties of MERCHANTABILITY and FITNESS FOR A PARTICULAR
42 # PURPOSE.  See the GNU General Public License for further details.
43 #
44 # You should have received a copy of the GNU General Public License,
45 # version 3.  If not, see <http://www.gnu.org/licenses/>
46 #
47 # License: GPLv3-only
48
49
50 use strict;
51 use warnings;
52
53 my $SVN = "/usr/bin/svn";
54 my $SVNLOOK = "/usr/bin/svnlook";
55 my $HG = '/usr/bin/hg';
56
57 my $LOBLAW_TRUNK_CHECKOUT = "/home/hg/SVNCheckouts/loblaw-trunk";
58 my $LOBLAW_TEST_HG_CHECKOUT = "/home/hg/HGCheckouts/loblaw-test";
59
60 my $PIPE_FILE = "/home/svn/pipes/loblaw-update";
61
62 use POSIX ":sys_wait_h";
63 use Fcntl;             # for sysopen
64 use Carp;
65 use File::Temp qw/:POSIX tempfile mkstemps/;
66 use File::Path;
67 use File::Basename;
68 use File::Copy;
69 ###############################################################################
70 use Unix::Syslog qw(:subs);
71 use Unix::Syslog qw(:macros);
72 use Carp;
73 {
74 #  my $PAGE_EMAIL = '6463617006@tmomail.net';
75   my $PAGE_EMAIL = 'root@softwarefreedom.org';
76   my %messageHistory;
77
78   sub DoLog ($$$;$) {
79     my($type, $user, $message, $cleanupCode) = @_;
80
81     use Date::Manip;
82     my $NOW = ParseDate("now");
83     syslog LOG_INFO, $message;
84
85     my $lastTime = $messageHistory{$message};
86
87     my $sendIt = 0;
88     if (not defined $lastTime) {
89       $sendIt = 1;
90     } else {
91       my $err;
92       my $sinceLast = DateCalc($lastTime,"+ 10 minutes",\$err);
93       $sendIt = 1 if ($NOW gt $sinceLast);
94     }
95     if ($sendIt) {
96       open(SENDMAIL, "|/usr/sbin/sendmail -f root\@softwarefreedom.org -t");
97       print SENDMAIL "To: $PAGE_EMAIL,root\@softwarefreedom.org";
98       print SENDMAIL ", $user\@softwarefreedom.org" if defined $user;
99       print SENDMAIL "\nFrom: root\@softwarefreedom.org\n",
100       "Subject: LOBLAW,COMMIT_HOOK\n\n$message\n.\n";
101       close(SENDMAIL);
102       if ($? != 0) {
103         syslog LOG_INFO, "Unable to perform sendmail: $!";
104       } else {
105         $messageHistory{$message} = $NOW;
106       }
107     }
108     my $more;
109     $more = &$cleanupCode if defined $cleanupCode and ref $cleanupCode;
110     $message .= "  $more" if (defined $more and $more !~ /^\s*$/);
111     croak $message if $type eq "die";
112     warn $message;
113   }
114   sub DieLog ($;$) {
115     DoLog("die", undef, $_[0], $_[1]);
116   }
117   sub WarnLog ($$) {
118     DoLog("warn", $_[0], $_[1]);
119   }
120 }
121 ###############################################################################
122
123 # Start a child process safely without using /bin/sh.
124 sub safe_read_from_pipe
125 {
126   unless (@_)
127     {
128       DieLog("$0: safe_read_from_pipe passed no arguments.");
129     }
130
131   my $pid = open(SAFE_READ, '-|');
132   unless (defined $pid)
133     {
134       DieLog("$0: cannot fork: $!");
135     }
136   unless ($pid)
137     {
138       open(STDERR, ">&STDOUT")
139         or DieLog("$0: cannot dup STDOUT: $!");
140       exec(@_)
141         or DieLog("$0: cannot exec `@_': $!\n");
142     }
143   my @output;
144   while (<SAFE_READ>)
145     {
146       s/[\r\n]+$//;
147       push(@output, $_);
148     }
149   close(SAFE_READ);
150   my $result = $?;
151   my $exit   = $result >> 8;
152   my $signal = $result & 127;
153   my $cd     = $result & 128 ? "with core dump" : "";
154   if ($signal or $cd)
155     {
156       DieLog("$0: pipe from `@_' failed $cd: exit=$exit signal=$signal\n");
157     }
158   if (wantarray)
159     {
160       return ($result, @output);
161     }
162   else
163     {
164       return $result;
165     }
166 }
167 ###############################################################################
168 # Use safe_read_from_pipe to start a child process safely and return
169 # the output if it succeeded or an error message followed by the output
170 # if it failed.
171 sub read_from_process
172 {
173   unless (@_)
174     {
175       DieLog("$0: read_from_process passed no arguments.");
176     }
177   my ($status, @output) = &safe_read_from_pipe(@_);
178   if ($status)
179     {
180       return ("$0: `@_' failed with this output:", @output);
181     }
182   else
183     {
184       return @output;
185     }
186 }
187 ###############################################################################
188 my $LOCK_FILE = "/home/hg/locks/update-loblaw-test-unpackaged-lock";
189
190 my $LOCK_CLEANUP_CODE = sub {
191   return (unlink($LOCK_FILE) != 1) ?
192     "Failed unlink of $LOCK_FILE.  Could cause trouble." :
193     "";
194 };
195
196 ###############################################################################
197 ###############################################################################
198 ###############################################################################
199 sub DoUpdate ($) {
200   my ($revision) = @_;
201
202   chdir $LOBLAW_TRUNK_CHECKOUT or
203     DieLog("unable to change directory to $LOBLAW_TRUNK_CHECKOUT: $!",
204            $LOCK_CLEANUP_CODE);
205
206   my @cleanupLines = read_from_process($SVN, 'cleanup');
207   my @updateLines = read_from_process($SVN, 'update');
208   foreach my $line (@updateLines) {
209     DieLog("conflict found on calendar update: $line", $LOCK_CLEANUP_CODE)
210       if ($line =~ /^\s*C\s+/);
211   }
212   DieLog("update failure; missing revision line or " .
213          "invalid revision number $1 (expect $revision or more) at end of: " .
214          join("\n", @updateLines), $LOCK_CLEANUP_CODE)
215     unless ($updateLines[$#updateLines] =~
216             /^\s*(?:Updated\s+to|At)\s+revision\s+(\d+)[\s\.]*$/i
217             and $1 >= $revision);
218 }
219 ###############################################################################
220
221 # Keep track of which revisions we've finished with.
222 my %CAUGHT_REVISIONS;
223 my %DUAL_REVISIONED_FILE_LIST;
224 my $LOBLAW_LIST_FILE = 'dev-tools/loblaw-unpackaged-files.dat';
225 ###############################################################################
226 sub BuildDualVersionFileList () {
227   %DUAL_REVISIONED_FILE_LIST = ();
228
229   open(FILE_LIST, "<$LOBLAW_LIST_FILE")
230     or DieLog("unable to open $LOBLAW_LIST_FILE: $!");
231   while (my $line = <FILE_LIST>) {
232     next if $line =~ /^\s*#/ or $line =~ /^\s*$/;
233     unless ($line =~
234             m%^\s*([/a-zA-Z0-9\-\.\_]+)\s+\=\>\s+/([/a-zA-Z0-9\-\.\_]+)\s*$%) {
235       # I am extremely picky in this regex to be super-security concious
236       DieLog("Invalid line in $LOBLAW_LIST_FILE: $line");
237     }
238     my($source, $destination) = ($1, $2);
239     $DUAL_REVISIONED_FILE_LIST{$source} = $destination;
240   }
241   close FILE_LIST;
242 }
243 ###############################################################################
244 sub ProcessRevision ($$) {
245   my($repository, $revision) = @_;
246
247   return unless $repository =~ m|Repository/loblaw\s*$|;
248
249   chdir $LOBLAW_TRUNK_CHECKOUT or
250     DieLog("unable to change directory to $LOBLAW_TRUNK_CHECKOUT: $!");
251
252   my @svnLookChangedLines =
253     read_from_process($SVNLOOK, 'changed', $repository, '-r', $revision);
254
255   my @operateOnFiles;
256
257   my $updateFileList = 0;
258   $updateFileList = 1 if (keys %DUAL_REVISIONED_FILE_LIST <= 0);
259
260   foreach my $changeLine (@svnLookChangedLines) {
261     chomp $changeLine;
262     # This script only keeps loblaw-test in sync with trunks' scripts and configs
263
264     my(%vals);
265     $vals{changeLine} = $changeLine;
266     if ($changeLine
267         =~ m|^\s*(\S)\s+trunk/$LOBLAW_LIST_FILE\s*$|) {
268       $updateFileList = 1;
269       next;
270     } elsif ($changeLine =~ m%^\s*(\S)\s+trunk/((scripts|configs)/\S+)\s*$%) {
271       ($vals{operation}, $vals{file}) = ($1, $2);
272     }
273     push(@operateOnFiles, \%vals)
274       if (defined $vals{operation} and $vals{operation} !~ /^\s*I\s*/);
275        # skip those cases where we didn't want to do anything with the file
276        # and those that we *would* do something but were ignored items
277   }
278   my(@lockData) = read_from_process("/usr/bin/lockfile -r 8 $LOCK_FILE");
279
280   DieLog("Failure to aquire lock in $repository for $revision. " .
281          "loblaw-test Mercurial update aborted!")
282     if defined $lockData[0] and $lockData[0] =~ /failed/;
283
284   DoUpdate($revision);  # Make sure we have the latest and the greatest
285
286   BuildDualVersionFileList() if $updateFileList;
287
288   # HG process will read this var.
289   $ENV{EMAIL} = 'svn@softwarefreedom.org';
290   my $commitDone = 0;
291
292   foreach my $vals (@operateOnFiles) {
293     my($operation, $inputFile, $changeLine) =
294       ($vals->{operation}, $vals->{file}, $vals->{changeLine});
295
296     # if we already, through some stretch, we've done this version already,
297     #   then just skip this one.
298     next if defined $CAUGHT_REVISIONS{"$repository,$revision"};
299
300     # See if this is a file we are actually processing over to the hg repository
301     next unless defined $DUAL_REVISIONED_FILE_LIST{$inputFile};
302     my $outputFile = $DUAL_REVISIONED_FILE_LIST{$inputFile};
303
304     chdir $LOBLAW_TEST_HG_CHECKOUT or
305       DieLog("unable to change directory to $LOBLAW_TEST_HG_CHECKOUT: $!");
306
307     read_from_process($HG, 'update');
308     read_from_process($HG, 'merge');
309     read_from_process($HG, 'update');
310
311     chdir $LOBLAW_TRUNK_CHECKOUT or
312       DieLog("unable to change directory to $LOBLAW_TRUNK_CHECKOUT: $!");
313
314     if ($operation =~ /^\s*(?:U|M|\~|L|A)\s*/) {
315       my($dir, $filename);
316       if (-d "$LOBLAW_TRUNK_CHECKOUT/$inputFile") {
317         ($dir, $filename) = ($outputFile, "");
318       } else {
319         ($dir, $filename) = fileparse($outputFile);
320       }
321       eval { mkpath("$LOBLAW_TEST_HG_CHECKOUT/$dir", 0, 0755) };
322       DieLog("Couldn't create $LOBLAW_TEST_HG_CHECKOUT/$dir: $@",
323              $LOCK_CLEANUP_CODE) if ($@);
324
325       # copy the actual file over, if it is a file and not a directory.
326       unless ($filename =~ /^\s*$/) {
327         copy($inputFile, "$LOBLAW_TEST_HG_CHECKOUT/$outputFile")
328           or DieLog("Couldn't copy $inputFile into " .
329                     "$LOBLAW_TEST_HG_CHECKOUT/$outputFile: $!",
330              $LOCK_CLEANUP_CODE);
331       }
332       chdir $LOBLAW_TEST_HG_CHECKOUT or
333         DieLog("unable to change directory to $LOBLAW_TEST_HG_CHECKOUT: $!");
334       read_from_process($HG, 'add', "$outputFile")
335         if ($operation =~ /^\s*A\s*/);
336     } elsif ($operation =~ /^\s*D\s*/) {
337       unlink("$LOBLAW_TEST_HG_CHECKOUT/$outputFile");
338       chdir $LOBLAW_TEST_HG_CHECKOUT or
339         DieLog("unable to change directory to $LOBLAW_TEST_HG_CHECKOUT: $!");
340       read_from_process($HG, 'remove', "$outputFile");
341     } else {
342       DieLog("Invalid svnlook status, $operation, in $changeLine",
343              $LOCK_CLEANUP_CODE);
344     }
345     chdir $LOBLAW_TRUNK_CHECKOUT or
346       DieLog("unable to change directory to $LOBLAW_TRUNK_CHECKOUT: $!");
347     my @svnLog = read_from_process($SVN, '-r', $revision, 'log', $inputFile);
348     my($tempFH, $tempFile) = mkstemps( "/tmp/hgcommitXXXXXX", ".log");
349     print $tempFH "From SVN Revision $revision:\n";
350     foreach my $line (@svnLog) {
351       chomp $line;
352       next if $line =~ /^\s*\-+\s*$/;
353       $line =~ s/^(.*\|.*\|.*)(\|.*)$/$1/;
354       print $tempFH "$line\n";
355     }
356     $tempFH->close();
357     chdir $LOBLAW_TEST_HG_CHECKOUT or
358       DieLog("unable to change directory to $LOBLAW_TEST_HG_CHECKOUT: $!");
359     my @vals = read_from_process($HG, 'commit', '-l', $tempFile,
360                                  "$outputFile");
361     unlink($tempFile);
362     DieLog("Unexpected output from commit: " . join("\n", @vals),
363            $LOCK_CLEANUP_CODE) if (@vals > 0);
364     $commitDone = 1;
365   }
366   chdir $LOBLAW_TEST_HG_CHECKOUT or
367       DieLog("unable to change directory to $LOBLAW_TEST_HG_CHECKOUT: $!");
368   read_from_process($HG, 'push', '-f') if $commitDone;
369   &$LOCK_CLEANUP_CODE;
370 }
371 ###############################################################################
372 sub MakePipe ($) {
373   my($file) = @_;
374
375   unless (-p $file) {   # not a pipe
376     POSIX::mkfifo($file, 0660) or die "can't mknod $file: $!";
377   }
378 }
379 ###############################################################################
380 my %FORK_STATUS;
381
382
383 sub REAPER {
384   my $child;
385   # If a second child dies while in the signal handler caused by the
386   # first death, we won't get another signal. So must loop here else
387   # we will leave the unreaped child as a zombie. And the next time
388   # two children die we get another zombie. And so on.
389   while (($child = waitpid(-1,WNOHANG)) > 0) {
390     $FORK_STATUS{$child}{exitCode} = $?;
391     $FORK_STATUS{$child}{exitString} = $!;
392   }
393   $SIG{CHLD} = \&REAPER;
394 }
395
396 # Don't fear the reaper....
397
398 $SIG{CHLD} = \&REAPER;
399
400 ###############################################################################
401 sub Execute {
402   my %caughtRevisions;
403
404   umask 0007;
405   $| = 1;
406
407   while (1) {
408     foreach my $child (keys %FORK_STATUS) {
409       if (defined $FORK_STATUS{$child}{exitCode}) {
410         if ($FORK_STATUS{$child}{exitCode} == 0) {
411           my $key = "$FORK_STATUS{$child}{repository}," .
412                     $FORK_STATUS{$child}{revision};
413           $CAUGHT_REVISIONS{$key} = time();
414         } else {
415           DieLog("Non-zero exit status ($FORK_STATUS{$child}{exitCode}) " .
416                 "for $FORK_STATUS{$child}{repository} processing of " .
417                 "$FORK_STATUS{$child}{revision}: " .
418                 $FORK_STATUS{$child}{exitString})
419         }
420         delete $FORK_STATUS{$child};
421       }
422     }
423     MakePipe($PIPE_FILE) unless -p $PIPE_FILE;
424     unless (-p $PIPE_FILE) {
425       DieLog("$PIPE_FILE just won't be a pipe."); sleep 60; next;
426     }
427
428     my $ii;
429     for ($ii = 0, select(undef, undef, undef, 0.2);
430          $ii < 25 and not open(FIFO, "<$PIPE_FILE"); $ii++) {
431       select(undef, undef, undef, 0.3); # sleep fraction of second
432     }
433     if ($ii >= 25) {
434       DieLog("Unable to read pipe in $PIPE_FILE after 25 tries," .
435               "sleeping 4 seconds and trying again: $!");
436       sleep 4; next;
437     }
438
439     my(@lines) = <FIFO>;
440     close FIFO;
441     foreach my $line (@lines) {
442       unless ($line =~ /^\s*REPOSITORY:\s*\"([^"]+)\"\s+
443                       REVISION:\s+(\d+)\s*$/x) {
444         DieLog("Invalid line from pipe: $line"); next;
445       }
446       my($repository, $revision) = ($1, $2);
447       my $childPid;
448       if (not defined($childPid = fork())) {
449         DieLog("fork failed when processing $repository with "
450                 . "revision $revision: $!");
451         sleep 60;
452         next;
453       } elsif ($childPid) {
454         # The parent just goes on...
455         $FORK_STATUS{$childPid}{repository} = $repository;
456         $FORK_STATUS{$childPid}{revision} = $revision;
457         next;
458       } else {
459         # The child does the revision process.
460         delete $SIG{CHLD};
461         $0 = "$0 handles $revision in $repository";
462         ProcessRevision($repository, $revision);
463         exit 0;
464       }
465     }
466     # give a chance for the FIFO to recover
467     #    DieLog("Error encountered closing $PIPE_FILE: $!") unless ($? == n0);
468   }
469 }
470 ###############################################################################
471
472 open(PID, ">/home/hg/pids/loblaw-test-update.pid") or
473   DieLog("Unable to open PID file: $!");
474 print PID "$$\n";
475 close(PID);
476 DieLog("Unable to close PID file: $!") unless ($? == 0);
477 Execute();
478
479 __END__
480 # Local variables:
481 # compile-command: "perl -c update-loblaw-test.plx"
482 # End:
Note: See TracBrowser for help on using the browser.

SFLC Main Page

[frdm] Support SFLC