#! /usr/bin/perl -w
#
# Written by Adam Byrtek <alpha@debian.org>, 2002
# 
# extfs to handle patches in unified diff format

use bytes;
use strict;
use POSIX;

# standard binaries
my $bzip = "bzip2";
my $gzip = "gzip";
my $file = "file";

# date parsing requires Date::Parse from TimeDate module
my $parsedates = eval "require Date::Parse";


# output unix date in a mc-readable format
sub timef
{
    my @time=localtime($_[0]);
    return sprintf "%02d-%02d-%02d %02d:%02d", $time[4]+1, $time[3],
		   $time[5]+1900, $time[2], $time[1];
}

# parse given string as a date and return unix time
sub datetime
{
    # in case of problems fall back to 0 in unix time
    # note: str2time interprets some wrong values (eg. " ") as 'today'
    if ($parsedates && defined (my $t=str2time($_[0]))) {
	return timef($t);
    }
    return timef(time);
}

# print message on stderr and exit
sub error
{
    print STDERR $_[0], "\n";
    exit 1;
}

# list files affected by patch
sub list
{
    my ($archive)=(quotemeta $_[0]);
    my ($state,$pos,$len,$time);
    my ($f,$fsrc,$fdst,$prefix);

    # use uid and gid from file
    my ($uid,$gid)=(`ls -l $archive`=~/^[^\s]+\s+[^\s]+\s+([^\s]+)\s+([^\s]+)/);

    import Date::Parse if ($parsedates);
    
    # state==1 means diff contents, state==0 means comments
    $state=1; $len=0; $f='';
    while (<I>) {
	if (/^--- /) {
	    # parse diff header
	    if ($state==1) {
		printf "-rw-r--r-- 1 %s %s %d %s %s%s\n", $uid, $gid, $len, datetime($time), $prefix, $f
		  if $f;
		$len=0;
	    }
	    $state=1;

	    error "Can't parse unified diff header"
	      unless ((($_.=<I>).=<I>)=~/^\--- .*\n\+\+\+ .*\n@@ .* @@.*\n$/);
	    ($fsrc)=/^--- ([^\s]+).*\n.*\n.*\n$/;
	    ($fdst)=/^.*\n\+\+\+ ([^\s]+).*\n.*\n$/;
	    ($time)=/^.*\n\+\+\+ [^\s]+\s+([^\t\n]+).*\n.*\n$/;

	    # select filename, conform with (diff.info)Multiple patches
	    $prefix="";
	    if ($fsrc eq "/dev/null") {
		$f=$fdst; $prefix="PATCH-CREATE/";
	    } elsif ($fdst eq "/dev/null") {
		$f=$fsrc; $prefix="PATCH-REMOVE/";
	    } elsif (($fdst eq "/dev/null") && ($fsrc eq "/dev/null")) {
		error "Malformed diff";
	    } elsif (!$fdst && !$fsrc) {
		error "Index: not yet implemented";
	    } else {
		# fewest path name components
		if ($fdst=~s|/|/|g < $fsrc=~s|/|/|g) {
		    $f=$fdst;
		} elsif ($fdst=~s|/|/|g > $fsrc=~s|/|/|g) {
		    $f=$fsrc;
		} else {
		    # shorter base name
		    if (($fdst=~m|^.*/([^/]+)$|,length $1) < ($fsrc=~m|^.*/([^/]+)$|,length $1)) {
			$f=$fdst;
		    } elsif (($fdst=~m|^.*/([^/]+)$|,length $1) > ($fsrc=~m|^.*/([^/]+)$|,length $1)) {
			$f=$fsrc;
		    } else {
			# shortest names
			if (length $fdst < length $fsrc) {
			    $f=$fdst;
			} else {
			    $f=$fsrc;
			}
		    }
		}
	    }
	    $f=$f.".diff";

	} elsif ($state==1 && !/^([+\- \n]|@@)/) {
	    # start of comments, end of diff contents
	    printf "-rw-r--r-- 1 %s %s %d %s %s%s\n", $uid, $gid, $len, datetime($time), $prefix, $f
	      if $f;
	    $state=$len=0;
	}
	$len+=length;
    }
    printf "-rw-r--r-- 1 %s %s %d %s %s%s\n", $uid, $gid, $len, datetime($time), $prefix, $f
      if $f;
}

sub copyout
{
    my ($file,$out)=@_;
    my ($fsrc,$fdst,$found,$state,$buf);

    $file=~s/^(PATCH-(CREATE|REMOVE)\/)?(.*)\.diff$/$3/;
    
    # state==1 means diff contents, state==0 mens comments
    $state=1; $found=0; $buf="";
    while (<I>) {
	if (/^--- /) {
	    # parse diff header
	    last if ($state==1 && $found);
	    $state=1;

	    error "Can't parse unified diff header"
	      unless ((($_.=<I>).=<I>)=~/^\--- .*\n\+\+\+ .*\n@@ .* @@.*\n$/);
	    ($fsrc)=/^--- ([^\s]+).*\n.*\n.*\n$/;
	    ($fdst)=/^.*\n\+\+\+ ([^\s]+).*\n.*\n$/;
	    $found=1 if (($fsrc eq $file) || ($fdst eq $file));

	} elsif ($state==1 && !/^([+\- \n]|@@)/) {
	    # start of comments, end of diff contents
	    last if ($found);
	    $state=0;
	    $buf="";
	}
	$buf.=$_ if ($found || $state==0)
    }
    if ($found) {
	open O, "> $out";
	print O $buf;
	close O;
    }
}

sub copyin
{
    # append diff to archive
    my ($archive,$name,$f)=(quotemeta $_[0],$_[1],quotemeta $_[2]);
    my ($cmd);

    error "File must have .diff or .patch extension"
      unless $name=~/\.(diff|patch)(\.(bz|bz2|gz|z|Z))?$/;

    $_=`$file $f`;
    if (/bzip/) {
	$cmd="$bzip -dc $f";
    } elsif (/gzip/) {
	$cmd="$gzip -dc $f";
    } else {
	$cmd="cat $f";
    }
    
    $_=`$file $archive`;
    if (/bzip/) {
	system "$cmd | $bzip -c >> $archive";
    } elsif (/gzip/) {
	system "$cmd | $gzip -c >> $archive";
    } else {
	system "$cmd >> $archive";
    }
}

sub openread
{
    # open (compressed) archive for reading
    my ($archive) = (quotemeta $_[0]);

    $_=`$file $archive`;
    if (/bzip/) {
	open I, "$bzip -dc $archive |";
    } elsif (/gzip/) {
	open I, "$gzip -dc $archive |";
    } else {
	open I, "< $ARGV[1]";
    }
}


if ($ARGV[0] eq "list") {
    openread $ARGV[1];
    list $ARGV[1];
    exit 0;
} if ($ARGV[0] eq "copyout") {
    openread $ARGV[1];
    copyout ($ARGV[2], $ARGV[3]);
    exit 0;
} if ($ARGV[0] eq "copyin") {
    copyin ($ARGV[1], $ARGV[2], $ARGV[3]);
    exit 0;
}
exit 1;
