Blob Blame History Raw
#!/usr/bin/perl -w
use strict;
use Error qw(:try);
use File::Copy qq(move);
use Git;
use Storable qq(retrieve);
use Term::ANSIColor qw(colored);

my $tmpdir = $ENV{TMPDIR} || "/tmp";

my $repo = Git->repository;

if (scalar @ARGV < 1) {
        print "Usage: $0 patches_dir [nonzero_to_force_removal]\n";
        exit 1;
}

open(SERIES, "+<series.conf") || die "cannot open series.conf";
my $series;
my $series_changed = 0;
{
	local $/;
	$series = <SERIES>;
}

my $patchdir = $ARGV[0];
my $idsfile = "$patchdir/ids";
my $force_removal = $ARGV[1] || 0;
my $destdir = "patches.kernel.org";
mkdir $destdir if (! -d $destdir);

my $ids = retrieve($idsfile) or die "cannot read $idsfile";
my $regexp = join "|", map @$_, values %$ids;
my @candidates = ();

if ($regexp eq "") {
	print STDERR colored("empty regexp computed? Skipping patches removal...\n", 'yellow');
} else {
	try {
		@candidates = $repo->command('grep', '-El', $regexp, '--',
			'patches.*') or die "cannot execute git grep";
	} catch Git::Error::Command with {
		# not found is OK
	};
}

sub output_refs($@) {
	my ($fh, @refs) = @_;
	my %uniq = map {
		s/fate/FATE/i;
		s/bnc/bnc/i;
		$_, 1
	} @refs;
	print $fh "References: ", join(' ', sort keys %uniq), "\n";
}

sub push_refs($@) {
	my ($dest, @refs) = @_;

	open(DEST, "<$dest") || die "cannot open $dest for reading";
	my @dest = <DEST>;
	close DEST;

	open(DEST, ">$dest") || die "cannot open $dest for writing";
	my $had_git_commit = 0;
	foreach my $line (@dest) {
		if (!$had_git_commit && $line =~ /^Git-commit: /) {
			output_refs(\*DEST, @refs);
			$had_git_commit = 1;
		} elsif ($line =~ /^References: (.*)$/) {
			chomp $1;
			push @refs, (split /[\s,]+/, $1);
			next;
		}
		print DEST $line;
	}
	close DEST;
}

my %files;
my $tags = qr/(?:Git-[Cc]ommit: |Patch-[Mm]ainline: |From )([0-9a-f]{40})/;

sub handle_removal($$) {
	my ($line, $dest) = @_;

	$line =~ /^([^:]+):($tags)?/;
	my $file = $1;
	my $match = $2;

	$files{$file} = 1;

	# weird git-commit tag or file may be deleted already
	return unless (defined $match && -f $file);

	print colored("\tRemoving $file\n", "yellow");

	open(PATCH, "<$file") or die "cannot open $file";
	my %shas = ();
	my @refs = ();
	while (my $line = <PATCH>) {
		chomp $line;
		$shas{$1} = 1 if ($line =~ /^$tags/);
		if ($line =~ /^References: (.*)$/) {
			push @refs, (split /[\s,]+/, $1);
		}
	}
	close PATCH;

	return unless ($force_removal || scalar(keys %shas) == 1);

	try {
		$repo->command('rm', '--', $file);
	} catch Git::Error::Command with {
		# sometimes, they are dirty
	};

	$series =~ s/
		(?:
			# empty or non-comment line
			(^(?:$|[ \t]*[^ \t#].*)\n)
			# comment to be deleted
			[ \t]*\#.*\n
		)?
		# file to be deleted
		[ \t]+\Q$file\E[ \t]*\n
		/$1 || ""/mex;
	$series_changed = 1;

	if (scalar @refs) {
		if (-f $dest) {
			push_refs($dest, @refs);
		} else {
			print STDERR colored("\tmissed references:\n\t", 'red');
			output_refs(\*STDERR, @refs);
		}
	}
}

foreach my $patch (sort keys %$ids) {
	my $src = "$patchdir/$patch";
	my $dest = "$destdir/$patch";

	print "Handling $patch\n";
	if (-f $src && ! -f $dest) {
		move($src, $dest) || die "cannot move $src to $dest";
		print "\tMoved to $destdir\n";
		$repo->command('add', $dest);
		print "\tAdded to GIT\n";
		unless ($series =~ s/(latest standard kernel patches(?:\n[^\n]+)+\n)\n/$1\t$dest\n\n/) {
			die "cannot find a place in series.conf to add a patch";
		}
		$series_changed = 1;
	}

	my $re = join("|", @{$$ids{$patch}});

	if (scalar @candidates > 0 && $re ne "") {
		my @found;
		try {
			@found = $repo->command('grep', '-E', $re, '--',
					@candidates) or
				die "cannot execute git grep";
		} catch Git::Error::Command with {
			# not found is OK
		};

		foreach (@found) {
			next if (/patches\.kernel\.org\//);
			handle_removal($_, $dest);
		}
	}

}

if ($series_changed) {
	seek(SERIES, 0, 0) || die "cannot seek series.conf";
	truncate(SERIES, 0) || die "cannot truncate series.conf";
	print SERIES $series;
}
close SERIES;

foreach my $file (keys %files) {
	next unless (-e $file);

	try {
		$repo->command_noisy('grep', '-E', $regexp, '--',
				$file);
	} catch Git::Error::Command with {
		# not found is OK
	};
}

0;