builtin-am: auto-detect mbox patches

Since 15ced75 (git-am foreign patch support: autodetect some patch formats, 2009-05-27), git-am.sh is able to autodetect mbox, stgit and mercurial patches through heuristics. Re-implement support for autodetecting mbox/maildir files in builtin/am.c. RFC 2822 requires that lines are terminated by "\r\n". To support this, implement strbuf_getline_crlf(), which will remove both '\n' and "\r\n" from the end of the line. Helped-by: Junio C Hamano <gitster@pobox.com> Helped-by: Eric Sunshine <sunshine@sunshineco.com> Helped-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Paul Tan <pyokagan@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Paul Tan committed Aug 4, 2015 at 21:51 UTC c29807b27dc60e17eb1cd8bc50900af2a09ce66f
1 file changed +109
builtin/am.c
+109
@@ -10,6 +10,21 @@
10 #include "dir.h"
11 #include "run-command.h"
12
13 +/**
14 + * Like strbuf_getline(), but treats both '\n' and "\r\n" as line terminators.
15 + */
16 +static int strbuf_getline_crlf(struct strbuf *sb, FILE *fp)
17 +{
18 + if (strbuf_getwholeline(sb, fp, '\n'))
19 + return EOF;
20 + if (sb->buf[sb->len - 1] == '\n') {
21 + strbuf_setlen(sb, sb->len - 1);
22 + if (sb->len > 0 && sb->buf[sb->len - 1] == '\r')
23 + strbuf_setlen(sb, sb->len - 1);
24 + }
25 + return 0;
26 +}
27 +
28 enum patch_format {
29 PATCH_FORMAT_UNKNOWN = 0,
30 PATCH_FORMAT_MBOX
@@ -127,6 +142,92 @@ static void am_destroy(const struct am_state *state)
142 strbuf_release(&sb);
143 }
144
145 +/**
146 + * Determines if the file looks like a piece of RFC2822 mail by grabbing all
147 + * non-indented lines and checking if they look like they begin with valid
148 + * header field names.
149 + *
150 + * Returns 1 if the file looks like a piece of mail, 0 otherwise.
151 + */
152 +static int is_mail(FILE *fp)
153 +{
154 + const char *header_regex = "^[!-9;-~]+:";
155 + struct strbuf sb = STRBUF_INIT;
156 + regex_t regex;
157 + int ret = 1;
158 +
159 + if (fseek(fp, 0L, SEEK_SET))
160 + die_errno(_("fseek failed"));
161 +
162 + if (regcomp(&regex, header_regex, REG_NOSUB | REG_EXTENDED))
163 + die("invalid pattern: %s", header_regex);
164 +
165 + while (!strbuf_getline_crlf(&sb, fp)) {
166 + if (!sb.len)
167 + break; /* End of header */
168 +
169 + /* Ignore indented folded lines */
170 + if (*sb.buf == '\t' || *sb.buf == ' ')
171 + continue;
172 +
173 + /* It's a header if it matches header_regex */
174 + if (regexec(&regex, sb.buf, 0, NULL, 0)) {
175 + ret = 0;
176 + goto done;
177 + }
178 + }
179 +
180 +done:
181 + regfree(&regex);
182 + strbuf_release(&sb);
183 + return ret;
184 +}
185 +
186 +/**
187 + * Attempts to detect the patch_format of the patches contained in `paths`,
188 + * returning the PATCH_FORMAT_* enum value. Returns PATCH_FORMAT_UNKNOWN if
189 + * detection fails.
190 + */
191 +static int detect_patch_format(const char **paths)
192 +{
193 + enum patch_format ret = PATCH_FORMAT_UNKNOWN;
194 + struct strbuf l1 = STRBUF_INIT;
195 + FILE *fp;
196 +
197 + /*
198 + * We default to mbox format if input is from stdin and for directories
199 + */
200 + if (!*paths || !strcmp(*paths, "-") || is_directory(*paths))
201 + return PATCH_FORMAT_MBOX;
202 +
203 + /*
204 + * Otherwise, check the first few lines of the first patch, starting
205 + * from the first non-blank line, to try to detect its format.
206 + */
207 +
208 + fp = xfopen(*paths, "r");
209 +
210 + while (!strbuf_getline_crlf(&l1, fp)) {
211 + if (l1.len)
212 + break;
213 + }
214 +
215 + if (starts_with(l1.buf, "From ") || starts_with(l1.buf, "From: ")) {
216 + ret = PATCH_FORMAT_MBOX;
217 + goto done;
218 + }
219 +
220 + if (l1.len && is_mail(fp)) {
221 + ret = PATCH_FORMAT_MBOX;
222 + goto done;
223 + }
224 +
225 +done:
226 + fclose(fp);
227 + strbuf_release(&l1);
228 + return ret;
229 +}
230 +
231 /**
232 * Splits out individual email patches from `paths`, where each path is either
233 * a mbox file or a Maildir. Returns 0 on success, -1 on failure.
@@ -185,6 +286,14 @@ static int split_mail(struct am_state *state, enum patch_format patch_format,
286 static void am_setup(struct am_state *state, enum patch_format patch_format,
287 const char **paths)
288 {
289 + if (!patch_format)
290 + patch_format = detect_patch_format(paths);
291 +
292 + if (!patch_format) {
293 + fprintf_ln(stderr, _("Patch format detection failed."));
294 + exit(128);
295 + }
296 +
297 if (mkdir(state->dir, 0777) < 0 && errno != EEXIST)
298 die_errno(_("failed to create directory '%s'"), state->dir);
299