#!/usr/bin/perl
##
# Copyright (C) 2008 by James E.J. Bottomley
##
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
##

use strict;
use Getopt::Std;

# -i means run interactively (ignore the mail and error directives)'
#
# -t <tree> means override the tree specified in the setup directive
#    this is for interactive runs to try a merge in a scratch tree
#    using the same config file
our($opt_i,$opt_t);
getopts('it:');

my $treefile=$ARGV[0];
my $logfile = "/tmp/builder-jejb.$$.log";

if (!$opt_i) {
    close STDOUT;
    open STDOUT, ">$logfile";
    close STDERR;
    open STDERR, ">$logfile";
}

my @trees;
my ($dir, $base, $tagbase, $master, $master_tag, $kernelbase, $maillog,
    $tagbasemk, $errlog);

sub base_transform {
    my $orig_base = $_[0];
    my $base;
    return $orig_base if (!($orig_base =~ m/-git[0-9]+$/));
    $orig_base = substr($orig_base, 1) if (substr($orig_base, 0, 1) eq 'v');
    my $patch = "/pub/linux/kernel/v2.6/snapshots/patch-${orig_base}.log";
    if (! -f $patch) {
	print "PATCH FILE $patch NOT FOUND\n";
	return $orig_base;
    }
    open(TMP, "<$patch") || die;
    my $head;
    chomp($head = <TMP>);
    close(TMP);
    if ($head =~ m/^commit (\S+)/) {
	$base = $1;
	print "$orig_base to $base\n";
    } else {
	print "Unrecognised line $head in file $patch\n";
	$base = $orig_base;
    }
    return $base;
}


open(TREES, "<$treefile") || die;

chomp(@trees = <TREES>);
close(TREES);

END {
    if ($? && !$opt_i) {
	if ($maillog) {
	    system("cat $logfile | mail -s \"ERROR BUILDING: $tagbase\" $maillog");
	}
	if ($errlog) {
	    system("cp $logfile $errlog${tagbase}");
	}
    }
    unlink($logfile);
}

foreach(@trees) {
    next if (m/^\s*#/ || m/^\s*$/);

    print "##\n# $_\n##\n";

    if (m/^\s*(git|gitfetch)\s+(\S+)\s+(\S+)\s+(\S+)/ || m/^\s*git\s+(\S+)\s+(\S+)/) {
	die ("no master before git\n") if (!defined $master);

	my $cmd = $1;
	my $url = $2;
	my $remote_branch = $3;
	my $local_branch = $4;

	$local_branch = $remote_branch if (!$local_branch);

	my $tmp;
	chomp($tmp = `git ls-remote --heads $url $remote_branch`);

	if (!$tmp) {
	    print "BRANCH $remote_branch not in tree $url, SKIPPING\n";
	    system("git branch -D $local_branch");
	    next;
	}

	system("git fetch --no-tags -f  $url $remote_branch:$local_branch") >> 8 == 0 || die;
	if ($cmd eq 'git') {
	    system("git pull . $local_branch") >> 8 == 0 || die;
	}
	
    } elsif (m/^\s*gitrebase\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/) {
	die ("no master before git\n") if (!defined $master);

	my $url = $1;
	my $remote_branch = $2;
	my $local_branch = $3;
	my $remote_merge_base = $4;
	my $local_merge_base = ${local_branch}.'-base';

	my $tmp;
	chomp($tmp = `git ls-remote --heads $url $remote_branch`);

	if (!$tmp) {
	    print "BRANCH $remote_branch not in tree $url, SKIPPING\n";
	    system("git branch -D $local_branch");
	    next;
	}

	system("git fetch --no-tags -f  $url $remote_branch:$local_branch") >> 8 == 0 || die;
	system("git fetch --no-tags -f  $url $remote_merge_base:$local_merge_base") >> 8 == 0 || die;
	system("git checkout $local_branch") >> 8 == 0 || die;
	system("git rebase --onto $tagbase $local_merge_base") >> 8 == 0 || die;
	system("git checkout $tagbase") >> 8 == 0 || die;
	system("git pull . $local_branch") >> 8 == 0 || die;
	
    } elsif (m/^\s*quiltstable\s+(\S+)\s+(\S+)\s+(\S+)/) {
	my $quilt_branch = $1;
	my $stable_tree = $2;
	my $stable_base = $3;
	my @patches = ();

	system("git checkout $quilt_branch") >> 8 == 0 || die;
	foreach(`git log ${stable_base}..${stable_tree}`) {
	    chomp($_);
	    if (/^commit (\S+)/) {
	        push @patches, $1;
	    }
	}
	foreach(@patches) {
	    ##
	    # We have to revert each patch individually so that
	    # rename information is preserved
	    ##
	    if (system("git revert --no-edit $_") >> 8 != 0) {
		# if nothing changed, the revert is empty
		system("git-update-index --refresh") >> 8 == 0 || die;
	    }
	}
	system("git pull . $stable_tree") >> 8 == 0 || die;
    } elsif (m/^\s*merge\s+(\S+)\s+resolve\s+(\S+)/ || m/^\s*merge\s+(\S+)/) {
	my $local_branch = $1;
	my $resolve_patch = $2;

	system("git checkout $tagbase") >> 8 == 0 || die;

	if (system("git cat-file -e $local_branch 2> /dev/null") >> 8 != 0) {
	    print "MERGE $local_branch not in tree, SKIPPING\n";
	    next;
	}
	    
	if ($resolve_patch) {
	    my @files;
	    my $file;
	    my @unresolved;

	    if (system("git pull . $local_branch") >> 8 == 0) {
		print "WARNING: Merge successful but resolve requested\n";
		die;
	    }
	    chomp(@files = `git status`);
	    grep {
		if (/^#\s+unmerged:\s+(\S+)/) {
		    $_ = $1;
		} else {
		    $_ = undef;
		}
	    } @files;
	    foreach $file (@files, $resolve_patch) {
		next if (!$file);
		open(TMP, "<$file") || die("Missing file $file");
		my @contents = <TMP>;
		close TMP;
		grep {
		    $_ = "$1<<<<<<<\n" if (m|^(-?)\<\<\<\<\<\<\<\s|);
		    $_ = "$1>>>>>>>\n" if (m|^(-?)\>\>\>\>\>\>\>\s|);
		} @contents;
		open(TMP, ">$file");
		print TMP join('', @contents);
		close TMP;
	    }
		    
	    system("git apply ${resolve_patch}") >> 8 == 0 || die;
	    foreach $file (@files) {
		next if (!$file);
		if (!open(TMP, "<$file")) {
		    # patch removed the file
		    system("git rm $file");
		    next;
		}
		foreach (<TMP>) {
		    if (m|^\<\<\<\<\<\<\<| || m|^\>\>\>\>\>\>\>|) {
			push @unresolved, $file;
		    }
		}
	    }
	    die("Unresolved files: ".join(",", @unresolved)."\n") if (@unresolved);
	    system("git commit --all -m \"Resolve Patch: ${resolve_patch}\"") >> 8 == 0 || die;
	} else {
	    system("git pull . $local_branch") >> 8 == 0 || die;
	}
    } elsif (m/^\s*patch\s+(-\S+)\s+(\S+)\s+(\S+)/ || m/^\s*patch()\s+(\S+)\s+(\S+)/) {
	my $flags = $1;
	my $patch = $2;
	my $branch = $3;

	die("no patch $patch") if (! -f $patch);
	system("git checkout $branch") >> 8 == 0 || die;
	system("git apply $flags --index $patch") >> 8 == 0 || die;
	system("git commit -m \"patch: $patch\"") >> 8 == 0 || die;
    } elsif (m/^\s*quilt\s+(\S+)\s+(\S+)\s+base\s+(\S+)/ || m/^\s*quilt\s+(\S+)\s+(\S+)/) {
	die ("no master before quilt") if(!defined $master);

	my $url = $1;
	my $branch = $2;
	my $patchdir = 'patches/'.$branch;
	my $base = $3;
	my $cont = m/\s+noerror/;

	system("rm -fr .dotest");
	mkdir('patches');
	mkdir($patchdir);
	if ($url =~ m|^http://|) {
	    system("rm -fr $patchdir && mkdir $patchdir");
	    system("wget -q --recursive --no-directories --no-parent --directory-prefix=$patchdir $url"); # wget has odd return codes, so don't die here
	} else {
	    system("rsync  -avc $url $patchdir") >>8 == 0|| die;
	}
	die("no series file") if (! -f "$patchdir/series");

	my (@tmp);
	open(TMP, "<$patchdir/series") || die;
	chomp(@tmp = <TMP>);
	close(TMP);
	foreach (@tmp) {
	    if (!$base && (/^\s*\#\s*BASE\s+commit\s+(\S+)/ ||
			   /^\s*\#\s*BASE\s+(\S+)/)) {
		$base = $1;
	    }
	    if (/(\S+)\s*\#/) {
		# strip trailing comments (git doesn't like them)
		$_ = $1;
	    }
	}
	open(TMP, ">$patchdir/series") || die;
	print TMP join("\n", @tmp);
	close(TMP);

	if ($base) {
	    # get the actual value for 2.6.xx-gitN urls (if present)
	    $base = base_transform($base);
	} else {
	    $base = $master if (!$base);
	    $base = $tagbase if ($base eq 'TOP');
	}
	print "Using base $base\n";
	if (system("git branch -f $branch $base") >> 8 != 0) {
	    #try with a v
	    $base = 'v'.$base;
	    system("git branch -f $branch $base") >> 8 == 0 || die;
	}
	system("git checkout $branch") >> 8 == 0 || die;
	system("git branch -f ${branch}-base") >> 8 == 0 || die;

	if (system("git apply --check --reverse $_ > /dev/null 2>&1") >> 8 == 0) {
	    # if the first patch can be reverse applied assume the series
	    # is already incorporated
	    print "REVERSE APPLICABLE PATCH, SKIPPING QUILT\n";
	    system("git branch -D $branch");
	    next;
	}
	    
	system("git quiltimport --author \"none <none\@none>\" --patches $patchdir") >> 8 == 0 || $cont || die;
	system("git checkout $tagbase") >> 8 == 0 || die;
    } elsif (m/^\s*master\s+(\S+)\s+(\S+)/) {
	die ("no setup before master") if(!defined $tagbase);
	my $url = $1;
	$master = $2;
	my $tmp;
	(system("git fetch $url master:$master") >> 8) == 0|| die;
	chomp($master_tag = `git describe --abbrev=0 $master`);
	die("Strange tag $master_tag") if (!($master_tag =~ /v(\d+\.\d+\.\d+-rc\d+)$/ || $master_tag =~/v(\d+\.\d+\.\d+)$/ ));
	$kernelbase = $1;
	$tagbasemk = $tagbase;
	$tagbase = $1.'-'.$tagbase;
	my @branches;
	chomp(@branches = `git branch`);
	$tmp = 0;
	foreach (@branches) {
	    next if (!m/^..$tagbase(\d+)/);
	    $tmp = $1 if ($1 > $tmp);
	}
	$tagbase .= ++$tmp;
	$tagbasemk .= $tmp;
	print "Using $tagbase\n";
	system("git branch $tagbase $master") >> 8 == 0 || die;
	system("git checkout $tagbase") >> 8 == 0 || die;
	system("git clean -x -d -f");
	system("git branch ${tagbase}-base") >> 8 == 0 || die;
    } elsif (m/^\s*setup\s+(\S*)\s+(\S*)/) {
	$tagbase = $1;
	$dir = $2;
	if ($opt_t) {
	    print "Interactive run, overriding tree with $opt_t\n";
	    $dir = $opt_t;
	}
	chdir $dir || die("Failed to change to $dir");
	system("git reset --hard") >> 8 == 0 || die;
    } elsif (m/^\s*diff\s+(\S+)/) {
	next if ($opt_i);	# skip if interactive
	die ("no diff before git\n") if (!defined $master);
	my $prefix = $1;

	system("git diff ${tagbase}-base $tagbase > ${prefix}${tagbase}.diff") >> 8 == 0 || die;
    } elsif(m/^\s*log\s+(\S+)/) {
	my $prefix = $1;
	next if ($opt_i);	# skip if interactive

	system("cp $logfile $prefix${tagbase}") >> 8 == 0 || die;
    } elsif(m/^\s*mail\s+(\S+)/) {
	$maillog = $1
    } elsif(m/^\s*errors\s+(\S+)/) {
	$errlog = $1;
    } elsif(m/^\s*check\s*/) {
	my ($prefix, $count, $prior, $diff);

	($prefix, $count) = ($tagbase =~ m/^(.*?)(\d+)$/);
	if ($count == 1) {
	    print "No prior to diff against\n";
	    next;
	}
	$prior = $count - 1;
	chomp($diff=`git diff ${prefix}${prior} ${prefix}${count}`);
	if (!$diff) {
	    print "Diff with prior is empty, nothing to do\n";
	    # remove all records of this build
	    system("git branch -D $tagbase");
	    system("git branch -D ${tagbase}-base");
	    exit(0);
	}
	print "Differences exist, continuing\n";
    } elsif (m/^\s*label\s*$/) {
	open(MK, '<Makefile');
	my @makefile = <MK>;
	close(MK);
	grep {
	    if (m/^EXTRAVERSION =\s*(\S*)/) {
		$_ = 'EXTRAVERSION = '.$1.'-'.$tagbasemk."\n";
	    }
	}  @makefile;
	open(MK, '>Makefile');
	print MK join('', @makefile);
	close(MK);
	system("git commit -m \"$tagbase\" Makefile") >> 8 == 0 || die;
    } elsif (m/^\s*skip\s(.*)/) {
	print "SKIPPING: $_\n";
    } elsif (m/^\s*revert\s+(\S+)\s+(\S+)/ || m/^\s*revert\s+(\S+)/) {
	my $revert = $1;
	my $branch = $2;

	if ($branch) {
	    system("git checkout $branch") >> 8 == 0 || die;
	}
	system ("git revert --no-edit $revert") >> 8 == 0 || die;
	if ($branch) {
	    system("git checkout $tagbase") >> 8 == 0 || die;
	}
    } else {
	die("Unknown line $_\n");
    }
}

exit(0);
