blob: 36823381efaf5de25a42e6b2850ef5ce676db6b5 [file] [log] [blame]
# Generate an mbox of mail from the applied quilt series in the proper
# format for a -stable kernel review.
use strict;
use warnings;
use File::Basename;
use lib File::Basename::dirname($0);
base_version last_update common_mail_header
mime_entity_to_mbox print_signature parse_mailbox_addr
mailbox_addr_to_addr_spec make_mailbox_addr
parse_mailbox_list quilt_dir_from_git_dir);
use FileHandle;
use MIME::Parser ();
use MIME::Words qw(decode_mimewords);
use Mail::Field::AddrList ();
use POSIX qw(getcwd strftime);
sub quilt_shortlog {
my ($dir, $series, $log_fh) = @_;
my %patches;
my $parser = new MIME::Parser;
for my $patch_name (@$series) {
my $message = $parser->parse_open("$dir/$patch_name") or return 0;
# Extract author and subject
my ($author) =
my $subject = scalar(decode_mimewords($message->head->get('Subject')));
chomp $subject;
if (!defined($author) || !defined($subject)) {
print STDERR "E: Author and subject not found in $patch_name\n";
return 0;
# Extract upstream commit hash
my $body = $message->bodyhandle->open('r');
my $upstream;
for (<$body>) {
if (/^commit (.*) upstream\.\n/m) {
$upstream = $1;
} elsif (/^\[ Upstream commit (.*) \]\n/m) {
$upstream = $1;
} elsif (/^\(cherry picked from commit (.*)\)\n/m) {
$upstream = $1;
if (!defined($upstream)) {
# non-fatal; sometimes there is a good reason for this
print STDERR "W: Upstream commit hash not found in $patch_name\n";
$upstream = 'not upstream';
$patches{$author} ||= {};
$patches{$author}->{$subject} = $upstream;
# Group and list ASCIIbetically by author and then subject
for my $author (sort(keys(%patches))) {
printf $log_fh "%s (%d):\n", $author, scalar(keys(%{$patches{$author}}));
for my $subject (sort(keys(%{$patches{$author}}))) {
my $upstream = $patches{$author}->{$subject};
printf $log_fh " %s\n [%s]\n", $subject, $upstream;
printf $log_fh "\n";
return 1;
my $update_rc_ver = `make kernelversion`;
chomp $update_rc_ver;
my $update_ver = $update_rc_ver;
$update_ver =~ s/-rc\d+$//;
my $last_update_ver = last_update($update_ver);
my $base_ver = base_version($update_ver);
my $DATE = strftime('%a %b %d %H:%M:%S UTC %Y', gmtime(time() + 2 * 86400));
if ($update_rc_ver eq $update_ver) {
print STDERR "Makefile says the version is ${update_rc_ver}, did you forget to set the -rc version?\n";
exit 1;
my $MBOX = "linux-$update_rc_ver.mbox";
print "Creating the mailbox for kernel release ${update_rc_ver}\n";
my $mbox_fh = new FileHandle($MBOX, 'w');
my $quilt_dir = quilt_dir_from_git_dir(getcwd()) or die "Where are you?";
my $series_fh = new FileHandle('quilt series |') or die "$!";
my @series = <$series_fh> or die "$!";
chomp @series;
my $NUM_PATCHES = scalar(@series);
my $patch_num_fmt = '%0' . length($NUM_PATCHES) . 'd';
my $cover = MIME::Entity->build(
From => $MAIL_FROM,
Subject => sprintf(
"[PATCH $base_ver $patch_num_fmt/$NUM_PATCHES] $update_rc_ver review", 0),
Type => 'text/plain',
Charset => 'UTF-8',
Encoding => '8bit',
Data => '');
my $body_fh = $cover->open('w');
print $body_fh
"This is the start of the stable review cycle for the ${update_ver} release.
There are ${NUM_PATCHES} patches in this series, which will be posted as responses
to this one. If anyone has any issues with these being applied, please
let me know.
Responses should be made by ${DATE}.
Anything received after that time might be too late.
All the patches have also been committed to the linux-${base_ver}.y-rc branch of
A shortlog and diffstat can be found below.
quilt_shortlog($quilt_dir, \@series, $body_fh) or exit 1;
print $body_fh `git diff --stat v$last_update_ver`;
close $body_fh;
# XXX I want to attach the combined patch, but I also want to sign it.
# And I don't want to have to load all the messages into a mailer. So
# this will have to wait for GPG integration.
mime_entity_to_mbox($cover, $mbox_fh);
my $parser = new MIME::Parser;
for my $i (1..@series) {
my $patch_name = $series[$i - 1];
# Extract what we need
my $patch = $parser->parse_open("$quilt_dir/$patch_name");
my $from = MIME::Words::decode_mimewords($patch->head->get('From'));
my $subject = $patch->head->get('Subject');
my $body_text = $patch->bodyhandle->as_string;
my %extra_cc;
# Remove temporary files
# Add the author and everyone mentioned in the commit message to
# the Cc list. De-dupe, and fix quoting of names.
my ($name, $addr_spec) = parse_mailbox_addr($patch->head->get('From'));
$extra_cc{$addr_spec} = make_mailbox_addr($name, $addr_spec);
while ($body_text =~ /^(?:Signed-off-by|Acked-by|Suggested-by|Reviewed-by|Requested-by|Reported-by|Tested-by|Cc):\s*(.*>|\S*)/gim) {
($name, $addr_spec) = parse_mailbox_addr($1);
$extra_cc{$addr_spec} ||= make_mailbox_addr($name, $addr_spec);
# Also remove recipients that are on the fixed address list
for ($MAIL_FROM, parse_mailbox_list($MAIL_REVIEW_TO),
parse_mailbox_list($MAIL_REVIEW_CC)) {
delete $extra_cc{mailbox_addr_to_addr_spec($_)};
# Build a new message
$patch = MIME::Entity->build(
From => $MAIL_FROM,
Cc => join(', ', $MAIL_REVIEW_CC, values(%extra_cc)),
Subject => sprintf("[PATCH $base_ver $patch_num_fmt/$NUM_PATCHES] %s",
$i, $subject),
Type => 'text/plain',
Charset => 'UTF-8',
Encoding => '8bit',
Data => '');
# Make it a reply to the cover
$patch->head->add('In-Reply-To' => $cover->head->get('Message-Id'));
my $body_fh = $patch->open('w');
print $body_fh
"$update_rc_ver review patch. If anyone has any objections, please let me know.
From: $from
print $body_fh $body_text;
close $body_fh;
mime_entity_to_mbox($patch, $mbox_fh);
print "mbox is now in ${MBOX}\n";