#!/usr/bin/perl

# this script can be used instead of "/usr/bin/patch [options] -i xxx.patch"


use strict;
use warnings;

my @argv = @ARGV;
my $verbose;

my @patches;
while ($argv[-1] =~ /\.(patch|diff)$/) {
    unshift @patches, pop @argv;
}

@patches or die "git-repository--apply-patch can not work with compressed patches\n"; # happens when the patch is passed through stdin

my @opts;
while (@argv) {
    my $s = shift @argv;
    if ($s eq '-b') {
	# we don't want .xxx files
    } elsif ($s eq '--suffix') {
	# we don't want .xxx files
	shift @argv;
    } elsif ($s eq '-i') {
	# ignore "-i"
    } else {
	push @opts, $s;
    }
}

# we really don't want .orig when using git (even when there are hunks)
push @opts, '--no-backup-if-mismatch';

foreach my $patch_file (@patches) { 
    my @header = get_patch_header($patch_file);

    if (grep { /^Subject:/ } @header) {
	my $patch_file_ = fix_git_patch($patch_file);
	system_("git am " . ($patch_file_ || $patch_file));
	$patch_file_ and unlink $patch_file_;
    } else {
	system_("patch @opts -i $patch_file");

	my ($patch_name) = $patch_file =~ m!([^/]*)\.(patch|diff)$!;

	system_('git add .');
	git_commit(commit_line_from_patch_name($patch_name) . 
		     (@header ? "\n\n" . join('', cleanup_patch_header(@header)) : ''),
		   # use the date of the patch for the commit:
		   (stat($patch_file))[9] . " +0000");
    }
}

sub system_ {
    my ($cmd) = @_;
    print "$cmd\n" if $verbose;
    system($cmd) == 0 or die "$cmd failed\n";
}

sub git_commit {
    my ($msg, $date) = @_;

    $ENV{GIT_AUTHOR_DATE} = $date;
    open(my $F, '| git commit -q --author="unknown author <mageia-dev@mageia.org>" --file=-');
    print $F $msg;
    close $F or die "git commit failed\n";
}

sub commit_line_from_patch_name {
    my ($name) = @_;

    # remove prefix (eg: "libtool-1.5.26-xxx" => "xxx")
    my $re_name = qr([a-z][\w+]*([_-][a-z][\w+]*)*)i;
    my $re_rc = qr((rc\d*|RC\d+|beta\d*|pre\d*|p\d+|test));
    my $re_special_version = qr([a-z]([._-]$re_rc?)?|[._-]?$re_rc?|[a-z]);
    $name =~ s/^ $re_name [._-] \d+ (\.\d+)+ $re_special_version [._-]//x;

    if (my $pkg_name = $ENV{PKG_NAME}) {
	$name =~ s/^\Q$pkg_name\E[_-]//;
    }
	    
    # replace "-" (resp. "_") with spaces if there is no spaces nor "_" (resp. "-")
    if ($name !~ /[\s_]/ && $name !~ /--/) {
	$name =~ s/-/ /g;
    } elsif ($name !~ /[\s-]/ && $name !~ /__/) {
	$name =~ s/_/ /g;
    }
    $name;
}

sub get_patch_header { 
    my ($file) = @_;
    open(my $F, '<', $file) or die "can not open $file: $!\n";

    my @header;
    while (my $s = <$F>) {
	last if $s =~ /^--- /;
	push @header, $s;
    }
    pop @header while @header && $header[-1] !~ /^\s*$/;

    @header;
}

sub cleanup_patch_header {
    my (@header) = @_;

    my @r;
    foreach (@header) {
	s/^##\s// or last;
	push @r, $_;
    }
    @r == @header and return @r;

    @header;
}

# "git format-patch" and "git am" do not agree how to handle commit logs when
# the first line is not separated from the rest.
# eg:
#
# > Subject: [PATCH 01/34] Delay NSS initialization until actually used
# >  - since NSS is allergic (ie becomes non-functional) after forking, delay
# >    it's initialization until really needed, ie lazy init in rpmDigestInit()
#
# workarounding by transforming header to:
#
# > Subject: [PATCH 01/34] Delay NSS initialization until actually used
# >
# >  - since NSS is allergic (ie becomes non-functional) after forking, delay
# >    it's initialization until really needed, ie lazy init in rpmDigestInit()
sub fix_git_patch {
    my ($file) = @_;
    open(my $F, '<', $file) or die "can not open $file: $!\n";

    my ($last_line, @l);
    while (my $s = <$F>) {
	push @l, $s;

	if ($s !~ /^\S+:\s/ && $last_line && $last_line =~ /^Subject:/) {
	    # argh, we are in the header, but the value is weird
	    # applying the fix
	    $l[-1] = "\n" . $l[-1];
	    push @l, <$F>;
	    output("$file.tmp", @l);
	    return "$file.tmp";
	} elsif ($s =~ /^\s*$/ || $s =~ /^--- /) {
	    last;
	}
	$last_line = $s;
    }
    undef;
}

sub output { my $f = shift; open(my $F, '>', $f) or die "output in file $f failed: $!\n"; print $F $_ foreach @_; 1 }