#!/usr/bin/python3 # SPDX-License-Identifier: GPL-2.0-or-later # Copyright (C) 2022 Laurent Pinchart # # Author: Laurent Pinchart # # Patch save script for mutt that handles mailman From header mangling. # # To install, copy the script to ~/.mutt/, and add the following lines to your # .muttrc to bind to ctrl-h. # # set pipe_split=yes # macro index \Ch "unset wait_key # set pipe_decode # ~/.mutt/patch-save.py # unset pipe_decode # set wait_key" "output git patches" # import email import email.parser import email.policy import pathlib import re import sys def sanitize(s): out = '' for c in s: if c in '[]': continue if c in "'": c = '.' elif c in '/:': c = '-' elif c in '*()" \t\n\r': c = '_' out += c return out def main(argv): # Parse the message from stdin as binary, as we can't assume any particular # encoding. parser = email.parser.BytesParser(policy=email.policy.default) msg = parser.parse(sys.stdin.buffer) # Handle mailman message mangling used to work around DMARC rejection rules: # # - mailman may encapsulate original messages in a message/rfc822 part. # That's an easy case, simply extract the encapsulated message. Any # additional mangling of the outer message (such as From header mangling) # can be ignored. # # - mailman may mangle the From header. It then formats From as "Name via # mailing-list " and stores the original sender in # the Reply-to header. Restore the original author in the From header to # please git-am. if msg.get_content_type() == 'message/rfc822': msg = next(msg.iter_parts()) else: from_header = msg.get('From') reply_to = msg.get('Reply-to') if reply_to and ' via ' in from_header: msg.replace_header('From', reply_to) subject = msg.get('Subject') if not subject: return 1 # The subject can be an str instance or an email.header.Header instance. # Convert it to str in all cases. subject = str(subject) # Strip everything before the [PATCH] or [RESEND] tag. match = re.search(r'\[(PATCH|RESEND) [^]]*\].*', subject) if match: subject = match.group(0) # Sanitize the subject. subject = sanitize(subject) subject = subject.replace('..', '.') subject = subject.replace('-_', '_') subject = subject.replace('__', '_') subject = subject.strip() subject = subject.rstrip('.') file_name = pathlib.Path.home() / '.maildir' / 'patches' / (subject + '.patch') open(file_name, 'wb').write(bytes(msg)) return 0 if __name__ == '__main__': sys.exit(main(sys.argv))