#!/usr/bin/perl -w

use strict;
use File::Spec;
use Cwd;
use Getopt::Long;
use vars qw(%Opt $VERSION);

$VERSION = sprintf "%.3f", (sprintf "%d.%03d", q$Revision: 1.2 $ =~ /(\d+)\.(\d+)/) - 1;
# $Id: patchaperlup,v 1.2 2000/02/26 14:26:55 k Exp k $

%Opt = ();

# options get and build are undocumented as they might be considered bloatware
GetOptions( \%Opt, qw( upto=i perldir=s diffdir=s quiet! version+) );

sub verbose {
  return if $Opt{quiet};
  printf STDERR @_;
}

print "Version: $VERSION\n";
if ($Opt{version}) {
  exit;
}

my $perldir = $Opt{perldir} or
    die "Usage $0 --perldir perldirectory-to-alter
    [--diffdir patchdirectory]
    [--upto number-of-last-patch]
    [--quiet]
    [--version]";
die "perldir[$perldir] not found" unless -d $perldir;
open F, "$perldir/Changes" or die "Couldn't open $perldir/Changes: $!";
my $already_patched;
while (<F>) {
  next unless /^\[\s*(\d+)\]\sBy:\s/;
  $already_patched = $1;
  last;
}
$|=1;

my $diffdir = $Opt{diffdir};
# as default we assume the directory that wget chooses if we called wget
$diffdir = "ftp.linux.activestate.com/pub/staff/gsar/APC/diffs"
    unless defined $diffdir;
die "diffdir[$diffdir] not found" unless -d $diffdir;
print "Diffdir: $diffdir\n";
my $diffdir_abs = File::Spec->file_name_is_absolute($diffdir) ?
    $diffdir : File::Spec->catdir(Cwd::cwd,$diffdir);
opendir DIR, $diffdir_abs or die "Couldnt opendir";
my @diffs = sort { $a <=> $b } grep s/^(\d+)\.gz/$1/, readdir DIR;
if ($already_patched > $diffs[0]) {
  verbose "Your patchdirectory starts with patch %d, but your perldir
has the patches up to %d already applied. Skipping those not needed.
", $diffs[0], $already_patched;
  shift @diffs while $diffs[0] <= $already_patched;
}
my $latest = $diffs[-1];
$latest =~ s/\D.*//;
if ($Opt{upto}) {
  if ($Opt{upto} > $latest) {
    die "Invalid option for upto[$Opt{upto}].
Latest patch in your diffdir is $latest\n";
  } elsif ($Opt{upto}<$diffs[0]) {
    die "Invalid option for upto[$Opt{upto}].
First patch in your diffdir is $diffs[0]\n";
  } else {
    pop @diffs while $diffs[-1] > $Opt{upto};
    $latest = $Opt{upto};
  }
}
chdir $perldir or die "Couldn't chdir to $perldir: $!";
print "Perldir: $perldir\n";
my @fails;
my %topatch;
verbose "Prescanning all patch files for contents\n";
my $tmpfile = "tmp.patchls.$$";
open F, "| perl Porting/patchls - > $tmpfile";
for my $d ( @diffs ){
  verbose "\r%10s of %10s to %10s", $d, $diffs[0], $diffs[-1];
  my $pathdiff = "$diffdir_abs/$d.gz";
  die "Couldn't find pathdiff[$pathdiff]" unless -f $pathdiff;
  open G, "zcat $pathdiff|";
  local $/;
  print F <G>;
  close G;
}
close F;
open F, $tmpfile or die;
while (<F>) {
  s/^-:\s//;
  chomp $_;
  my @topatch = grep { -f $_ } split m{ }, $_;
  @topatch{@topatch} = ();
}
close F;
unlink $tmpfile;
@ARGV = keys %topatch;
die "No files to patch. Unexpected error" unless @ARGV;
verbose "\nRemoving CR from %d files\n", scalar @ARGV;
$^I = "";
my $i = 0;
my $b = 0;
while (<>) {
  s/\r$//;
  print;
  if (eof(ARGV)){
    $b += -s $ARGV;
    verbose "\r%5d files, %6d lines, %7d characters", ++$i, $., $b;
  }
}
my $lines = $.;
verbose "\n";
for my $d ( @diffs ){
  my $pathdiff = "$diffdir_abs/$d.gz";
  if (system ("zcat $pathdiff | patch -s -p1 -N")==0) {
    print "Firstpatch: $d\n" if $d==$diffs[0];
  } else {
    push @fails, $d;
  }
  verbose "\rapplied %s", $d;
}
verbose "\n";
if (@fails) {
  verbose "The following patches had errors:\n";
  verbose map {"\t$_\n"} @fails;
  verbose "\n";
  die "Errors while patching\n";
} else {
  print "Lastpatch: $diffs[-1]\n";
}

verbose qq{Now you can make a new perl by running e.g.:
  cd $perldir && ./Configure -des && make test
};


__END__

=head1 NAME

patchaperlup - apply a couple of patches in a perl source directory

=head1 SYNOPSIS

  patchaperlup --perldir perldir
               [ --diffdir diffdir ]
               [ --upto patch-number ]
               [ --quiet ]
               [ --version ]

=head1 DESCRIPTION

This utility runs a batch of jobs that upgrade the most recent
development perl with the bleeding edge patches to produce a snapshot
of the current committed development version of perl. Patching up to a
certain patchnumber is also supported.

The status of this script is very alpha as the applicability of the
assumptions about where the current patches are, how they are named
and treated, etc. has not been discussed with the pumpking and the
perl community. The script is only reflecting current practice which
is subject to change without notice.

The idea is the following:

The user untars a recent perl distribution, mirrors the pumkin's patch
repository, runs this C<patchaperlup> utility, and runs the perl
build commands as usual.

Untarring a recent perl snapshot is usually done with the tar command,
say

  tar xvzf /local/path/to/perl5.5.660.tar.gz

Mirroring the pumpkin's patch repositury can be done with C<wget>,
e.g.

  wget -r -m -v ftp://ftp.linux.activestate.com/pub/staff/gsar/APC/diffs

Now patchaperlup could be run as

  perl patchaperlup --perldir perl5.5.660 \
    --diffdir ftp.linux.activestate.com/pub/staff/gsar/APC/diffs

The --diffdir parameter defaults to the above one, so this command
could be abbreviated to

  perl patchaperlup --perldir perl5.5.660

The batch job is pretty verbose and explains what it is doing. The
reason for the verbosity was that it can take a while until
C<patchaperlup> is finishing.

C<patchaperlup> prints a few mail-header-like lines to standard
output, namely

  Version: version of patchaperlup
  Perldir: perl directory
  Diffdir: directory containing the patches
  Firstpatch: number of the first applied patch
  Lastpatch: number of the last applied patch

All other diagnostics are written to STDERR. These can be turned off
with the --quiet switch.

The --version switch prints the version and exits.

=head1 EXAMPLE

The following shell script built that day's perl at the time of
writing and stuffed it into a directory of its own. Please fill in
your nearest CPAN site in line 2:

  DEVPERL=perl5.5.660
  wget -m -nd -v ftp://cpan.host.and.path/authors/id/GSAR/$DEVPERL.tar.gz
  tar xzf $DEVPERL.tar.gz
  wget -r -m -v ftp://ftp.linux.activestate.com/pub/staff/gsar/APC/diffs
  patchaperlup --perldir $DEVPERL > patchaperlup.out
  LPATCH=`awk '$1=="Lastpatch:"{print $2}' patchaperlup.out`
  mv $DEVPERL $DEVPERL..$LPATCH
  cd $DEVPERL..$LPATCH && ./Configure -des && make test

=head1 PREREQUISITES

The programs C<zcat> and C<patch> must be in your path. Likewise
C<perl> must be available in the path to run the utility C<patchls>
which can be found in recent perl distributions.

=head1 AUTHOR

Andreas Koenig <andreas.koenig@anima.de>

=cut

