git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v6 39/45] builtin-am: support and auto-detect StGit patches

From
Paul Tan <pyokagan@gmail.com>
Date
Jul 19, 2015, 16:10 UTC
Message-ID
<1437322237-29863-40-git-send-email-pyokagan@gmail.com>
In-Reply-To
<1437322237-29863-1-git-send-email-pyokagan@gmail.com>

Since c574e68 (git-am foreign patch support: StGIT support, 2009-05-27), git-am.sh supported converting StGit patches into RFC2822 mail patches that can be parsed with git-mailinfo.

Implement this by introducing two functions in builtin/am.c: stgit_patch_to_mail() and split_mail_conv().

stgit_patch_to_mail() is a callback function for split_mail_conv(), and contains the logic for converting an StGit patch into an RFC2822 mail patch.

split_mail_conv() implements the logic to go through each file in the `paths` list, reading from stdin where specified, and calls the callback function to write the converted patch to the corresponding output file in the state directory. This interface should be generic enough to support other foreign patch formats in the future.

Since 15ced75 (git-am foreign patch support: autodetect some patch formats, 2009-05-27), git-am.sh is able to auto-detect StGit patches. Re-implement this in builtin/am.c.

Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Paul Tan <pyokagan@gmail.com>
---
 builtin/am.c | 132 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 131 insertions(+), 1 deletion(-)
diff --git a/builtin/am.c b/builtin/am.c
index 89613e0..101bca3 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -65,9 +65,22 @@ static int linelen(const char *msg)
 	return strchrnul(msg, '\n') - msg;
 }
 
+/**
+ * Returns true if `str` consists of only whitespace, false otherwise.
+ */
+static int str_isspace(const char *str)
+{
+	for (; *str; str++)
+		if (!isspace(*str))
+			return 0;
+
+	return 1;
+}
+
 enum patch_format {
 	PATCH_FORMAT_UNKNOWN = 0,
-	PATCH_FORMAT_MBOX
+	PATCH_FORMAT_MBOX,
+	PATCH_FORMAT_STGIT
 };
 
 enum keep_type {
@@ -612,6 +625,8 @@ static int detect_patch_format(const char **paths)
 {
 	enum patch_format ret = PATCH_FORMAT_UNKNOWN;
 	struct strbuf l1 = STRBUF_INIT;
+	struct strbuf l2 = STRBUF_INIT;
+	struct strbuf l3 = STRBUF_INIT;
 	FILE *fp;
 
 	/*
@@ -637,6 +652,23 @@ static int detect_patch_format(const char **paths)
 		goto done;
 	}
 
+	strbuf_reset(&l2);
+	strbuf_getline_crlf(&l2, fp);
+	strbuf_reset(&l3);
+	strbuf_getline_crlf(&l3, fp);
+
+	/*
+	 * If the second line is empty and the third is a From, Author or Date
+	 * entry, this is likely an StGit patch.
+	 */
+	if (l1.len && !l2.len &&
+		(starts_with(l3.buf, "From:") ||
+		 starts_with(l3.buf, "Author:") ||
+		 starts_with(l3.buf, "Date:"))) {
+		ret = PATCH_FORMAT_STGIT;
+		goto done;
+	}
+
 	if (l1.len && is_mail(fp)) {
 		ret = PATCH_FORMAT_MBOX;
 		goto done;
@@ -677,6 +709,100 @@ static int split_mail_mbox(struct am_state *state, const char **paths, int keep_
 }
 
 /**
+ * Callback signature for split_mail_conv(). The foreign patch should be
+ * read from `in`, and the converted patch (in RFC2822 mail format) should be
+ * written to `out`. Return 0 on success, or -1 on failure.
+ */
+typedef int (*mail_conv_fn)(FILE *out, FILE *in, int keep_cr);
+
+/**
+ * Calls `fn` for each file in `paths` to convert the foreign patch to the
+ * RFC2822 mail format suitable for parsing with git-mailinfo.
+ *
+ * Returns 0 on success, -1 on failure.
+ */
+static int split_mail_conv(mail_conv_fn fn, struct am_state *state,
+			const char **paths, int keep_cr)
+{
+	static const char *stdin_only[] = {"-", NULL};
+	int i;
+
+	if (!*paths)
+		paths = stdin_only;
+
+	for (i = 0; *paths; paths++, i++) {
+		FILE *in, *out;
+		const char *mail;
+		int ret;
+
+		if (!strcmp(*paths, "-"))
+			in = stdin;
+		else
+			in = fopen(*paths, "r");
+
+		if (!in)
+			return error(_("could not open '%s' for reading: %s"),
+					*paths, strerror(errno));
+
+		mail = mkpath("%s/%0*d", state->dir, state->prec, i + 1);
+
+		out = fopen(mail, "w");
+		if (!out)
+			return error(_("could not open '%s' for writing: %s"),
+					mail, strerror(errno));
+
+		ret = fn(out, in, keep_cr);
+
+		fclose(out);
+		fclose(in);
+
+		if (ret)
+			return error(_("could not parse patch '%s'"), *paths);
+	}
+
+	state->cur = 1;
+	state->last = i;
+	return 0;
+}
+
+/**
+ * A split_mail_conv() callback that converts an StGit patch to an RFC2822
+ * message suitable for parsing with git-mailinfo.
+ */
+static int stgit_patch_to_mail(FILE *out, FILE *in, int keep_cr)
+{
+	struct strbuf sb = STRBUF_INIT;
+	int subject_printed = 0;
+
+	while (!strbuf_getline(&sb, in, '\n')) {
+		const char *str;
+
+		if (str_isspace(sb.buf))
+			continue;
+		else if (skip_prefix(sb.buf, "Author:", &str))
+			fprintf(out, "From:%s\n", str);
+		else if (starts_with(sb.buf, "From") || starts_with(sb.buf, "Date"))
+			fprintf(out, "%s\n", sb.buf);
+		else if (!subject_printed) {
+			fprintf(out, "Subject: %s\n", sb.buf);
+			subject_printed = 1;
+		} else {
+			fprintf(out, "\n%s\n", sb.buf);
+			break;
+		}
+	}
+
+	strbuf_reset(&sb);
+	while (strbuf_fread(&sb, 8192, in) > 0) {
+		fwrite(sb.buf, 1, sb.len, out);
+		strbuf_reset(&sb);
+	}
+
+	strbuf_release(&sb);
+	return 0;
+}
+
+/**
  * Splits a list of files/directories into individual email patches. Each path
  * in `paths` must be a file/directory that is formatted according to
  * `patch_format`.
@@ -704,6 +830,8 @@ static int split_mail(struct am_state *state, enum patch_format patch_format,
 	switch (patch_format) {
 	case PATCH_FORMAT_MBOX:
 		return split_mail_mbox(state, paths, keep_cr);
+	case PATCH_FORMAT_STGIT:
+		return split_mail_conv(stgit_patch_to_mail, state, paths, keep_cr);
 	default:
 		die("BUG: invalid patch_format");
 	}
@@ -1751,6 +1879,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int
 
 	if (!strcmp(arg, "mbox"))
 		*opt_value = PATCH_FORMAT_MBOX;
+	else if (!strcmp(arg, "stgit"))
+		*opt_value = PATCH_FORMAT_STGIT;
 	else
 		return error(_("Invalid value for --patch-format: %s"), arg);
 	return 0;
-- 
2.5.0.rc2.110.gb39b692
Previous: Paul TanNext: Paul Tan
Message 41 of 47 in “Make git-am a builtin”
  1. 00/45 Make git-am a builtinPaul Tan, Jul 19, 2015
  2. 01/45 wrapper: implement xopen()Paul Tan, Jul 19, 2015
  3. 02/45 wrapper: implement xfopen()Paul Tan, Jul 19, 2015
  4. 03/45 builtin-am: implement skeletal builtin amPaul Tan, Jul 19, 2015
  5. 04/45 builtin-am: implement patch queue mechanismPaul Tan, Jul 19, 2015
  6. 05/45 builtin-am: split out mbox/maildir patches with git-mailsplitPaul Tan, Jul 19, 2015
  7. 06/45 builtin-am: auto-detect mbox patchesPaul Tan, Jul 19, 2015
  8. 07/45 builtin-am: extract patch and commit info with git-mailinfoPaul Tan, Jul 19, 2015
  9. 08/45 builtin-am: apply patch with git-applyPaul Tan, Jul 19, 2015
  10. 09/45 builtin-am: implement committing applied patchPaul Tan, Jul 19, 2015
  11. 10/45 builtin-am: refuse to apply patches if index is dirtyPaul Tan, Jul 19, 2015
  12. 11/45 builtin-am: implement --resolved/--continuePaul Tan, Jul 19, 2015
  13. 12/45 builtin-am: don't parse mail when resumingPaul Tan, Jul 19, 2015
  14. 13/45 builtin-am: implement --skipPaul Tan, Jul 19, 2015
  15. 14/45 builtin-am: implement --abortPaul Tan, Jul 19, 2015
  16. Stefan BellerJul 31, 2015
  17. 15/45 builtin-am: reject patches when there's a session in progressPaul Tan, Jul 19, 2015
  18. 16/45 builtin-am: implement -q/--quietPaul Tan, Jul 19, 2015
  19. 17/45 builtin-am: exit with user friendly message on failurePaul Tan, Jul 19, 2015
  20. 18/45 builtin-am: implement -s/--signoffPaul Tan, Jul 19, 2015
  21. 19/45 cache-tree: introduce write_index_as_tree()Paul Tan, Jul 19, 2015
  22. 20/45 builtin-am: implement --3way, am.threeWayPaul Tan, Jul 19, 2015
  23. 21/45 builtin-am: implement --rebasing modePaul Tan, Jul 19, 2015
  24. 22/45 builtin-am: bypass git-mailinfo when --rebasingPaul Tan, Jul 19, 2015
  25. 23/45 builtin-am: handle stray state directoryPaul Tan, Jul 19, 2015
  26. 24/45 builtin-am: implement -u/--utf8Paul Tan, Jul 19, 2015
  27. 25/45 builtin-am: implement -k/--keep, --keep-non-patchPaul Tan, Jul 19, 2015
  28. 26/45 builtin-am: implement --[no-]message-id, am.messageidPaul Tan, Jul 19, 2015
  29. 27/45 builtin-am: support --keep-cr, am.keepcrPaul Tan, Jul 19, 2015
  30. 28/45 builtin-am: implement --[no-]scissorsPaul Tan, Jul 19, 2015
  31. 29/45 builtin-am: pass git-apply's options to git-applyPaul Tan, Jul 19, 2015
  32. 30/45 builtin-am: implement --ignore-datePaul Tan, Jul 19, 2015
  33. 31/45 builtin-am: implement --committer-date-is-author-datePaul Tan, Jul 19, 2015
  34. 32/45 builtin-am: implement -S/--gpg-sign, commit.gpgsignPaul Tan, Jul 19, 2015
  35. 33/45 builtin-am: invoke post-rewrite hookPaul Tan, Jul 19, 2015
  36. 34/45 builtin-am: support automatic notes copyingPaul Tan, Jul 19, 2015
  37. 35/45 builtin-am: invoke applypatch-msg hookPaul Tan, Jul 19, 2015
  38. 36/45 builtin-am: invoke pre-applypatch hookPaul Tan, Jul 19, 2015
  39. 37/45 builtin-am: invoke post-applypatch hookPaul Tan, Jul 19, 2015
  40. 38/45 builtin-am: rerere supportPaul Tan, Jul 19, 2015
  41. 39/45 builtin-am: support and auto-detect StGit patchesPaul Tan, Jul 19, 2015
  42. 40/45 builtin-am: support and auto-detect StGit series filesPaul Tan, Jul 19, 2015
  43. 41/45 builtin-am: support and auto-detect mercurial patchesPaul Tan, Jul 19, 2015
  44. 42/45 builtin-am: implement -i/--interactivePaul Tan, Jul 19, 2015
  45. 43/45 builtin-am: implement legacy -b/--binary optionPaul Tan, Jul 19, 2015
  46. 44/45 builtin-am: check for valid committer identPaul Tan, Jul 19, 2015
  47. 45/45 builtin-am: remove redirection to git-am.shPaul Tan, Jul 19, 2015

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.