rebase: really just passthru the `git am` options

Currently, we parse the options intended for `git am` as if we wanted to handle them in `git rebase`, and then reconstruct them painstakingly to define the `git_am_opt` variable. However, there is a much better way (that I was unaware of, at the time when I mentored Pratik to implement these options): OPT_PASSTHRU_ARGV. It is intended for exactly this use case, where command-line options want to be parsed into a separate `argv_array`. Let's use this feature. Incidentally, this also allows us to address a bug discovered by Phillip Wood, where the built-in rebase failed to understand that the `-C` option takes an optional argument. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Johannes Schindelin committed Nov 14, 2018 at 08:25 UTC f57696802c300494562f4287e88f07540b3dbd4a
1 file changed +35 -63
builtin/rebase.c
+35 -63
@@ -87,7 +87,7 @@ struct rebase_options {
87 REBASE_FORCE = 1<<3,
88 REBASE_INTERACTIVE_EXPLICIT = 1<<4,
89 } flags;
90 - struct strbuf git_am_opt;
90 + struct argv_array git_am_opts;
91 const char *action;
92 int signoff;
93 int allow_rerere_autoupdate;
@@ -339,7 +339,7 @@ N_("Resolve all conflicts manually, mark them as resolved with\n"
339 static int run_specific_rebase(struct rebase_options *opts)
340 {
341 const char *argv[] = { NULL, NULL };
342 - struct strbuf script_snippet = STRBUF_INIT;
342 + struct strbuf script_snippet = STRBUF_INIT, buf = STRBUF_INIT;
343 int status;
344 const char *backend, *backend_func;
345
@@ -433,7 +433,9 @@ static int run_specific_rebase(struct rebase_options *opts)
433 oid_to_hex(&opts->restrict_revision->object.oid) : NULL);
434 add_var(&script_snippet, "GIT_QUIET",
435 opts->flags & REBASE_NO_QUIET ? "" : "t");
436 - add_var(&script_snippet, "git_am_opt", opts->git_am_opt.buf);
436 + sq_quote_argv_pretty(&buf, opts->git_am_opts.argv);
437 + add_var(&script_snippet, "git_am_opt", buf.buf);
438 + strbuf_release(&buf);
439 add_var(&script_snippet, "verbose",
440 opts->flags & REBASE_VERBOSE ? "t" : "");
441 add_var(&script_snippet, "diffstat",
@@ -756,7 +758,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
758 struct rebase_options options = {
759 .type = REBASE_UNSPECIFIED,
760 .flags = REBASE_NO_QUIET,
759 - .git_am_opt = STRBUF_INIT,
761 + .git_am_opts = ARGV_ARRAY_INIT,
762 .allow_rerere_autoupdate = -1,
763 .allow_empty_message = 1,
764 .git_format_patch_opt = STRBUF_INIT,
@@ -777,12 +779,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
779 ACTION_EDIT_TODO,
780 ACTION_SHOW_CURRENT_PATCH,
781 } action = NO_ACTION;
780 - int committer_date_is_author_date = 0;
781 - int ignore_date = 0;
782 - int ignore_whitespace = 0;
782 const char *gpg_sign = NULL;
784 - int opt_c = -1;
785 - struct string_list whitespace = STRING_LIST_INIT_NODUP;
783 struct string_list exec = STRING_LIST_INIT_NODUP;
784 const char *rebase_merges = NULL;
785 int fork_point = -1;
@@ -804,15 +801,20 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
801 {OPTION_NEGBIT, 'n', "no-stat", &options.flags, NULL,
802 N_("do not show diffstat of what changed upstream"),
803 PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT },
807 - OPT_BOOL(0, "ignore-whitespace", &ignore_whitespace,
808 - N_("passed to 'git apply'")),
804 OPT_BOOL(0, "signoff", &options.signoff,
805 N_("add a Signed-off-by: line to each commit")),
811 - OPT_BOOL(0, "committer-date-is-author-date",
812 - &committer_date_is_author_date,
813 - N_("passed to 'git am'")),
814 - OPT_BOOL(0, "ignore-date", &ignore_date,
815 - N_("passed to 'git am'")),
806 + OPT_PASSTHRU_ARGV(0, "ignore-whitespace", &options.git_am_opts,
807 + NULL, N_("passed to 'git am'"),
808 + PARSE_OPT_NOARG),
809 + OPT_PASSTHRU_ARGV(0, "committer-date-is-author-date",
810 + &options.git_am_opts, NULL,
811 + N_("passed to 'git am'"), PARSE_OPT_NOARG),
812 + OPT_PASSTHRU_ARGV(0, "ignore-date", &options.git_am_opts, NULL,
813 + N_("passed to 'git am'"), PARSE_OPT_NOARG),
814 + OPT_PASSTHRU_ARGV('C', NULL, &options.git_am_opts, N_("n"),
815 + N_("passed to 'git apply'"), 0),
816 + OPT_PASSTHRU_ARGV(0, "whitespace", &options.git_am_opts,
817 + N_("action"), N_("passed to 'git apply'"), 0),
818 OPT_BIT('f', "force-rebase", &options.flags,
819 N_("cherry-pick all commits, even if unchanged"),
820 REBASE_FORCE),
@@ -856,10 +858,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
858 { OPTION_STRING, 'S', "gpg-sign", &gpg_sign, N_("key-id"),
859 N_("GPG-sign commits"),
860 PARSE_OPT_OPTARG, NULL, (intptr_t) "" },
859 - OPT_STRING_LIST(0, "whitespace", &whitespace,
860 - N_("whitespace"), N_("passed to 'git apply'")),
861 - OPT_SET_INT('C', NULL, &opt_c, N_("passed to 'git apply'"),
862 - REBASE_AM),
861 OPT_BOOL(0, "autostash", &options.autostash,
862 N_("automatically stash/stash pop before and after")),
863 OPT_STRING_LIST('x', "exec", &exec, N_("exec"),
@@ -884,6 +882,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
882 N_("rebase all reachable commits up to the root(s)")),
883 OPT_END(),
884 };
885 + int i;
886
887 /*
888 * NEEDSWORK: Once the builtin rebase has been tested enough
@@ -1064,22 +1063,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
1063 state_dir_base, cmd_live_rebase, buf.buf);
1064 }
1065
1067 - if (!(options.flags & REBASE_NO_QUIET))
1068 - strbuf_addstr(&options.git_am_opt, " -q");
1069 -
1070 - if (committer_date_is_author_date) {
1071 - strbuf_addstr(&options.git_am_opt,
1072 - " --committer-date-is-author-date");
1073 - options.flags |= REBASE_FORCE;
1066 + for (i = 0; i < options.git_am_opts.argc; i++) {
1067 + const char *option = options.git_am_opts.argv[i];
1068 + if (!strcmp(option, "--committer-date-is-author-date") ||
1069 + !strcmp(option, "--ignore-date") ||
1070 + !strcmp(option, "--whitespace=fix") ||
1071 + !strcmp(option, "--whitespace=strip"))
1072 + options.flags |= REBASE_FORCE;
1073 }
1074
1076 - if (ignore_whitespace)
1077 - strbuf_addstr(&options.git_am_opt, " --ignore-whitespace");
1078 -
1079 - if (ignore_date) {
1080 - strbuf_addstr(&options.git_am_opt, " --ignore-date");
1081 - options.flags |= REBASE_FORCE;
1082 - }
1075 + if (!(options.flags & REBASE_NO_QUIET))
1076 + argv_array_push(&options.git_am_opts, "-q");
1077
1078 if (options.keep_empty)
1079 imply_interactive(&options, "--keep-empty");
@@ -1089,23 +1083,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
1083 options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign);
1084 }
1085
1092 - if (opt_c >= 0)
1093 - strbuf_addf(&options.git_am_opt, " -C%d", opt_c);
1094 -
1095 - if (whitespace.nr) {
1096 - int i;
1097 -
1098 - for (i = 0; i < whitespace.nr; i++) {
1099 - const char *item = whitespace.items[i].string;
1100 -
1101 - strbuf_addf(&options.git_am_opt, " --whitespace=%s",
1102 - item);
1103 -
1104 - if ((!strcmp(item, "fix")) || (!strcmp(item, "strip")))
1105 - options.flags |= REBASE_FORCE;
1106 - }
1107 - }
1108 -
1086 if (exec.nr) {
1087 int i;
1088
@@ -1181,23 +1158,18 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
1158 break;
1159 }
1160
1184 - if (options.git_am_opt.len) {
1185 - const char *p;
1186 -
1161 + if (options.git_am_opts.argc) {
1162 /* all am options except -q are compatible only with --am */
1188 - strbuf_reset(&buf);
1189 - strbuf_addbuf(&buf, &options.git_am_opt);
1190 - strbuf_addch(&buf, ' ');
1191 - while ((p = strstr(buf.buf, " -q ")))
1192 - strbuf_splice(&buf, p - buf.buf, 4, " ", 1);
1193 - strbuf_trim(&buf);
1163 + for (i = options.git_am_opts.argc - 1; i >= 0; i--)
1164 + if (strcmp(options.git_am_opts.argv[i], "-q"))
1165 + break;
1166
1195 - if (is_interactive(&options) && buf.len)
1167 + if (is_interactive(&options) && i >= 0)
1168 die(_("error: cannot combine interactive options "
1169 "(--interactive, --exec, --rebase-merges, "
1170 "--preserve-merges, --keep-empty, --root + "
1171 "--onto) with am options (%s)"), buf.buf);
1200 - if (options.type == REBASE_MERGE && buf.len)
1172 + if (options.type == REBASE_MERGE && i >= 0)
1173 die(_("error: cannot combine merge options (--merge, "
1174 "--strategy, --strategy-option) with am options "
1175 "(%s)"), buf.buf);
@@ -1207,7 +1179,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
1179 if (options.type == REBASE_PRESERVE_MERGES)
1180 die("cannot combine '--signoff' with "
1181 "'--preserve-merges'");
1210 - strbuf_addstr(&options.git_am_opt, " --signoff");
1182 + argv_array_push(&options.git_am_opts, "--signoff");
1183 options.flags |= REBASE_FORCE;
1184 }
1185