From 1ac98d05d1a665f4376e3652163b9f3c17b6f808 Mon Sep 17 00:00:00 2001 From: Konstantin Ryabitsev Date: Tue, 8 Jun 2021 10:57:05 -0400 Subject: Tentative suport for sending exploded series I've been working on a way to automatically convert pull requests into series, complete with mailing them out to arbitrary destinations. This would allow folks to send a pull request to a dedicated list and have it automatically converted into a well-formed series. This is a tentative implementation that relies on git-send-email to do most of the heavy lifting. I have misgivings about using git-send-email for this purpose, but it does reduce the amount of duplicated code we would have otherwise had to write, and allows us to hook into things like tocmd/cccmd, etc. For example, adding the following to your .git/config: [sendemail "autopr"] smtpserver = [your.server.here] smtpserverport = 587 smtpencryption = tls smtpuser = [your-user] smtppass = [your-pass] transferEncoding = 8bit suppressFrom = yes confirm = never validate = no tocmd = "$(git rev-parse --show-toplevel)/scripts/get_maintainer.pl --norolestats --nol" cccmd = "$(git rev-parse --show-toplevel)/scripts/get_maintainer.pl --norolestats --nom" This would allow doing the following: b4 pr -e -f "AutoPR Exploder " -s autopr [--dry-run] The pull request will be exploded into a patch series and sent to all the proper destinations as returned by get_maintainer.pl. We construct the message headers in a way that allow regular code review and "b4 am" usage after the auto-exploded series is sent out. If testing goes well, we'll implement this as a kernel.org service and then hook a similar implementation via Gitlab/Github. Signed-off-by: Konstantin Ryabitsev --- b4/command.py | 5 ++++ b4/pr.py | 94 ++++++++++++++++++++++++++++++++++++++--------------------- 2 files changed, 66 insertions(+), 33 deletions(-) diff --git a/b4/command.py b/b4/command.py index 3f130c8..2d6994d 100644 --- a/b4/command.py +++ b/b4/command.py @@ -160,6 +160,11 @@ def cmd(): help='Attempt to retrieve any Link: URLs (use with -e)') sp_pr.add_argument('-f', '--from-addr', dest='mailfrom', default=None, help='Use this From: in exploded messages (use with -e)') + sp_pr.add_argument('-s', '--send-as-identity', dest='sendidentity', default=None, + help=('Use git-send-email to send exploded series (use with -e);' + 'the identity must match a [sendemail "identity"] config section')) + sp_pr.add_argument('--dry-run', dest='dryrun', action='store_true', default=False, + help='Force a --dry-run on git-send-email invocation (use with -s)') sp_pr.add_argument('msgid', nargs='?', help='Message ID to process, or pipe a raw message') sp_pr.set_defaults(func=cmd_pr) diff --git a/b4/pr.py b/b4/pr.py index 98de07b..1e308d8 100644 --- a/b4/pr.py +++ b/b4/pr.py @@ -322,19 +322,22 @@ def explode(gitdir, lmsg, mailfrom=None, retrieve_links=True, fpopts=None): # Is this the cover letter? if msubj.counter == 0: # We rebuild the message from scratch - cmsg = MIMEMultipart() - cmsg.add_header('From', mailfrom) - cmsg.add_header('Subject', '[' + ' '.join(msubj.prefixes) + '] ' + lmsg.subject) - cmsg.add_header('Date', lmsg.msg.get('Date')) - # The cover letter body is the pull request body, plus a few trailers body = '%s\n\nbase-commit: %s\nPR-Link: %s\n' % ( lmsg.body.strip(), lmsg.pr_base_commit, config['linkmask'] % lmsg.msgid) - cmsg.attach(MIMEText(body, 'plain')) - # now we attach the original request - # XXX: seems redundant, so turned off for now - # cmsg.attach(MIMEMessage(lmsg.msg)) + # Make it a multipart if we're doing retrieve_links + if retrieve_links: + cmsg = MIMEMultipart() + cmsg.attach(MIMEText(body, 'plain')) + else: + cmsg = email.message.EmailMessage() + cmsg.set_payload(body) + + cmsg.add_header('From', mailfrom) + cmsg.add_header('Subject', '[' + ' '.join(msubj.prefixes) + '] ' + lmsg.subject) + cmsg.add_header('Date', lmsg.msg.get('Date')) + msg = cmsg else: @@ -456,20 +459,6 @@ def main(cmdargs): lmsg.pr_tip_commit = lmsg.pr_remote_tip_commit if cmdargs.explode: - config = b4.get_main_config() - if config.get('save-maildirs', 'no') == 'yes': - save_maildir = True - dftext = 'maildir' - else: - save_maildir = False - dftext = 'mbx' - savefile = cmdargs.outmbox - if savefile is None: - savefile = f'{lmsg.msgid}.{dftext}' - if os.path.exists(savefile): - logger.info('File exists: %s', savefile) - sys.exit(1) - # Set up a temporary clone with b4.git_temp_clone(gitdir) as tc: try: @@ -478,19 +467,58 @@ def main(cmdargs): logger.critical('Nothing exploded.') sys.exit(1) - if msgs: - if save_maildir: - b4.save_maildir(msgs, savefile) - else: - with open(savefile, 'wb') as fh: - b4.save_git_am_mbox(msgs, fh) - logger.info('---') - logger.info('Saved %s', savefile) - sys.exit(0) + if msgs: + if cmdargs.sendidentity: + # Pass exploded series via git-send-email + config = b4.get_config_from_git(rf'sendemail\.{cmdargs.sendidentity}\..*') + if not len(config): + logger.critical('Not able to find sendemail.%s configuration', cmdargs.sendidentity) + sys.exit(1) + # Make sure from is not overridden by current user + mailfrom = msgs[0].get('from') + gitargs = ['send-email', '--identity', cmdargs.sendidentity, '--from', mailfrom] + if cmdargs.dryrun: + gitargs.append('--dry-run') + # Write out everything into a temporary dir + counter = 0 + with tempfile.TemporaryDirectory() as tfd: + for msg in msgs: + outfile = os.path.join(tfd, '%04d' % counter) + with open(outfile, 'wb') as tfh: + tfh.write(msg.as_string(policy=b4.emlpolicy).encode()) + gitargs.append(outfile) + counter += 1 + ecode, out = b4.git_run_command(cmdargs.gitdir, gitargs, logstderr=True) + if cmdargs.dryrun: + logger.info(out) + sys.exit(ecode) + + config = b4.get_main_config() + if config.get('save-maildirs', 'no') == 'yes': + save_maildir = True + dftext = 'maildir' else: - logger.critical('Nothing exploded.') + save_maildir = False + dftext = 'mbx' + savefile = cmdargs.outmbox + if savefile is None: + savefile = f'{lmsg.msgid}.{dftext}' + if os.path.exists(savefile): + logger.info('File exists: %s', savefile) sys.exit(1) + if save_maildir: + b4.save_maildir(msgs, savefile) + else: + with open(savefile, 'wb') as fh: + b4.save_git_am_mbox(msgs, fh) + logger.info('---') + logger.info('Saved %s', savefile) + sys.exit(0) + else: + logger.critical('Nothing exploded.') + sys.exit(1) + exists = b4.git_commit_exists(gitdir, lmsg.pr_tip_commit) if exists: # Is it in any branch, or just flapping in the wind? -- cgit v1.2.3