git send-email --notmuch expr

5 messages, 2 authors, 2016-06-15 · open the first message on its own page

git send-email --notmuch expr

From: Jed Brown <hidden>
Date: 2016-06-15 22:47:46

Notmuch is a new mail system with fast indexing and tagging, see
notmuchmail.org.  It has a command-line tool to build replies to emails,
and I wrote a patch to format-patch that enables

  git format-patch --notmuch EXPR

where EXPR is any notmuch query, but usually id:<Message-ID> to match a
specific message.  This will set up several headers, notably
In-Reply-To, References, To, Cc, Bcc.  This works great, and the the
patch follows this message.


But I really want

  git send-email --notmuch EXPR

This sort-of works, but the interactive part prompts for the various
headers (even though format-patch supplies them), and then duplicates
the To header (both the possibly empty field the user provided, plus the
one coming from format-patch).  I had a brief look at
git-send-email.perl, and I'm a little confused.

	} elsif (/^(?:To|Cc|Bcc):/i) {
		print "To/Cc/Bcc fields are not interpreted yet, they have been ignored\n";
		next;
	}

This regex doesn't match these headers (is the leading ?: a typo?) so
there is no warning.  But it's important that these headers *not* be
ignored, getting them set automatically is a key feature of the
--notmuch option.  I'm having trouble discerning whether I would cause
problems by just using these headers coming out of format-patch.
Presumably there was a reason why they were (intended to be) explicitly
ignored, and my poor perl skills are not helping.  Could someone
enlighten me?


Jed

[PATCH 1/2] Added the --notmuch option to git format-patch.

From: Jed Brown <hidden>
Date: 2016-06-15 22:47:46

The typical use case for this is

  git format-patch --notmuch id:<MESSAGE-ID>

which will format your patch with all threading, references, and To, Cc
fields appropriate for a reply to the given message.
---
 builtin-log.c |   61 +++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 57 insertions(+), 4 deletions(-)
diff --git a/builtin-log.c b/builtin-log.c
index 33fa6ea..9a44955 100644
--- a/builtin-log.c
+++ b/builtin-log.c
@@ -754,6 +754,53 @@ static const char *clean_message_id(const char *msg_id)
 	return xmemdupz(a, z - a);
 }
 
+static int get_notmuch_reply(struct strbuf *buf, struct string_list *ref_message_ids, const char *query)
+{
+	struct child_process notmuch;
+	struct strbuf notmuch_out;
+	char *p, *other_headers, *rstart;
+	ssize_t len;
+	const char *argv[] = {"notmuch", "reply", "--format=headers-only", query, NULL};
+
+	memset(&notmuch, 0, sizeof(notmuch));
+
+	notmuch.argv	 = argv;
+	notmuch.no_stdin = 1;
+	notmuch.out	 = -1;
+
+	if (start_command(&notmuch))
+		return error("could not run notmuch.");
+
+	strbuf_init(&notmuch_out, 4096);
+	len = strbuf_read(&notmuch_out, notmuch.out, 4096);
+	close(notmuch.out);
+
+	if (finish_command(&notmuch) || !len || len < 0)
+		return error("notmuch did not return any headers");
+
+	/* Harvest the referenced message IDs, all on the first line */
+	p = notmuch_out.buf;
+
+	if (!strncmp(p, "References: ", sizeof("References: ")))
+		return error("notmuch response malformed");
+
+	other_headers = strstr(p, "To: ");
+	if (!other_headers)
+		return error("notmuch provided no other headers");
+
+	while ((rstart = strchr(p, '<')) && (p = strchr(rstart, '>')) && p < other_headers-1) {
+		if (!p)
+			error("notmuch returned malformed references");
+		p++;
+		*p++ = 0;
+		string_list_append(clean_message_id(rstart), ref_message_ids);
+	}
+	/* Add everything after the first line */
+	strbuf_addstr(buf, other_headers);
+	strbuf_release(&notmuch_out);
+	return 0;
+}
+
 static const char *set_outdir(const char *prefix, const char *output_directory)
 {
 	if (output_directory && is_absolute_path(output_directory))
@@ -893,7 +940,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 	int boundary_count = 0;
 	int no_binary_diff = 0;
 	struct commit *origin = NULL, *head = NULL;
-	const char *in_reply_to = NULL;
+	const char *in_reply_to = NULL, *notmuch = NULL;
 	struct patch_ids ids;
 	char *add_signoff = NULL;
 	struct strbuf buf = STRBUF_INIT;
@@ -940,6 +987,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 			    PARSE_OPT_NONEG, cc_callback },
 		OPT_STRING(0, "in-reply-to", &in_reply_to, "message-id",
 			    "make first mail a reply to <message-id>"),
+		OPT_STRING(0, "notmuch", &notmuch, "query",
+			    "make first mail a reply to messages matched by <query>"),
 		{ OPTION_CALLBACK, 0, "attach", &rev, "boundary",
 			    "attach the patch", PARSE_OPT_OPTARG,
 			    attach_callback },
@@ -1015,8 +1064,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		strbuf_addch(&buf, '\n');
 	}
 
-	rev.extra_headers = strbuf_detach(&buf, NULL);
-
 	if (start_number < 0)
 		start_number = 1;
 
@@ -1135,12 +1182,18 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
 		numbered = 1;
 	if (numbered)
 		rev.total = total + start_number - 1;
-	if (in_reply_to || thread || cover_letter)
+	if (in_reply_to || thread || cover_letter || notmuch)
 		rev.ref_message_ids = xcalloc(1, sizeof(struct string_list));
 	if (in_reply_to) {
 		const char *msgid = clean_message_id(in_reply_to);
 		string_list_append(msgid, rev.ref_message_ids);
 	}
+	if (notmuch) {
+		get_notmuch_reply(&buf, rev.ref_message_ids, notmuch);
+	}
+
+	rev.extra_headers = strbuf_detach(&buf, NULL);
+
 	rev.numbered_files = numbered_files;
 	rev.patch_suffix = fmt_patch_suffix;
 	if (cover_letter) {
-- 
1.6.5.3

[PATCH 2/2] Documentation for format-patch --notmuch.

From: Jed Brown <hidden>
Date: 2016-06-15 22:47:46

---
 Documentation/git-format-patch.txt |    7 +++++++
 1 files changed, 7 insertions(+), 0 deletions(-)
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index f1fd0df..aaa472d 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -171,6 +171,13 @@ will want to ensure that threading is disabled for `git send-email`.
 	to any configured headers, and may be used multiple times.
 	For example, `--add-header="Organization: git-foo"`
 
+--notmuch=<expr>::
+	Generate headers appropriate for a reply to a notmuch search for the
+	given expression.  Usually an expression of the form id:<Message-ID>
+	will be used to match an exact message.  The reply will set
+	In-Reply-To, References, To, Cc, Bcc, and possibly other headers
+	(depending on notmuch configuration).
+
 --cover-letter::
 	In addition to the patches, generate a cover letter file
 	containing the shortlog and the overall diffstat.  You can
-- 
1.6.5.3

Re: git send-email --notmuch expr

From: Jakub Narebski <hidden>
Date: 2016-06-15 22:47:46

Jed Brown [off-list ref] writes:
	} elsif (/^(?:To|Cc|Bcc):/i) {
		print "To/Cc/Bcc fields are not interpreted yet, they have been ignored\n";
		next;
	}

This regex doesn't match these headers (is the leading ?: a typo?) so
there is no warning.
(?: ... ) is in Perl non-capturing grouping
From perlre(1)
  "(?:pattern)"
  "(?imsx-imsx:pattern)"
       This is for clustering, not capturing; it groups subexpressions
       like "()", but doesn't make backreferences as "()" does.

So it is not a bug, and it definitely should match... unless implicit
variable $_ (the default input and pattern-searching space) got
mangled.  It would be better to use explicit form:

   $variable =~ /pattern/

-- 
Jakub Narebski
Poland
ShadeHawk on #git

Re: git send-email --notmuch expr

From: Jed Brown <hidden>
Date: 2016-06-15 22:47:46

On Wed, 25 Nov 2009 02:16:52 -0800 (PST), Jakub Narebski [off-list ref] wrote:
(?: ... ) is in Perl non-capturing grouping
Thanks.  Actually that code only executes under --compose, the headers
provided by format-patch all just come through untouched (but all the
interesting ones are duplicated).  So it looks like we just need to
actually parse the relevant headers from format-patch, before the part
where the user gets prompted.  These are a little harder because they
can span multiple lines.  The current validation with --compose is a bit
broken: suppose the user sets the perfectly valid header

To: foo@example.com,
    bar@example.com

The validation will strip the first line while issuing warning, but
send the second through untouched.

Jed
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help