diff options
author | Sean Whitton <spwhitton@spwhitton.name> | 2019-11-20 22:19:15 -0700 |
---|---|---|
committer | Sean Whitton <spwhitton@spwhitton.name> | 2019-11-20 22:19:15 -0700 |
commit | 33afb1e3af7ce05c8f2ce4af354c8908a14573f5 (patch) | |
tree | 3c0eae4836e7f20c054c193ac3e76da5e0462dfa | |
parent | 914b13d9c578aab098f427aef88b7cbd5eb3f5a2 (diff) | |
parent | 693117551a0e21359ac6dbadba443516c56b04df (diff) | |
download | mailscripts-33afb1e3af7ce05c8f2ce4af354c8908a14573f5.tar.gz |
Merge tag 'debian/0.14-1' into buster-bpo
mailscripts release 0.14-1 for unstable (sid) [dgit]
[dgit distro=debian no-split --quilt=linear]
# gpg: Signature made Fri 15 Nov 2019 06:20:35 PM MST
# gpg: using RSA key 9B917007AE030E36E4FC248B695B7AE4BF066240
# gpg: Good signature from "Sean Whitton <spwhitton@spwhitton.name>" [ultimate]
# Primary key fingerprint: 8DC2 487E 51AB DD90 B5C4 753F 0F56 D055 3B6D 411B
# Subkey fingerprint: 9B91 7007 AE03 0E36 E4FC 248B 695B 7AE4 BF06 6240
-rw-r--r-- | Makefile | 9 | ||||
-rw-r--r-- | debian/changelog | 47 | ||||
-rw-r--r-- | debian/control | 9 | ||||
-rw-r--r-- | debian/mailscripts.bash-completion | 1 | ||||
-rw-r--r-- | debian/mailscripts.install | 10 | ||||
-rw-r--r-- | debian/mailscripts.manpages | 10 | ||||
-rwxr-xr-x | debian/rules | 2 | ||||
-rw-r--r-- | email-extract-openpgp-certs.1.pod | 2 | ||||
-rwxr-xr-x | email-print-mime-structure | 189 | ||||
-rw-r--r-- | email-print-mime-structure.1.pod | 47 | ||||
-rw-r--r-- | mailscripts.el | 88 | ||||
-rw-r--r-- | notmuch-extract-patch.1.pod | 31 | ||||
-rwxr-xr-x | notmuch-extract-patch/notmuch-extract-patch | 24 |
13 files changed, 385 insertions, 84 deletions
@@ -3,14 +3,21 @@ MANPAGES=mdmv.1 mbox2maildir.1 \ email-extract-openpgp-certs.1 \ email-print-mime-structure.1 \ notmuch-import-patch.1 +COMPLETIONS=completions/bash/email-print-mime-structure -all: $(MANPAGES) +all: $(MANPAGES) $(COMPLETIONS) clean: rm -f $(MANPAGES) + rm -rf completions %.1: %.1.pod pod2man --section=1 --date="Debian Project" --center="User Commands" \ --utf8 \ --name=$(subst .1,,$@) \ $^ $@ + +completions/bash/%: + mkdir -p completions/bash + register-python-argcomplete3 $(notdir $@) >$@.tmp + mv $@.tmp $@ diff --git a/debian/changelog b/debian/changelog index 407759f..61d1a11 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,48 @@ +mailscripts (0.14-1) unstable; urgency=medium + + * email-print-mime-structure: add bash completion (Closes: #944434). + Thanks to Daniel Kahn Gillmor for the patch. + - Build-depend on bash-completion, python3-argcomplete. + - Recommend python3-argcomplete. + * email-print-mime-structure: replace --use-gpg-agent=true with + --use-gpg-agent, and add --no-use-gpg-agent (Closes: #944475). + This is due to limitations in Python's argparse library. + Thanks to Daniel Kahn Gillmor for the report and a patch. + * Declare compliance with Debian Policy 4.4.1. + Thanks to Daniel Kahn Gillmor for taking the time to verify that no + changes are required. + + -- Sean Whitton <spwhitton@spwhitton.name> Fri, 15 Nov 2019 18:19:04 -0700 + +mailscripts (0.13-1) unstable; urgency=medium + + * notmuch-extract-patch: add -v/--reroll-count option (Closes: #944418). + * mailscripts.el: prefix arg to pass -v/--reroll-count to + notmuch-extract-patch. + * email-print-mime-structure: add --use-gpg-agent option (Closes: #944340). + Thanks to Daniel Kahn Gillmor for the patch series. + - Suggest gpg & gpg-agent. + + -- Sean Whitton <spwhitton@spwhitton.name> Sun, 10 Nov 2019 01:12:04 -0700 + +mailscripts (0.12-1) unstable; urgency=medium + + * email-print-mime-structure: make typesafe. + Thanks to Daniel Kahn Gillmor for the patch. + * email-print-mime-structure: add capability to decrypt message parts + (Closes: #943959). + Thanks to Daniel Kahn Gillmor for the patch series. + + * mailscripts.el: + - new defcustom: mailscripts-extract-patches-branch-prefix + - new commands: + + notmuch-extract-thread-patches-projectile + + notmuch-extract-message-patches{,-projectile} + - if user does not enter a branch name, use current HEAD. + * elpa-mailscripts now depends on elpa-projectile. + + -- Sean Whitton <spwhitton@spwhitton.name> Wed, 06 Nov 2019 20:54:56 -0700 + mailscripts (0.11-1~bpo10+1) buster-backports; urgency=medium * Rebuild for buster-backports. @@ -7,7 +52,7 @@ mailscripts (0.11-1~bpo10+1) buster-backports; urgency=medium mailscripts (0.11-1) unstable; urgency=medium * New script: email-print-mime-structure (Closes: #939993). - Imported from the notmuch project, which never shipped it in releases. + Imported from the notmuch project, which never installed it. Thanks to Daniel Kahn Gillmor for the patches. * Generate nroff output in UTF-8. Thanks to Daniel Kahn Gillmor for the patch. diff --git a/debian/control b/debian/control index 6d3a54f..72b57c3 100644 --- a/debian/control +++ b/debian/control @@ -2,11 +2,13 @@ Source: mailscripts Section: mail Priority: optional Maintainer: Sean Whitton <spwhitton@spwhitton.name> -Standards-Version: 4.1.5 +Standards-Version: 4.4.1 Build-Depends: + bash-completion, debhelper (>= 10), dh-elpa, perl, + python3-argcomplete, Vcs-Git: https://git.spwhitton.name/mailscripts Vcs-Browser: https://git.spwhitton.name/mailscripts @@ -39,6 +41,11 @@ Recommends: devscripts, git, notmuch, + python3-argcomplete, + python3-pgpy, +Suggests: + gpg, + gpg-agent, Architecture: all Description: collection of scripts for manipulating e-mail on Debian This package provides a collection of scripts for manipulating e-mail diff --git a/debian/mailscripts.bash-completion b/debian/mailscripts.bash-completion new file mode 100644 index 0000000..435576f --- /dev/null +++ b/debian/mailscripts.bash-completion @@ -0,0 +1 @@ +completions/bash/email-print-mime-structure diff --git a/debian/mailscripts.install b/debian/mailscripts.install index 99216c1..2c060df 100644 --- a/debian/mailscripts.install +++ b/debian/mailscripts.install @@ -1,8 +1,8 @@ +email-extract-openpgp-certs /usr/bin +email-print-mime-structure /usr/bin +maildir-import-patch /usr/bin mbox2maildir /usr/bin mdmv /usr/bin -notmuch-slurp-debbug /usr/bin -maildir-import-patch /usr/bin -notmuch-import-patch /usr/bin notmuch-extract-patch/notmuch-extract-patch /usr/bin -email-extract-openpgp-certs /usr/bin -email-print-mime-structure /usr/bin +notmuch-import-patch /usr/bin +notmuch-slurp-debbug /usr/bin diff --git a/debian/mailscripts.manpages b/debian/mailscripts.manpages index 6d7cb30..1de088f 100644 --- a/debian/mailscripts.manpages +++ b/debian/mailscripts.manpages @@ -1,8 +1,8 @@ +email-extract-openpgp-certs.1 +email-print-mime-structure.1 +maildir-import-patch.1 mbox2maildir.1 mdmv.1 -notmuch-slurp-debbug.1 -maildir-import-patch.1 -notmuch-import-patch.1 notmuch-extract-patch.1 -email-extract-openpgp-certs.1 -email-print-mime-structure.1 +notmuch-import-patch.1 +notmuch-slurp-debbug.1 diff --git a/debian/rules b/debian/rules index e8e22ba..6d50bf4 100755 --- a/debian/rules +++ b/debian/rules @@ -1,4 +1,4 @@ #!/usr/bin/make -f %: - dh $@ --with elpa + dh $@ --with elpa --with bash-completion diff --git a/email-extract-openpgp-certs.1.pod b/email-extract-openpgp-certs.1.pod index 9983de0..d1d641a 100644 --- a/email-extract-openpgp-certs.1.pod +++ b/email-extract-openpgp-certs.1.pod @@ -38,7 +38,7 @@ message's cryptographic envelope. B<email-extract-openpgp-certs> does not attempt to validate the certificates it finds in any way. It does not ensure that they are valid OpenPGP certificates, or even that they are of a sane size. It -doeds not try to establish any relationship between the extracted +does not try to establish any relationship between the extracted certificates and the messages in which they are sent. For example, it does not check the Autocrypt addr= attribute against the message's From: header. diff --git a/email-print-mime-structure b/email-print-mime-structure index 7adeb2b..4f165b1 100755 --- a/email-print-mime-structure +++ b/email-print-mime-structure @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# PYTHON_ARGCOMPLETE_OK # -*- coding: utf-8 -*- # Copyright (C) 2019 Daniel Kahn Gillmor @@ -29,49 +30,153 @@ Example: If you want to number the parts, i suggest piping the output through something like "cat -n" ''' -import email +import os import sys +import email +import logging +import subprocess -def print_part(z, prefix): - fname = '' if z.get_filename() is None else ' [' + z.get_filename() + ']' - cset = '' if z.get_charset() is None else ' (' + z.get_charset() + ')' - disp = z.get_params(None, header='Content-Disposition') - if (disp is None): - disposition = '' - else: - disposition = '' - for d in disp: - if d[0] in [ 'attachment', 'inline' ]: - disposition = ' ' + d[0] - if z.is_multipart(): - nbytes = len(z.as_string()) - else: - nbytes = len(z.get_payload()) - - print('{}{}{}{}{} {:d} bytes'.format( - prefix, - z.get_content_type(), - cset, - disposition, - fname, - nbytes, - )) - -def test(z, prefix=''): - if (z.is_multipart()): - print_part(z, prefix+'┬╴') - if prefix.endswith('└'): - prefix = prefix.rpartition('└')[0] + ' ' - if prefix.endswith('├'): - prefix = prefix.rpartition('├')[0] + '│' - parts = z.get_payload() - i = 0 - while (i < parts.__len__()-1): - test(parts[i], prefix + '├') - i += 1 - test(parts[i], prefix + '└') - # FIXME: show epilogue? +from argparse import ArgumentParser, Namespace +from typing import Optional, Union, List, Tuple, Any +from email.charset import Charset +from email.message import Message + +try: + import pgpy #type: ignore +except ImportError: + pgpy = None + +try: + import argcomplete #type: ignore +except ImportError: + argcomplete = None + +class MimePrinter(object): + def __init__(self, args:Namespace): + self.args = args + + def print_part(self, z:Message, prefix:str, parent:Optional[Message], num:int) -> None: + ofname:Optional[str] = z.get_filename() + fname:str = '' if ofname is None else f' [{ofname}]' + ocharset:Union[Charset, str, None] = z.get_charset() + cset:str = '' if ocharset is None else f' ({ocharset})' + disp:Union[List[Tuple[str,str]], List[str], None] = z.get_params(None, header='Content-Disposition') + disposition:str = '' + if (disp is not None): + for d in disp: + if d[0] in [ 'attachment', 'inline' ]: + disposition = ' ' + d[0] + nbytes:int + if z.is_multipart(): + # FIXME: it looks like we are counting chars here, not bytes: + nbytes = len(z.as_string()) + else: + payload:Union[List[Message], str, bytes, None] = z.get_payload() + if not isinstance(payload, (str,bytes)): + raise TypeError(f'expected payload to be either str or bytes, got {type(payload)}') + # FIXME: it looks like we are counting chars here, not bytes: + nbytes = len(payload) + + print(f'{prefix}{z.get_content_type()}{cset}{disposition}{fname} {nbytes:d} bytes') + try_decrypt:bool = self.args.pgpkey or self.args.use_gpg_agent + + if try_decrypt and \ + (parent is not None) and \ + (parent.get_content_type().lower() == 'multipart/encrypted') and \ + (str(parent.get_param('protocol')).lower() == 'application/pgp-encrypted') and \ + (num == 2): + cryptopayload:Optional[Message] = None + ciphertext:Union[List[Message],str,bytes,None] = z.get_payload() + if not isinstance(ciphertext, str): + logging.warning('encrypted part was not a leaf mime part somehow') + return + if self.args.pgpkey: + cryptopayload = self.pgpy_decrypt(self.args.pgpkey, ciphertext) + if cryptopayload is None and self.args.use_gpg_agent: + cryptopayload = self.gpg_decrypt(ciphertext) + if cryptopayload is None: + logging.warning(f'Unable to decrypt') + return + newprefix = prefix[:-3] + ' ' + print(f'{newprefix}↧ (decrypts to)') + self.print_tree(cryptopayload, newprefix + '└', z, 0) + + def pgpy_decrypt(self, keys:List[str], ciphertext:str) -> Optional[Message]: + if pgpy is None: + logging.warning(f'Python module pgpy is not available, not decrypting (try "apt install python3-pgpy")') + return None + keyname:str + ret:Optional[Message] = None + for keyname in keys: + try: + key:pgpy.PGPKey + key, _ = pgpy.PGPKey.from_file(keyname) + msg:pgpy.PGPMessage = pgpy.PGPMessage.from_blob(ciphertext) + msg = key.decrypt(msg) + return email.message_from_bytes(msg.message) + except: + pass + return None + + def gpg_decrypt(self, ciphertext:str) -> Optional[Message]: + inp:int + outp:int + inp, outp = os.pipe() + with open(outp, 'w') as outf: + outf.write(ciphertext) + out:subprocess.CompletedProcess[bytes] = subprocess.run(['gpg', '--batch', '--decrypt'], + stdin=inp, + capture_output=True) + if out.returncode == 0: + return email.message_from_bytes(out.stdout) + return None + + def print_tree(self, z:Message, prefix:str, parent:Optional[Message], num:int) -> None: + if (z.is_multipart()): + self.print_part(z, prefix+'┬╴', parent, num) + if prefix.endswith('└'): + prefix = prefix.rpartition('└')[0] + ' ' + if prefix.endswith('├'): + prefix = prefix.rpartition('├')[0] + '│' + parts:Union[List[Message], str, bytes, None] = z.get_payload() + if not isinstance(parts, list): + raise TypeError(f'parts was {type(parts)}, expected List[Message]') + i = 0 + while (i < len(parts)-1): + self.print_tree(parts[i], prefix + '├', z, i+1) + i += 1 + self.print_tree(parts[i], prefix + '└', z, i+1) + # FIXME: show epilogue? + else: + self.print_part(z, prefix+'─╴', parent, num) + +def main() -> None: + parser:ArgumentParser = ArgumentParser(description='Read RFC2822 MIME message from stdin and emit a tree diagram to stdout.', + epilog="Example: email-print-mime-structure <message.eml") + parser.add_argument('--pgpkey', metavar='KEYFILE', action='append', + help='OpenPGP Transferable Secret Key for decrypting') + parser.add_argument('--use-gpg-agent', action='store_true', + help='Ask local GnuPG installation for decryption') + parser.add_argument('--no-use-gpg-agent', action='store_false', + help='Don\'t ask local GnuPG installation for decryption') + parser.set_defaults(use_gpg_agent=False) + + if argcomplete: + argcomplete.autocomplete(parser) + elif '_ARGCOMPLETE' in os.environ: + logging.error('Argument completion requested but the "argcomplete" ' + 'module is not installed. ' + 'Maybe you want to "apt install python3-argcomplete"') + sys.exit(1) + + args:Namespace = parser.parse_args() + msg:Union[Message, str, int, Any] = email.message_from_file(sys.stdin) + + if isinstance(msg, Message): + printer:MimePrinter = MimePrinter(args) + printer.print_tree(msg, '└', None, 0) else: - print_part(z, prefix+'─╴') + logging.error('Input was not an e-mail message') -test(email.message_from_file(sys.stdin), '└') +if __name__ == '__main__': + main() diff --git a/email-print-mime-structure.1.pod b/email-print-mime-structure.1.pod index ab1ec05..d8545ad 100644 --- a/email-print-mime-structure.1.pod +++ b/email-print-mime-structure.1.pod @@ -19,7 +19,45 @@ something like "cat -n". =head1 OPTIONS -None. +=over 4 + +=item B<--pgpkey=>I<KEYFILE> + +I<KEYFILE> should name an OpenPGP transferable secret key that is not +password-protected. If a PGP/MIME-encrypted message is found on +standard input, this key will be tried for decryption. May be used +multiple times if you want to try decrypting with more than one secret +key. + +OpenPGP secret keys listed in B<--pgpkey=> are used ephemerally, and +do not interact with any local GnuPG keyring. + +=item B<--use-gpg-agent> + +If this flag is present, and B<email-print-mime-structure> encounters +a PGP/MIME-encrypted part, it will try to decrypt the part using the +secret keys found in the local installation of GnuPG. + +If both B<--pgpkey=>I<KEYFILE> and B<--use-gpg-agent> are +supplied, I<KEYFILE> arguments will be tried before falling back to +GnuPG. + +If B<email-print-mime-structure> has been asked to decrypt parts with +either B<--pgpkey=>I<KEYFILE> or with B<--use-gpg-agent>, and it +is unable to decrypt an encrypted part, it will emit a warning to +stderr. + +=item B<--no-use-gpg-agent> + +Don't try to decrypt PGP/MIME-encrypted parts using secret keys found +in the local installation of GnuPG. This is the default. + +=item B<--help>, B<-h> + +Show usage instructions. + +=back + =head1 EXAMPLE @@ -34,10 +72,6 @@ None. =head1 LIMITATIONS -B<email-print-mime-structure> currently does not try to decrypt -encrypted e-mails, so it cannot display the MIME structure that is -inside the message's cryptographic envelope. - B<email-print-mime-structure>'s output is not stable, and is not intended to be interpreted by machines, so please do not depend on it in scripts! @@ -52,7 +86,8 @@ environment. =head1 SEE ALSO -https://tools.ietf.org/html/rfc2045, https://tools.ietf.org/html/rfc2049 +https://tools.ietf.org/html/rfc2045, https://tools.ietf.org/html/rfc2049, +https://tools.ietf.org/html/rfc3156 =head1 AUTHOR diff --git a/mailscripts.el b/mailscripts.el index f0002fc..916aec8 100644 --- a/mailscripts.el +++ b/mailscripts.el @@ -1,10 +1,10 @@ ;;; mailscripts.el --- functions to access tools in the mailscripts package ;; Author: Sean Whitton <spwhitton@spwhitton.name> -;; Version: 0.11 -;; Package-Requires: (notmuch) +;; Version: 0.13 +;; Package-Requires: (notmuch projectile) -;; Copyright (C) 2018 Sean Whitton +;; Copyright (C) 2018, 2019 Sean Whitton ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by @@ -22,6 +22,17 @@ ;;; Code: (require 'notmuch) +(require 'projectile) + +(defgroup mailscripts nil + "Customisation of functions in the mailscripts package.") + +(defcustom mailscripts-extract-patches-branch-prefix nil + "Prefix for git branches created by functions which extract patch series. + +E.g. `email/'." + :type 'string + :group 'mailscripts) ;;;###autoload (defun notmuch-slurp-debbug (bug &optional no-open) @@ -43,25 +54,84 @@ If NO-OPEN, don't open the thread." (notmuch-refresh-this-buffer))) ;;;###autoload -(defun notmuch-extract-thread-patches (repo branch) +(defun notmuch-extract-thread-patches (repo branch &optional reroll-count) "Extract patch series in current thread to branch BRANCH in repo REPO. The target branch may or may not already exist. +With an optional prefix numeric argument REROLL-COUNT, try to +extract the nth revision of a series. See the --reroll-count +option detailed in notmuch-extract-patch(1). + See notmuch-extract-patch(1) manpage for limitations: in particular, this Emacs Lisp function supports passing only entire threads to the notmuch-extract-patch(1) command." - (interactive "Dgit repo: \nsnew branch name: ") + (interactive + "Dgit repo: \nsbranch name (or leave blank to apply to current HEAD): \np") (let ((thread-id notmuch-show-thread-id) (default-directory (expand-file-name repo))) - (call-process-shell-command - (format "git checkout -b %s" - (shell-quote-argument branch))) + (mailscripts--check-out-branch branch) (shell-command - (format "notmuch-extract-patch %s | git am" + (format "notmuch-extract-patch -v%d %s | git am" + (if reroll-count reroll-count 1) (shell-quote-argument thread-id)) "*notmuch-apply-thread-series*"))) +;;;###autoload +(defun notmuch-extract-thread-patches-projectile () + "Like `notmuch-extract-thread-patches', but use projectile to choose the repo." + (interactive) + (mailscripts--projectile-repo-and-branch + 'notmuch-extract-thread-patches (prefix-numeric-value current-prefix-arg))) + +;;;###autoload +(defun notmuch-extract-message-patches (repo branch) + "Extract patches attached to current message to branch BRANCH in repo REPO. + +The target branch may or may not already exist. + +Patches are applied using git-am(1), so we only consider +attachments with filenames which look like they were generated by +git-format-patch(1)." + (interactive + "Dgit repo: \nsbranch name (or leave blank to apply to current HEAD): ") + (with-current-notmuch-show-message + (let ((default-directory (expand-file-name repo)) + (mm-handle (mm-dissect-buffer))) + (mailscripts--check-out-branch branch) + (notmuch-foreach-mime-part + (lambda (p) + (let* ((disposition (mm-handle-disposition p)) + (filename (cdr (assq 'filename disposition)))) + (and filename + (string-match + "^\\(v[0-9]+-\\)?[0-9]+-.+\.\\(patch\\|diff\\|txt\\)$" filename) + (mm-pipe-part p "git am")))) + mm-handle)))) + +;;;###autoload +(defun notmuch-extract-message-patches-projectile () + "Like `notmuch-extract-message-patches', but use projectile to choose the repo." + (interactive) + (mailscripts--projectile-repo-and-branch 'notmuch-extract-message-patches)) + +(defun mailscripts--check-out-branch (branch) + (unless (string= branch "") + (call-process-shell-command + (format "git checkout -b %s" + (shell-quote-argument + (if mailscripts-extract-patches-branch-prefix + (concat mailscripts-extract-patches-branch-prefix branch) + branch)))))) + +(defun mailscripts--projectile-repo-and-branch (f &rest args) + (let ((repo (projectile-completing-read + "Select projectile project: " projectile-known-projects)) + (branch (completing-read + "Branch name (or leave blank to apply to current HEAD): " + nil))) + (apply f repo branch args))) + (provide 'mailscripts) ;;; mailscripts.el ends here diff --git a/notmuch-extract-patch.1.pod b/notmuch-extract-patch.1.pod index 21095bc..a18cc22 100644 --- a/notmuch-extract-patch.1.pod +++ b/notmuch-extract-patch.1.pod @@ -4,7 +4,7 @@ notmuch-extract-patch - extract a git patch series from notmuch =head1 SYNOPSIS -B<notmuch-extract-patch> I<QUERY> +B<notmuch-extract-patch> [B<-v>|B<--reroll-count=>I<N>] I<QUERY> =head1 DESCRIPTION @@ -15,7 +15,23 @@ replies/reviews. =head1 OPTIONS -None. +=over 4 + +=item B<-v>|B<--reroll-count=>I<N> + +Try to extract the I<N>th version of a patch series, where these +patches are identified by subject prefixes like "[PATCH vI<N> 1/3]". + +If this option is not specified, default to extracting the first +version of the patch series. + +Note that this option should not usually be needed, because best +practices when sharing patches with git-send-email(1) include starting +a new thread when posting a revised series. The I<--in-reply-to> +option to git-format-patch(1) is used mainly for posting a patch +series in reply to a bug report. + +=back =head1 EXAMPLE @@ -28,17 +44,12 @@ None. =head1 LIMITATIONS -B<notmuch-extract-patch> assumes one patch series per query. So if -there is more than one patch series in a thread, you will need to +B<notmuch-extract-patch> can select patches to extract based on the +reroll count, but otherwise assumes that there is only one patch +series in a thread. If this assumption is violated, you would need to construct a notmuch query that includes only the patches you want to extract, which somewhat defeats the purpose of this script. -This should not happen often because best practices when sharing -patches with git-send-email(1) include starting a new thread when -posting a revised series. The I<--in-reply-to> option to -B<notmuch-extract-patch> is used mainly for posting a patch series in -reply to a bug report. - =head1 SEE ALSO notmuch(1), git-send-email(1) diff --git a/notmuch-extract-patch/notmuch-extract-patch b/notmuch-extract-patch/notmuch-extract-patch index cfd4464..4cfda4c 100755 --- a/notmuch-extract-patch/notmuch-extract-patch +++ b/notmuch-extract-patch/notmuch-extract-patch @@ -22,6 +22,7 @@ import sys import tempfile import subprocess import re +import getopt def get_body(message): body = None @@ -48,8 +49,27 @@ def is_git_patch(msg): # return ("git-send-email" in msg['x-mailer'] and match) return match +def has_reroll_count(msg, v): + subject_prefix = get_subject_prefix(msg['subject']) + if subject_prefix is not None: + return "v"+str(v) in subject_prefix \ + or (v == 1 and not any(entry[0] == 'v' for entry in subject_prefix)) + +def get_subject_prefix(s): + match = re.search(r'''^\[(.*PATCH.*)\]''', s) + if match: + return match.group(1).split() + def main(): - query = sys.argv[1:] + try: + opts, query = getopt.getopt(sys.argv[1:], "v:", ["reroll-count="]) + except getopt.GetoptError as err: + sys.stderr.write(str(err)+"\n") + sys.exit(2) + reroll_count = 1 + for o, a in opts: + if o in ("-v", "--reroll-count"): + reroll_count = int(a) with tempfile.NamedTemporaryFile() as in_mb_file: out = subprocess.check_output(['notmuch', 'show', '--format=mbox']+query) in_mb_file.write(out) @@ -59,7 +79,7 @@ def main(): with tempfile.NamedTemporaryFile() as out_mb_file: out_mb = mailbox.mbox(out_mb_file.name) for m in in_mb: - if is_git_patch(m): + if is_git_patch(m) and has_reroll_count(m, reroll_count): sys.stderr.write(m['subject']+"\n") out_mb.add(m) out_mb.flush() |