Skip to content

Commit 483932a

Browse files
committed
Merge branch 'dd/mailinfo-quoted-cr'
"git mailinfo" (hence "git am") learned the "--quoted-cr" option to control how lines ending with CRLF wrapped in base64 or qp are handled. * dd/mailinfo-quoted-cr: am: learn to process quoted lines that ends with CRLF mailinfo: allow stripping quoted CR without warning mailinfo: allow squelching quoted CRLF warning mailinfo: warn if CRLF found in decoded base64/QP email mailinfo: stop parsing options manually mailinfo: load default metainfo_charset lazily
2 parents c8e34a7 + 59b519a commit 483932a

File tree

14 files changed

+376
-34
lines changed

14 files changed

+376
-34
lines changed

Documentation/git-am.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ SYNOPSIS
1515
[--whitespace=<option>] [-C<n>] [-p<n>] [--directory=<dir>]
1616
[--exclude=<path>] [--include=<path>] [--reject] [-q | --quiet]
1717
[--[no-]scissors] [-S[<keyid>]] [--patch-format=<format>]
18+
[--quoted-cr=<action>]
1819
[(<mbox> | <Maildir>)...]
1920
'git am' (--continue | --skip | --abort | --quit | --show-current-patch[=(diff|raw)])
2021

@@ -59,6 +60,9 @@ OPTIONS
5960
--no-scissors::
6061
Ignore scissors lines (see linkgit:git-mailinfo[1]).
6162

63+
--quoted-cr=<action>::
64+
This flag will be passed down to 'git mailinfo' (see linkgit:git-mailinfo[1]).
65+
6266
-m::
6367
--message-id::
6468
Pass the `-m` flag to 'git mailinfo' (see linkgit:git-mailinfo[1]),

Documentation/git-mailinfo.txt

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ git-mailinfo - Extracts patch and authorship from a single e-mail message
99
SYNOPSIS
1010
--------
1111
[verse]
12-
'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n] [--[no-]scissors] <msg> <patch>
12+
'git mailinfo' [-k|-b] [-u | --encoding=<encoding> | -n]
13+
[--[no-]scissors] [--quoted-cr=<action>]
14+
<msg> <patch>
1315

1416

1517
DESCRIPTION
@@ -89,6 +91,23 @@ This can be enabled by default with the configuration option mailinfo.scissors.
8991
--no-scissors::
9092
Ignore scissors lines. Useful for overriding mailinfo.scissors settings.
9193

94+
--quoted-cr=<action>::
95+
Action when processes email messages sent with base64 or
96+
quoted-printable encoding, and the decoded lines end with a CRLF
97+
instead of a simple LF.
98+
+
99+
The valid actions are:
100+
+
101+
--
102+
* `nowarn`: Git will do nothing when such a CRLF is found.
103+
* `warn`: Git will issue a warning for each message if such a CRLF is
104+
found.
105+
* `strip`: Git will convert those CRLF to LF.
106+
--
107+
+
108+
The default action could be set by configuration option `mailinfo.quotedCR`.
109+
If no such configuration option has been set, `warn` will be used.
110+
92111
<msg>::
93112
The commit log message extracted from e-mail, usually
94113
except the title line which comes from e-mail Subject.

builtin/am.c

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ struct am_state {
116116
int keep; /* enum keep_type */
117117
int message_id;
118118
int scissors; /* enum scissors_type */
119+
int quoted_cr; /* enum quoted_cr_action */
119120
struct strvec git_apply_opts;
120121
const char *resolvemsg;
121122
int committer_date_is_author_date;
@@ -145,6 +146,7 @@ static void am_state_init(struct am_state *state)
145146
git_config_get_bool("am.messageid", &state->message_id);
146147

147148
state->scissors = SCISSORS_UNSET;
149+
state->quoted_cr = quoted_cr_unset;
148150

149151
strvec_init(&state->git_apply_opts);
150152

@@ -165,6 +167,16 @@ static void am_state_release(struct am_state *state)
165167
strvec_clear(&state->git_apply_opts);
166168
}
167169

170+
static int am_option_parse_quoted_cr(const struct option *opt,
171+
const char *arg, int unset)
172+
{
173+
BUG_ON_OPT_NEG(unset);
174+
175+
if (mailinfo_parse_quoted_cr_action(arg, opt->value) != 0)
176+
return error(_("bad action '%s' for '%s'"), arg, "--quoted-cr");
177+
return 0;
178+
}
179+
168180
/**
169181
* Returns path relative to the am_state directory.
170182
*/
@@ -397,6 +409,12 @@ static void am_load(struct am_state *state)
397409
else
398410
state->scissors = SCISSORS_UNSET;
399411

412+
read_state_file(&sb, state, "quoted-cr", 1);
413+
if (!*sb.buf)
414+
state->quoted_cr = quoted_cr_unset;
415+
else if (mailinfo_parse_quoted_cr_action(sb.buf, &state->quoted_cr) != 0)
416+
die(_("could not parse %s"), am_path(state, "quoted-cr"));
417+
400418
read_state_file(&sb, state, "apply-opt", 1);
401419
strvec_clear(&state->git_apply_opts);
402420
if (sq_dequote_to_strvec(sb.buf, &state->git_apply_opts) < 0)
@@ -1002,6 +1020,24 @@ static void am_setup(struct am_state *state, enum patch_format patch_format,
10021020
}
10031021
write_state_text(state, "scissors", str);
10041022

1023+
switch (state->quoted_cr) {
1024+
case quoted_cr_unset:
1025+
str = "";
1026+
break;
1027+
case quoted_cr_nowarn:
1028+
str = "nowarn";
1029+
break;
1030+
case quoted_cr_warn:
1031+
str = "warn";
1032+
break;
1033+
case quoted_cr_strip:
1034+
str = "strip";
1035+
break;
1036+
default:
1037+
BUG("invalid value for state->quoted_cr");
1038+
}
1039+
write_state_text(state, "quoted-cr", str);
1040+
10051041
sq_quote_argv(&sb, state->git_apply_opts.v);
10061042
write_state_text(state, "apply-opt", sb.buf);
10071043

@@ -1162,6 +1198,18 @@ static int parse_mail(struct am_state *state, const char *mail)
11621198
BUG("invalid value for state->scissors");
11631199
}
11641200

1201+
switch (state->quoted_cr) {
1202+
case quoted_cr_unset:
1203+
break;
1204+
case quoted_cr_nowarn:
1205+
case quoted_cr_warn:
1206+
case quoted_cr_strip:
1207+
mi.quoted_cr = state->quoted_cr;
1208+
break;
1209+
default:
1210+
BUG("invalid value for state->quoted_cr");
1211+
}
1212+
11651213
mi.input = xfopen(mail, "r");
11661214
mi.output = xfopen(am_path(state, "info"), "w");
11671215
if (mailinfo(&mi, am_path(state, "msg"), am_path(state, "patch")))
@@ -2242,6 +2290,9 @@ int cmd_am(int argc, const char **argv, const char *prefix)
22422290
0, PARSE_OPT_NONEG),
22432291
OPT_BOOL('c', "scissors", &state.scissors,
22442292
N_("strip everything before a scissors line")),
2293+
OPT_CALLBACK_F(0, "quoted-cr", &state.quoted_cr, N_("action"),
2294+
N_("pass it through git-mailinfo"),
2295+
PARSE_OPT_NONEG, am_option_parse_quoted_cr),
22452296
OPT_PASSTHRU_ARGV(0, "whitespace", &state.git_apply_opts, N_("action"),
22462297
N_("pass it through git-apply"),
22472298
0),

builtin/mailinfo.c

Lines changed: 82 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,54 +7,103 @@
77
#include "utf8.h"
88
#include "strbuf.h"
99
#include "mailinfo.h"
10+
#include "parse-options.h"
1011

11-
static const char mailinfo_usage[] =
12-
"git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding=<encoding> | -n] [--scissors | --no-scissors] <msg> <patch> < mail >info";
12+
static const char * const mailinfo_usage[] = {
13+
/* TRANSLATORS: keep <> in "<" mail ">" info. */
14+
N_("git mailinfo [<options>] <msg> <patch> < mail >info"),
15+
NULL,
16+
};
17+
18+
struct metainfo_charset
19+
{
20+
enum {
21+
CHARSET_DEFAULT,
22+
CHARSET_NO_REENCODE,
23+
CHARSET_EXPLICIT,
24+
} policy;
25+
const char *charset;
26+
};
27+
28+
static int parse_opt_explicit_encoding(const struct option *opt,
29+
const char *arg, int unset)
30+
{
31+
struct metainfo_charset *meta_charset = opt->value;
32+
33+
BUG_ON_OPT_NEG(unset);
34+
35+
meta_charset->policy = CHARSET_EXPLICIT;
36+
meta_charset->charset = arg;
37+
38+
return 0;
39+
}
40+
41+
static int parse_opt_quoted_cr(const struct option *opt, const char *arg, int unset)
42+
{
43+
BUG_ON_OPT_NEG(unset);
44+
45+
if (mailinfo_parse_quoted_cr_action(arg, opt->value) != 0)
46+
return error(_("bad action '%s' for '%s'"), arg, "--quoted-cr");
47+
return 0;
48+
}
1349

1450
int cmd_mailinfo(int argc, const char **argv, const char *prefix)
1551
{
16-
const char *def_charset;
52+
struct metainfo_charset meta_charset;
1753
struct mailinfo mi;
1854
int status;
1955
char *msgfile, *patchfile;
2056

57+
struct option options[] = {
58+
OPT_BOOL('k', NULL, &mi.keep_subject, N_("keep subject")),
59+
OPT_BOOL('b', NULL, &mi.keep_non_patch_brackets_in_subject,
60+
N_("keep non patch brackets in subject")),
61+
OPT_BOOL('m', "message-id", &mi.add_message_id,
62+
N_("copy Message-ID to the end of commit message")),
63+
OPT_SET_INT_F('u', NULL, &meta_charset.policy,
64+
N_("re-code metadata to i18n.commitEncoding"),
65+
CHARSET_DEFAULT, PARSE_OPT_NONEG),
66+
OPT_SET_INT_F('n', NULL, &meta_charset.policy,
67+
N_("disable charset re-coding of metadata"),
68+
CHARSET_NO_REENCODE, PARSE_OPT_NONEG),
69+
OPT_CALLBACK_F(0, "encoding", &meta_charset, N_("encoding"),
70+
N_("re-code metadata to this encoding"),
71+
PARSE_OPT_NONEG, parse_opt_explicit_encoding),
72+
OPT_BOOL(0, "scissors", &mi.use_scissors, N_("use scissors")),
73+
OPT_CALLBACK_F(0, "quoted-cr", &mi.quoted_cr, N_("<action>"),
74+
N_("action when quoted CR is found"),
75+
PARSE_OPT_NONEG, parse_opt_quoted_cr),
76+
OPT_HIDDEN_BOOL(0, "inbody-headers", &mi.use_inbody_headers,
77+
N_("use headers in message's body")),
78+
OPT_END()
79+
};
80+
2181
setup_mailinfo(&mi);
82+
meta_charset.policy = CHARSET_DEFAULT;
2283

23-
def_charset = get_commit_output_encoding();
24-
mi.metainfo_charset = def_charset;
25-
26-
while (1 < argc && argv[1][0] == '-') {
27-
if (!strcmp(argv[1], "-k"))
28-
mi.keep_subject = 1;
29-
else if (!strcmp(argv[1], "-b"))
30-
mi.keep_non_patch_brackets_in_subject = 1;
31-
else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--message-id"))
32-
mi.add_message_id = 1;
33-
else if (!strcmp(argv[1], "-u"))
34-
mi.metainfo_charset = def_charset;
35-
else if (!strcmp(argv[1], "-n"))
36-
mi.metainfo_charset = NULL;
37-
else if (starts_with(argv[1], "--encoding="))
38-
mi.metainfo_charset = argv[1] + 11;
39-
else if (!strcmp(argv[1], "--scissors"))
40-
mi.use_scissors = 1;
41-
else if (!strcmp(argv[1], "--no-scissors"))
42-
mi.use_scissors = 0;
43-
else if (!strcmp(argv[1], "--no-inbody-headers"))
44-
mi.use_inbody_headers = 0;
45-
else
46-
usage(mailinfo_usage);
47-
argc--; argv++;
48-
}
84+
argc = parse_options(argc, argv, prefix, options, mailinfo_usage, 0);
4985

50-
if (argc != 3)
51-
usage(mailinfo_usage);
86+
if (argc != 2)
87+
usage_with_options(mailinfo_usage, options);
88+
89+
switch (meta_charset.policy) {
90+
case CHARSET_DEFAULT:
91+
mi.metainfo_charset = get_commit_output_encoding();
92+
break;
93+
case CHARSET_NO_REENCODE:
94+
mi.metainfo_charset = NULL;
95+
break;
96+
case CHARSET_EXPLICIT:
97+
break;
98+
default:
99+
BUG("invalid meta_charset.policy");
100+
}
52101

53102
mi.input = stdin;
54103
mi.output = stdout;
55104

56-
msgfile = prefix_filename(prefix, argv[1]);
57-
patchfile = prefix_filename(prefix, argv[2]);
105+
msgfile = prefix_filename(prefix, argv[0]);
106+
patchfile = prefix_filename(prefix, argv[1]);
58107

59108
status = !!mailinfo(&mi, msgfile, patchfile);
60109
clear_mailinfo(&mi);

contrib/completion/git-completion.bash

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,6 +1333,7 @@ __git_whitespacelist="nowarn warn error error-all fix"
13331333
__git_patchformat="mbox stgit stgit-series hg mboxrd"
13341334
__git_showcurrentpatch="diff raw"
13351335
__git_am_inprogress_options="--skip --continue --resolved --abort --quit --show-current-patch"
1336+
__git_quoted_cr="nowarn warn strip"
13361337

13371338
_git_am ()
13381339
{
@@ -1354,6 +1355,10 @@ _git_am ()
13541355
__gitcomp "$__git_showcurrentpatch" "" "${cur##--show-current-patch=}"
13551356
return
13561357
;;
1358+
--quoted-cr=*)
1359+
__gitcomp "$__git_quoted_cr" "" "${cur##--quoted-cr=}"
1360+
return
1361+
;;
13571362
--*)
13581363
__gitcomp_builtin am "" \
13591364
"$__git_am_inprogress_options"

mailinfo.c

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,16 @@ static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,
994994
const char *rest;
995995

996996
if (!mi->format_flowed) {
997+
if (len >= 2 &&
998+
line->buf[len - 2] == '\r' &&
999+
line->buf[len - 1] == '\n') {
1000+
mi->have_quoted_cr = 1;
1001+
if (mi->quoted_cr == quoted_cr_strip) {
1002+
strbuf_setlen(line, len - 2);
1003+
strbuf_addch(line, '\n');
1004+
len--;
1005+
}
1006+
}
9971007
handle_filter(mi, line);
9981008
return;
9991009
}
@@ -1033,6 +1043,13 @@ static void handle_filter_flowed(struct mailinfo *mi, struct strbuf *line,
10331043
handle_filter(mi, line);
10341044
}
10351045

1046+
static void summarize_quoted_cr(struct mailinfo *mi)
1047+
{
1048+
if (mi->have_quoted_cr &&
1049+
mi->quoted_cr == quoted_cr_warn)
1050+
warning(_("quoted CRLF detected"));
1051+
}
1052+
10361053
static void handle_body(struct mailinfo *mi, struct strbuf *line)
10371054
{
10381055
struct strbuf prev = STRBUF_INIT;
@@ -1051,6 +1068,8 @@ static void handle_body(struct mailinfo *mi, struct strbuf *line)
10511068
handle_filter(mi, &prev);
10521069
strbuf_reset(&prev);
10531070
}
1071+
summarize_quoted_cr(mi);
1072+
mi->have_quoted_cr = 0;
10541073
if (!handle_boundary(mi, line))
10551074
goto handle_body_out;
10561075
}
@@ -1100,6 +1119,7 @@ static void handle_body(struct mailinfo *mi, struct strbuf *line)
11001119

11011120
if (prev.len)
11021121
handle_filter(mi, &prev);
1122+
summarize_quoted_cr(mi);
11031123

11041124
flush_inbody_header_accum(mi);
11051125

@@ -1206,6 +1226,19 @@ int mailinfo(struct mailinfo *mi, const char *msg, const char *patch)
12061226
return mi->input_error;
12071227
}
12081228

1229+
int mailinfo_parse_quoted_cr_action(const char *actionstr, int *action)
1230+
{
1231+
if (!strcmp(actionstr, "nowarn"))
1232+
*action = quoted_cr_nowarn;
1233+
else if (!strcmp(actionstr, "warn"))
1234+
*action = quoted_cr_warn;
1235+
else if (!strcmp(actionstr, "strip"))
1236+
*action = quoted_cr_strip;
1237+
else
1238+
return -1;
1239+
return 0;
1240+
}
1241+
12091242
static int git_mailinfo_config(const char *var, const char *value, void *mi_)
12101243
{
12111244
struct mailinfo *mi = mi_;
@@ -1216,6 +1249,11 @@ static int git_mailinfo_config(const char *var, const char *value, void *mi_)
12161249
mi->use_scissors = git_config_bool(var, value);
12171250
return 0;
12181251
}
1252+
if (!strcmp(var, "mailinfo.quotedcr")) {
1253+
if (mailinfo_parse_quoted_cr_action(value, &mi->quoted_cr) != 0)
1254+
return error(_("bad action '%s' for '%s'"), value, var);
1255+
return 0;
1256+
}
12191257
/* perhaps others here */
12201258
return 0;
12211259
}
@@ -1228,6 +1266,7 @@ void setup_mailinfo(struct mailinfo *mi)
12281266
strbuf_init(&mi->charset, 0);
12291267
strbuf_init(&mi->log_message, 0);
12301268
strbuf_init(&mi->inbody_header_accum, 0);
1269+
mi->quoted_cr = quoted_cr_warn;
12311270
mi->header_stage = 1;
12321271
mi->use_inbody_headers = 1;
12331272
mi->content_top = mi->content;

0 commit comments

Comments
 (0)