Skip to content

Commit 3aa4d81

Browse files
rscharfegitster
authored andcommitted
mailinfo: support format=flowed
Add best-effort support for patches sent using format=flowed (RFC 3676). Remove leading spaces ("unstuff"), remove soft line breaks (indicated by space + newline), but leave the signature separator (dash dash space newline) alone. Warn in git am when encountering a format=flowed patch, because any trailing spaces would most probably be lost, as the sending MUA is encouraged to remove them when preparing the email. Provide a test patch formatted by Mozilla Thunderbird 60 using its default configuration. It reuses the contents of the file mailinfo.c before and after this patch. Signed-off-by: Rene Scharfe <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 53f9a3e commit 3aa4d81

File tree

7 files changed

+2646
-2
lines changed

7 files changed

+2646
-2
lines changed

builtin/am.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,6 +1243,10 @@ static int parse_mail(struct am_state *state, const char *mail)
12431243
fclose(mi.input);
12441244
fclose(mi.output);
12451245

1246+
if (mi.format_flowed)
1247+
warning(_("Patch sent with format=flowed; "
1248+
"space at the end of lines might be lost."));
1249+
12461250
/* Extract message and author information */
12471251
fp = xfopen(am_path(state, "info"), "r");
12481252
while (!strbuf_getline_lf(&sb, fp)) {

mailinfo.c

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,11 +237,22 @@ static int slurp_attr(const char *line, const char *name, struct strbuf *attr)
237237
return 1;
238238
}
239239

240+
static int has_attr_value(const char *line, const char *name, const char *value)
241+
{
242+
struct strbuf sb = STRBUF_INIT;
243+
int rc = slurp_attr(line, name, &sb) && !strcasecmp(sb.buf, value);
244+
strbuf_release(&sb);
245+
return rc;
246+
}
247+
240248
static void handle_content_type(struct mailinfo *mi, struct strbuf *line)
241249
{
242250
struct strbuf *boundary = xmalloc(sizeof(struct strbuf));
243251
strbuf_init(boundary, line->len);
244252

253+
mi->format_flowed = has_attr_value(line->buf, "format=", "flowed");
254+
mi->delsp = has_attr_value(line->buf, "delsp=", "yes");
255+
245256
if (slurp_attr(line->buf, "boundary=", boundary)) {
246257
strbuf_insert(boundary, 0, "--", 2);
247258
if (++mi->content_top >= &mi->content[MAX_BOUNDARIES]) {
@@ -964,6 +975,52 @@ static int handle_boundary(struct mailinfo *mi, struct strbuf *line)
964975
return 1;
965976
}
966977

978+
static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,
979+
struct strbuf *prev)
980+
{
981+
size_t len = line->len;
982+
const char *rest;
983+
984+
if (!mi->format_flowed) {
985+
handle_filter(mi, line);
986+
return;
987+
}
988+
989+
if (line->buf[len - 1] == '\n') {
990+
len--;
991+
if (len && line->buf[len - 1] == '\r')
992+
len--;
993+
}
994+
995+
/* Keep signature separator as-is. */
996+
if (skip_prefix(line->buf, "-- ", &rest) && rest - line->buf == len) {
997+
if (prev->len) {
998+
handle_filter(mi, prev);
999+
strbuf_reset(prev);
1000+
}
1001+
handle_filter(mi, line);
1002+
return;
1003+
}
1004+
1005+
/* Unstuff space-stuffed line. */
1006+
if (len && line->buf[0] == ' ') {
1007+
strbuf_remove(line, 0, 1);
1008+
len--;
1009+
}
1010+
1011+
/* Save flowed line for later, but without the soft line break. */
1012+
if (len && line->buf[len - 1] == ' ') {
1013+
strbuf_add(prev, line->buf, len - !!mi->delsp);
1014+
return;
1015+
}
1016+
1017+
/* Prepend any previous partial lines */
1018+
strbuf_insert(line, 0, prev->buf, prev->len);
1019+
strbuf_reset(prev);
1020+
1021+
handle_filter(mi, line);
1022+
}
1023+
9671024
static void handle_body(struct mailinfo *mi, struct strbuf *line)
9681025
{
9691026
struct strbuf prev = STRBUF_INIT;
@@ -1012,7 +1069,7 @@ static void handle_body(struct mailinfo *mi, struct strbuf *line)
10121069
strbuf_addbuf(&prev, sb);
10131070
break;
10141071
}
1015-
handle_filter(mi, sb);
1072+
handle_filter_flowed(mi, sb, &prev);
10161073
}
10171074
/*
10181075
* The partial chunk is saved in "prev" and will be
@@ -1022,13 +1079,16 @@ static void handle_body(struct mailinfo *mi, struct strbuf *line)
10221079
break;
10231080
}
10241081
default:
1025-
handle_filter(mi, line);
1082+
handle_filter_flowed(mi, line, &prev);
10261083
}
10271084

10281085
if (mi->input_error)
10291086
break;
10301087
} while (!strbuf_getwholeline(line, mi->input, '\n'));
10311088

1089+
if (prev.len)
1090+
handle_filter(mi, &prev);
1091+
10321092
flush_inbody_header_accum(mi);
10331093

10341094
handle_body_out:

mailinfo.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ struct mailinfo {
2020
struct strbuf *content[MAX_BOUNDARIES];
2121
struct strbuf **content_top;
2222
struct strbuf charset;
23+
unsigned int format_flowed:1;
24+
unsigned int delsp:1;
2325
char *message_id;
2426
enum {
2527
TE_DONTCARE, TE_QP, TE_BASE64

t/t4256-am-format-flowed.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/bin/sh
2+
3+
test_description='test format=flowed support of git am'
4+
5+
. ./test-lib.sh
6+
7+
test_expect_success 'setup' '
8+
cp "$TEST_DIRECTORY/t4256/1/mailinfo.c.orig" mailinfo.c &&
9+
git add mailinfo.c &&
10+
git commit -m initial
11+
'
12+
13+
test_expect_success 'am with format=flowed' '
14+
git am <"$TEST_DIRECTORY/t4256/1/patch" >stdout 2>stderr &&
15+
test_i18ngrep "warning: Patch sent with format=flowed" stderr &&
16+
test_cmp "$TEST_DIRECTORY/t4256/1/mailinfo.c" mailinfo.c
17+
'
18+
19+
test_done

0 commit comments

Comments
 (0)