#!/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;