summaryrefslogtreecommitdiff
path: root/patch-save.py
blob: a9b9a3c3dc3187b2b3848565b2e26fddfd7cce46 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#!/usr/bin/python3
# SPDX-License-Identifier: GPL-2.0-or-later
# Copyright (C) 2022 Laurent Pinchart
#
# Author: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
#
# 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 "<enter-command>unset wait_key<enter>
#   <enter-command>set pipe_decode<enter>
#   <tag-prefix><pipe-entry>~/.mutt/patch-save.py<enter>
#   <enter-command>unset pipe_decode<enter>
#   <enter-command>set wait_key<enter>" "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)

    # mailman can mangle the From header to work around DMARC rejection rules.
    # It then formats From as "Name via mailing-list <mailing-list@domain>" and
    # stores the original sender in the Reply-to header. Restore the original
    # author in the From header to please git-am.
    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))