Message ID | 9a90212ef284510fa691b8eebd6bdd61098282fd.1680708043.git.phillip.wood@dunelm.org.uk (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | rebase: cleanup merge strategy option handling | expand |
On Wed, Apr 5, 2023 at 8:22 AM Phillip Wood <phillip.wood123@gmail.com> wrote: > > From: Phillip Wood <phillip.wood@dunelm.org.uk> > > To store the strategy options rebase prepends " --" to each one and > writes them to a file. To load them it reads the file and passes the > contents to split_cmdline(). This roughly mimics the behavior of the > scripted rebase but has a couple of limitations, (1) options containing > whitespace are not properly preserved (this is true of the scripted > rebase as well) and (2) options containing '"' or '\' are incorrectly > parsed and may cause the parser to return an error. > > Fix these limitations by quoting each option when they are stored so > that they can be parsed correctly. Now that "--preserve-merges" no > longer exist this change also stops prepending "--" to the options when > they are stored as that was an artifact of the scripted rebase. > > These changes are backwards compatible so the files written by an older > version of git can be still be read. They are also forwards compatible, s/be still be/still be/ Sorry for not noticing this last time. > the file can still be parsed by recent versions of git as they treat the > "--" prefix as optional. > > Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk> > --- > alias.c | 18 +++++++++++++++++ > alias.h | 3 +++ > sequencer.c | 11 ++++++----- > t/t3418-rebase-continue.sh | 36 ++++++++++++++++++++++------------ > t/t3436-rebase-more-options.sh | 24 ----------------------- > 5 files changed, 50 insertions(+), 42 deletions(-) > > diff --git a/alias.c b/alias.c > index e814948ced..54a1a23d2c 100644 > --- a/alias.c > +++ b/alias.c > @@ -3,6 +3,7 @@ > #include "alloc.h" > #include "config.h" > #include "gettext.h" > +#include "strbuf.h" > #include "string-list.h" > > struct config_alias_data { > @@ -46,6 +47,23 @@ void list_aliases(struct string_list *list) > read_early_config(config_alias_cb, &data); > } > > +void quote_cmdline(struct strbuf *buf, const char **argv) > +{ > + for (const char **argp = argv; *argp; argp++) { > + if (argp != argv) > + strbuf_addch(buf, ' '); > + strbuf_addch(buf, '"'); > + for (const char *p = *argp; *p; p++) { > + const char c = *p; > + > + if (c == '"' || c =='\\') > + strbuf_addch(buf, '\\'); > + strbuf_addch(buf, c); > + } > + strbuf_addch(buf, '"'); > + } > +} > + > #define SPLIT_CMDLINE_BAD_ENDING 1 > #define SPLIT_CMDLINE_UNCLOSED_QUOTE 2 > #define SPLIT_CMDLINE_ARGC_OVERFLOW 3 > diff --git a/alias.h b/alias.h > index aef4843bb7..43db736484 100644 > --- a/alias.h > +++ b/alias.h > @@ -1,9 +1,12 @@ > #ifndef ALIAS_H > #define ALIAS_H > > +struct strbuf; > struct string_list; > > char *alias_lookup(const char *alias); > +/* Quote argv so buf can be parsed by split_cmdline() */ > +void quote_cmdline(struct strbuf *buf, const char **argv); > int split_cmdline(char *cmdline, const char ***argv); > /* Takes a negative value returned by split_cmdline */ > const char *split_cmdline_strerror(int cmdline_errno); > diff --git a/sequencer.c b/sequencer.c > index 045d549042..9907100c8a 100644 > --- a/sequencer.c > +++ b/sequencer.c > @@ -2925,7 +2925,7 @@ static void parse_strategy_opts(struct replay_opts *opts, char *raw_opts) > > count = split_cmdline(strategy_opts_string, &argv); > if (count < 0) > - die(_("could not split '%s': %s"), strategy_opts_string, > + BUG("could not split '%s': %s", strategy_opts_string, > split_cmdline_strerror(count)); > for (i = 0; i < count; i++) { > const char *arg = argv[i]; > @@ -3048,12 +3048,13 @@ static int read_populate_opts(struct replay_opts *opts) > > static void write_strategy_opts(struct replay_opts *opts) > { > - int i; > struct strbuf buf = STRBUF_INIT; > > - for (i = 0; i < opts->xopts.nr; ++i) > - strbuf_addf(&buf, " --%s", opts->xopts.v[i]); > - > + /* > + * Quote strategy options so that they can be read correctly > + * by split_cmdline(). > + */ > + quote_cmdline(&buf, opts->xopts.v); > write_file(rebase_path_strategy_opts(), "%s\n", buf.buf); > strbuf_release(&buf); > } > diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh > index 130e2f9b55..42c3954125 100755 > --- a/t/t3418-rebase-continue.sh > +++ b/t/t3418-rebase-continue.sh > @@ -62,29 +62,39 @@ test_expect_success 'rebase --continue remembers merge strategy and options' ' > rm -fr .git/rebase-* && > git reset --hard commit-new-file-F2-on-topic-branch && > test_commit "commit-new-file-F3-on-topic-branch" F3 32 && > - test_when_finished "rm -fr test-bin funny.was.run" && > + test_when_finished "rm -fr test-bin" && > mkdir test-bin && > - cat >test-bin/git-merge-funny <<-EOF && > - #!$SHELL_PATH > - case "\$1" in --opt) ;; *) exit 2 ;; esac > - shift && > - >funny.was.run && > - exec git merge-recursive "\$@" > - EOF > - chmod +x test-bin/git-merge-funny && > + > + write_script test-bin/git-merge-funny <<-\EOF && > + printf "[%s]\n" $# "$1" "$2" "$3" "$5" >actual > + shift 3 && > + exec git merge-recursive "$@" > + EOF > + > + cat >expect <<-\EOF && > + [7] > + [--option=arg with space] > + [--op"tion\] > + [--new > + line ] > + [--] > + EOF > + > + rm -f actual && > ( > PATH=./test-bin:$PATH && > - test_must_fail git rebase -s funny -Xopt main topic > + test_must_fail git rebase -s funny -X"option=arg with space" \ > + -Xop\"tion\\ -X"new${LF}line " main topic > ) && > - test -f funny.was.run && > - rm funny.was.run && > + test_cmp expect actual && > + rm actual && > echo "Resolved" >F2 && > git add F2 && > ( > PATH=./test-bin:$PATH && > git rebase --continue > ) && > - test -f funny.was.run > + test_cmp expect actual > ' > > test_expect_success 'rebase -i --continue handles merge strategy and options' ' > diff --git a/t/t3436-rebase-more-options.sh b/t/t3436-rebase-more-options.sh > index 3adf42f47d..94671d3c46 100755 > --- a/t/t3436-rebase-more-options.sh > +++ b/t/t3436-rebase-more-options.sh > @@ -40,30 +40,6 @@ test_expect_success 'setup' ' > EOF > ' > > -test_expect_success 'bad -X <strategy-option> arguments: unclosed quote' ' > - test_when_finished "test_might_fail git rebase --abort" && > - cat >expect <<-\EOF && > - fatal: could not split '\''--bad'\'': unclosed quote > - EOF > - GIT_SEQUENCE_EDITOR="echo break >" \ > - git rebase -i -X"bad argument\"" side main && > - test_expect_code 128 git rebase --continue >out 2>actual && > - test_must_be_empty out && > - test_cmp expect actual > -' > - > -test_expect_success 'bad -X <strategy-option> arguments: bad escape' ' > - test_when_finished "test_might_fail git rebase --abort" && > - cat >expect <<-\EOF && > - fatal: could not split '\''--bad'\'': cmdline ends with \ > - EOF > - GIT_SEQUENCE_EDITOR="echo break >" \ > - git rebase -i -X"bad escape \\" side main && > - test_expect_code 128 git rebase --continue >out 2>actual && > - test_must_be_empty out && > - test_cmp expect actual > -' > - > test_expect_success '--ignore-whitespace works with apply backend' ' > test_must_fail git rebase --apply main side && > git rebase --abort && > -- > 2.40.0.670.g64ef305212.dirty Looks good.
diff --git a/alias.c b/alias.c index e814948ced..54a1a23d2c 100644 --- a/alias.c +++ b/alias.c @@ -3,6 +3,7 @@ #include "alloc.h" #include "config.h" #include "gettext.h" +#include "strbuf.h" #include "string-list.h" struct config_alias_data { @@ -46,6 +47,23 @@ void list_aliases(struct string_list *list) read_early_config(config_alias_cb, &data); } +void quote_cmdline(struct strbuf *buf, const char **argv) +{ + for (const char **argp = argv; *argp; argp++) { + if (argp != argv) + strbuf_addch(buf, ' '); + strbuf_addch(buf, '"'); + for (const char *p = *argp; *p; p++) { + const char c = *p; + + if (c == '"' || c =='\\') + strbuf_addch(buf, '\\'); + strbuf_addch(buf, c); + } + strbuf_addch(buf, '"'); + } +} + #define SPLIT_CMDLINE_BAD_ENDING 1 #define SPLIT_CMDLINE_UNCLOSED_QUOTE 2 #define SPLIT_CMDLINE_ARGC_OVERFLOW 3 diff --git a/alias.h b/alias.h index aef4843bb7..43db736484 100644 --- a/alias.h +++ b/alias.h @@ -1,9 +1,12 @@ #ifndef ALIAS_H #define ALIAS_H +struct strbuf; struct string_list; char *alias_lookup(const char *alias); +/* Quote argv so buf can be parsed by split_cmdline() */ +void quote_cmdline(struct strbuf *buf, const char **argv); int split_cmdline(char *cmdline, const char ***argv); /* Takes a negative value returned by split_cmdline */ const char *split_cmdline_strerror(int cmdline_errno); diff --git a/sequencer.c b/sequencer.c index 045d549042..9907100c8a 100644 --- a/sequencer.c +++ b/sequencer.c @@ -2925,7 +2925,7 @@ static void parse_strategy_opts(struct replay_opts *opts, char *raw_opts) count = split_cmdline(strategy_opts_string, &argv); if (count < 0) - die(_("could not split '%s': %s"), strategy_opts_string, + BUG("could not split '%s': %s", strategy_opts_string, split_cmdline_strerror(count)); for (i = 0; i < count; i++) { const char *arg = argv[i]; @@ -3048,12 +3048,13 @@ static int read_populate_opts(struct replay_opts *opts) static void write_strategy_opts(struct replay_opts *opts) { - int i; struct strbuf buf = STRBUF_INIT; - for (i = 0; i < opts->xopts.nr; ++i) - strbuf_addf(&buf, " --%s", opts->xopts.v[i]); - + /* + * Quote strategy options so that they can be read correctly + * by split_cmdline(). + */ + quote_cmdline(&buf, opts->xopts.v); write_file(rebase_path_strategy_opts(), "%s\n", buf.buf); strbuf_release(&buf); } diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh index 130e2f9b55..42c3954125 100755 --- a/t/t3418-rebase-continue.sh +++ b/t/t3418-rebase-continue.sh @@ -62,29 +62,39 @@ test_expect_success 'rebase --continue remembers merge strategy and options' ' rm -fr .git/rebase-* && git reset --hard commit-new-file-F2-on-topic-branch && test_commit "commit-new-file-F3-on-topic-branch" F3 32 && - test_when_finished "rm -fr test-bin funny.was.run" && + test_when_finished "rm -fr test-bin" && mkdir test-bin && - cat >test-bin/git-merge-funny <<-EOF && - #!$SHELL_PATH - case "\$1" in --opt) ;; *) exit 2 ;; esac - shift && - >funny.was.run && - exec git merge-recursive "\$@" - EOF - chmod +x test-bin/git-merge-funny && + + write_script test-bin/git-merge-funny <<-\EOF && + printf "[%s]\n" $# "$1" "$2" "$3" "$5" >actual + shift 3 && + exec git merge-recursive "$@" + EOF + + cat >expect <<-\EOF && + [7] + [--option=arg with space] + [--op"tion\] + [--new + line ] + [--] + EOF + + rm -f actual && ( PATH=./test-bin:$PATH && - test_must_fail git rebase -s funny -Xopt main topic + test_must_fail git rebase -s funny -X"option=arg with space" \ + -Xop\"tion\\ -X"new${LF}line " main topic ) && - test -f funny.was.run && - rm funny.was.run && + test_cmp expect actual && + rm actual && echo "Resolved" >F2 && git add F2 && ( PATH=./test-bin:$PATH && git rebase --continue ) && - test -f funny.was.run + test_cmp expect actual ' test_expect_success 'rebase -i --continue handles merge strategy and options' ' diff --git a/t/t3436-rebase-more-options.sh b/t/t3436-rebase-more-options.sh index 3adf42f47d..94671d3c46 100755 --- a/t/t3436-rebase-more-options.sh +++ b/t/t3436-rebase-more-options.sh @@ -40,30 +40,6 @@ test_expect_success 'setup' ' EOF ' -test_expect_success 'bad -X <strategy-option> arguments: unclosed quote' ' - test_when_finished "test_might_fail git rebase --abort" && - cat >expect <<-\EOF && - fatal: could not split '\''--bad'\'': unclosed quote - EOF - GIT_SEQUENCE_EDITOR="echo break >" \ - git rebase -i -X"bad argument\"" side main && - test_expect_code 128 git rebase --continue >out 2>actual && - test_must_be_empty out && - test_cmp expect actual -' - -test_expect_success 'bad -X <strategy-option> arguments: bad escape' ' - test_when_finished "test_might_fail git rebase --abort" && - cat >expect <<-\EOF && - fatal: could not split '\''--bad'\'': cmdline ends with \ - EOF - GIT_SEQUENCE_EDITOR="echo break >" \ - git rebase -i -X"bad escape \\" side main && - test_expect_code 128 git rebase --continue >out 2>actual && - test_must_be_empty out && - test_cmp expect actual -' - test_expect_success '--ignore-whitespace works with apply backend' ' test_must_fail git rebase --apply main side && git rebase --abort &&