| #!/usr/bin/perl | |
| # 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); | |
| use LinuxStableQueue qw($MAIL_FROM $MAIL_REVIEW_TO $MAIL_REVIEW_COVER_CC | |
| $MAIL_REVIEW_CC $STABLE_RC_URL | |
| 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; | |
| $parser->output_to_core(1); | |
| for my $patch_name (@$series) { | |
| my $message = $parser->parse_open("$dir/$patch_name") or return 0; | |
| # Extract author and subject | |
| $message->head->unfold(); | |
| my ($author) = | |
| Mail::Field->new('From', | |
| scalar(decode_mimewords($message->head->get('From')))) | |
| ->names(); | |
| 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, | |
| To => $MAIL_REVIEW_TO, | |
| Cc => "$MAIL_REVIEW_COVER_CC, $MAIL_REVIEW_CC", | |
| common_mail_header(), | |
| 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 | |
| ${STABLE_RC_URL} . | |
| A shortlog and diffstat can be found below. | |
| Ben. | |
| ------------- | |
| "; | |
| quilt_shortlog($quilt_dir, \@series, $body_fh) or exit 1; | |
| print $body_fh `git diff --stat v$last_update_ver`; | |
| print_signature($body_fh); | |
| 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 | |
| $patch->purge; | |
| # 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, | |
| To => $MAIL_REVIEW_TO, | |
| Cc => join(', ', $MAIL_REVIEW_CC, values(%extra_cc)), | |
| common_mail_header(), | |
| 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"; |