Blob Blame History Raw
#!/usr/bin/perl -w
use strict;
use File::Basename qq(basename);
use Git;
use Storable qw(store);
use Term::ANSIColor qw(colored);

if (@ARGV < 2) {
	print "Usage: $0 suse_machine|-d stable_version\n";
	exit 1;
}

my $machine = shift;
my $dump_only = $machine eq '-d';
my $stable_ver = shift;
my $old_version;
my $new_version;
my $bsc;

my %bsc_map = (
	'other' => '1012628',
);

if ($stable_ver =~ /^v?(([3-9]\.[0-9]+)\.([0-9]+))$/) {
	$new_version = $1;
	$old_version = $2;
	$bsc = $bsc_map{$old_version};
	if ($3 ne 1) {
		$old_version .= '.' . ($3 - 1);
	}
} else {
	die "cannot understand stable version $stable_ver";
}

if (!defined $bsc) {
	print colored("Kernel version not found in the map, assuming Tumbleweed\n", 'red');
	$bsc = $bsc_map{'other'};
}

my $patchpar = '/dev/shm';
my $patchdir = "patches-$new_version";
my $patchpath = "$patchpar/$patchdir";
my $idsfile = "$patchpath/ids";
if (!$dump_only && !mkdir $patchpath) {
	die "$patchpath already exists";
}

my $range = "v$old_version..v$new_version";
my $repo = Git->repository();
my @revs = $repo->command('rev-list', '--reverse', $range);
my %ids;
my $digits = length scalar @revs;
my $counter = 1;
my @to_delete;
my $sha_re = qr/[0-9a-f]{40}/;

$digits = 3 if ($digits < 3);

print "References: bsc#$bsc $new_version\n" if ($dump_only);

foreach my $rev (@revs) {
	my ($filename, @commit_log) = $repo->command('show', '--no-patch',
		'--format=%f%n%B', $rev);

	my $cont = 0;
	my @shas;
	my @unmatched_shas;

	foreach (@commit_log) {
		if ($cont) {
			if (/^\s+($sha_re)\s*[\])]$/) {
				push @shas, $1;
				$cont = 0;
				next;
			}
		}
		if (/^[Cc]ommit ($sha_re) upstream\.?$/ ||
			/^[\[(]\s*[Uu]pstream commit ($sha_re)\s*[\])]$/ ||
			/^[uU]pstream commit ($sha_re)\.$/ ||
			/^This is a backport of ($sha_re)$/) {
			push @shas, $1;
		} elsif (/^\(cherry picked from commit ($sha_re)\)$/) {
			# if this is not the first SHA, it's likely DRM crap
			push(@shas, $1) if (!scalar @shas);
		} elsif (/^[\[(]\s*[Uu]pstream commits ($sha_re)\s+and\s*$/) {
			push @shas, $1;
			$cont = 1;
		} elsif (/^(Fixes:|This reverts commit) $sha_re(?: \(".*)?\.?$/ ||
			/^Link: .*lkml/) {
			# ignore
		} elsif (/\b$sha_re\b/) {
			push @unmatched_shas, $_;
		}
	}

	if ($dump_only) {
		print "$rev";
		if (scalar @shas) {
			print "=", join ' ', @shas;
		} else {
			print ' STABLE-ONLY patch: "', $commit_log[0], '"';
		}
		print "\n";
		next;
	}

	# better than nothing
	if (!scalar @shas) {
		push @shas, $rev;
	}

	my @patch = $repo->command('format-patch', '--stdout',
		'--signoff', '--no-renames',
		'-1', $rev,
		'--add-header', "References: bsc#$bsc",
		'--add-header', "Patch-mainline: $new_version",
		(map { ('--add-header', "Git-commit: $_") } @shas));

	# drop From
	shift(@patch) =~ /^From/ or die "From line is not the first one?";

	my $newname = sprintf("${new_version}-%0${digits}d-%s", $counter,
		$filename);
	# 57 is what git-format-patch uses
	$newname =~ s/^(.{1,57}).*$/$1.patch/;
	my $newpath = "$patchpath/$newname";

	open(PATCH, ">$newpath") or die "cannot output to $newpath";
	print PATCH join "\n", @patch;
	print PATCH "\n";
	close PATCH;

	$ids{$newname} = [ @shas ];

	$rev =~ /.{12}/;
	print colored($&, "yellow"), " -> $newname\n";
	foreach (@shas) {
		/.{12}/;
		print "\tUpstream SHA: ", colored("$&\n", "yellow");
	}
	foreach (@unmatched_shas) {
		print colored("\tUNMATCHED SHA:", 'bold red'), " $_\n";
	}

	push @to_delete, $newpath;
	$counter++;
}

exit 0 if ($dump_only);

store(\%ids, $idsfile) or die "cannot write $idsfile";
push @to_delete, $idsfile;

if ($machine ne 'localhost') {
	system("tar -cC $patchpar $patchdir|ssh -C $machine -o StrictHostKeyChecking=no 'tar -xC $patchpar'") == 0 ||
		die "ssh didn't start";
	unlink(@to_delete) or print STDERR "cannot delete some temp files\n";
	rmdir("$patchpath") or print STDERR "cannot remove $patchpath\n";
}

print "Written patches and ids to $machine:$patchpath\n";

0;