#!/usr/bin/perl
use strict;
use Getopt::Long;

# User preferences

my $inidiff     = 3330;		# Initial diff, if etc/last_retrieved not found
my $test        = 0;		# Don't use/update last_retrieved
my $no_update	= 0;		# Don't update from master repository
my $local_update= 0;		# update from ~/master
my $gen_brk_out = 0;		# Force generation of broken-out patches
my $debug       = 0;

my $init_rev;
my $only_rev;
my $end_rev;
my $help;

my %patchset;

GetOptions("init-rev=i" => \$init_rev,
	   "rev=i" => \$only_rev,
	   "end-rev=i" => \$end_rev,
	   "local!" => \$local_update,
	   "help+" => \$help,
	   "debug" => \$debug,
	  );

my $pullrepo = shift or die ("Need to specify a repository to pull");
my $is_patch = 0;

$is_patch = 1 if ($pullrepo =~ m/.patch$/);

if ($help) {
	printf ("Usage: $0 <repository to pull> [--rev <rev] [--init-rev <rev>] [--end-rev <rev>] [--local] [--help]\n");
	exit;
}

# Directory configurations

my $main        = '/home/v4l/';
my $tmp         = '$main/tmp';
my $gentree     = "$main/bin/gentree.pl --strip_dead_code";

my $url;
if ($local_update) {
	$url         = '~/master';
} else {
	$url         = 'https://linuxtv.org/hg/v4l-dvb';
}
my $last        = "$main/etc/last_retrieved";

# empty $blacklist_file means: no blacklist
#my $blacklist_file = "$main/etc/blacklist.txt";
my $blacklist_file;

my $temprepo    = "$tmp/hg_temp";
my $repository  = "$tmp/hg/v4l-dvb";
my $cleantree   = "$tmp/linux";
my $cleanold    = "$tmp/oldtree";

my $in_queue    = "patches";
my $checkpatch  = "scripts/checkpatch.pl";

my $hg          = 'hg';
my $series	=  "$in_queue/series";
my $tmpseries	=  "$in_queue/series.tmp";

my $ver;
open IN,"$main/etc/kern_version" or die "No kernel version file!";
while (<IN>) {
	if (m/^\s*\n/) {
		next;
	}
	$ver = $_;	# Kernel version used on cleanups
	$ver =~ s/\n//;
}
close IN;
printf "Generating patches for version $ver\n";

$ENV{PATH} = "/usr/local/bin:/usr/bin:/bin:$main";

# Reads a patch and reformats
sub patchread($$) {
	my ($path,$num)=@_;
	my $patch="";
	my $diff="";
	my $header="";
	my $diffstat="";
	my $initer="";
	my $signers="";
	my $subject;
	my $description=0;

	print "$hg -R $path log -r $num -v -p\n" if ($debug);
	open HEAD,"$hg -R $path log -r $num -v -p |";
	while (<HEAD>) {
		if (m/^user:\s*(.*)\n/) {
			$header="Commiter: $1\n".$header;
			next;
		}
		if (m/^date:\s*(.*)\n/) {
			$header=$header."Date: $1\n";
			next;
		}
		if (m/^tag:\s*(.*)\n/) {
			$header=$header."Tag: $1\n";
			next;
		}
		if (m/^branch:\s*(.*)\n/) {
			$header=$header."Branch: $1\n";
			next;
		}
		if (m/^description:/) {
			$description=1;
			next;
		}
		if (!$description) {
			next;
		}
		if (!$subject) {
			$header=$header."Subject: $_";
			$subject=$_;
			next;
		}
		if (m/^(From:)\s*(.*)(<.*>)/) {
			$header="$1 $2 $3\n$header";
			next;
		}
		if (m/^diff/) {
			$diff="$diff$_";
			next;
		}
		if ($diff eq "") {
			if (m/^(Signed-off-by|Acked-by|Reviewed-by):/) {
				$signers="$signers$_";
				$signers=~ s/mchehab\@infradead.org/mchehab\@redhat.com/;
				next;
			}
			$patch="$patch$_";
		} else {
			# Hack: Fixes a trouble at chanseset #176
			if (m|^\\ No newline at end of file|) {
				next;
			}

			$diff="$diff$_";
		}
	}
	# Strip blank lines at the beginning and at the end
	$patch =~ s/^\n+//;
	$patch =~ s/\n+$/\n/;

	$patch="Changeset: $num\n$header\n$patch\n$signers---\n\n";
	return ($patch, $diff, $subject);
}

sub check_pending($$) {
	my ($path, $blacklist)=@_;
	my ($init, $end);

	system "mkdir $in_queue";

	if ($only_rev) {
		$init_rev=$only_rev;
		$end_rev=$only_rev;
	}

	if (!$init_rev) {
		# get info about the latest commit
		if (open HEAD,"<$last") {
			while (<HEAD>) {
				$init = $_;
			}
			close HEAD;
		} else {
			$init=$inidiff-1;
		}
	} else {
		$init=$init_rev-1;
	}

	if (!$end_rev) {
		print "$hg -R $path tip\n" if ($debug);
		open HEAD,"$hg -R $path tip |";
		while (<HEAD>) {
			if (m/changeset:\s*(.\d+):/) {
				$end = $1;
			}
		}
	} else {
		die "Need to specify initial revision" if (!$init_rev);

		$end = $end_rev;
	}
	close HEAD;

	if ( $test ) {
		$init = $inidiff-1;
	}
	if ( "$init" ne "$end" ) {
		printf "Processing from patch %i to patch %i\n",$init+1, $end;
		for (my $i = $init + 1; $i <= $end; $i++) {
			next if (!defined($patchset{$i}));
			print "Processing changeset $i\r";

			my ($patch, $diff, $fname)=patchread("$path", $i);

			$fname =~ tr/[A-Z]/[a-z]/;
			$fname =~ s/^v4l_dvb//g;
			$fname =~ s/[^a-z0-9]+/_/g;
			$fname =~ s/^_+//;
			$fname =~ s/^\d+_*//;
			$fname =~ s/_+$//;
			$fname =~ s/_+/_/g;
			$fname = sprintf "hg_%i_$fname.patch", $i;

			# Clean old stuff
			print "rm -rf $path/* $cleanold $cleantree\n" if ($debug);
			system "rm -rf $path/* $cleanold $cleantree";

			# Make a clean update at current pos
			print "$hg -q -R $path update -C $i\n" if ($debug);
			system("$hg -q -R $path update -C $i");

			# Generate the previous and current clean tree
			print "$gentree $ver $path/linux $cleantree >/dev/null\n" if ($debug);
			system "$gentree $ver $path/linux $cleantree >/dev/null";
			print "patch -R -p1 -s -E -l -d $path -i /tmp/orig_$i.patch\n" if ($debug);
			open DIFF,"|patch -R -p1 -s -E -l -d $path" or die ("Error applying patch");
			print DIFF $diff or die ("Error applying patch");
			close DIFF or die ("Error applying patch");

			if ($debug) {
				open TMP, ">/tmp/orig_$i.patch";
				print TMP $diff;
				close TMP;
			}

			print "$gentree $ver $path/linux $cleanold >/dev/null\n" if ($debug);
			system "$gentree $ver $path/linux $cleanold >/dev/null";

			# Generate diff between the two trees
			print "diff -upNr $cleanold $cleantree >/tmp/fixed_$i.patch\n" if ($debug);
			open DIFF,"diff -upNr $cleanold $cleantree |";
			my $fixdiff="";
			my $ok=1;
			while (<DIFF>) {
				if (m/^diff/) {
					if ($blacklist && m/($blacklist)/) {
						$ok=0;
						m|^diff.*/([^/]*) |;
						print "** Removing file $1 because it is at blacklist ($blacklist matched)\n";
						next;
					} else {
						$ok=1;
					}
					s|$tmp/||g;
				}
				if ($ok) {
					s|^([\-\+]..\ )$tmp/(.*)$|\1\2|;
					$fixdiff=$fixdiff.$_;
				}
			}
			close DIFF;

			if ($debug) {
				open TMP, ">/tmp/fixed_$i.patch";
				print TMP $fixdiff;
				close TMP;
			}

			if ($fixdiff ne "") {
				printf "Generating $fname\n";
				open OUT, ">$in_queue/$fname"
					or die "Failed to create patch $in_queue/$fname";
				print OUT $patch, $fixdiff;
				close OUT;

				open OUT,">>$tmpseries"
					or die "Can't open $tmpseries file\n";
				print OUT "$fname\n";
				close OUT;
				system "$checkpatch -q --strict $in_queue/$fname";
			}
		}
	}
	print "                                                               \r";

	if ( $test ) {
		return;
	}
	if (!$end_rev) {
		open OUT,">$last" or die "Cannot open $last for update.";
		print OUT $end;
		close OUT;
	}
}

sub readfile ($) {
	my ($filename) = @_;
	my $file;

	open FILE, "<$filename" or
		die "can't open file $filename: $!";
	{ local $/; undef $/; $file = <FILE> }

	close FILE;
	return $file;
}


#
# Main
#

open OUT,">$tmpseries" or die "Can't create $tmpseries";
printf OUT "# Mercurial patches imported from $pullrepo\n";
close OUT;

my $blacklist;
if ($blacklist_file) {
	$blacklist = readfile("$blacklist_file");
	$blacklist =~ s/(\n)/|/g;
	$blacklist =~ s/^(.*)\|$/\(\1\)/;
}

if (!$no_update) {
	print("Retrieving changes at master repository\n");
	print "$hg -R $repository pull $url\n" if ($debug);
	system("$hg -R $repository pull $url");
}

# First of all, create a temporary cloned tree

system("rm -rf $temprepo");
my $r = system("$hg clone $repository $temprepo");
die("hg clone $repository $temprepo failed with error $?") if ($r);

if (!$is_patch) {
	$r = system("$hg -R $temprepo pull $pullrepo");
	die("$hg -R $temprepo pull $pullrepo failed with error $?") if ($r);
} else {
	$r = system("$hg -R $temprepo import $pullrepo");
	die("Failed to import $pullrepo patch") if ($r);
}

# Now, checks what are the new patches there
print "hg incoming -R $repository $temprepo\n";

if (!$init_rev) {
	open IN, "$hg incoming -R $repository $temprepo|";
	while (<IN>) {
		if (m/changeset:\s*(.*):/) {
			my $cs = $1;
			if (!$init_rev) {
				$init_rev = $cs;
			} elsif ($init_rev > $cs) {
				$init_rev = $cs;
			}
			$patchset{$cs} = 1;
		}
	}
	close IN;
	print "New patches start at $init_rev\n";
}

check_pending($temprepo, $blacklist);
system("rm -rf $temprepo");

open IN, "<$series" or die "Can't open $series file\n";

open OUT,">>$tmpseries"
	or die "Can't open $tmpseries file\n";
print OUT "\n";
while (<IN>) {
	print OUT $_;
}
close OUT;
close IN;

system("mv $series $series.old");
system("mv $tmpseries $series");
