Skip to content

Commit 4da373c

Browse files
phillipwoodgitster
authored andcommitted
sequencer: fix quoting in write_author_script
Single quotes should be escaped as \' not \\'. The bad quoting breaks the interactive version of 'rebase --root' (which is used when there is no '--onto' even if the user does not specify --interactive) for authors that contain "'" as sq_dequote() called read_author_ident() errors out on the bad quoting. For other interactive rebases this only affects external scripts that read the author script and users whose git is upgraded from the shell version of rebase -i while rebase was stopped when the author contains "'". This is because the parsing in read_env_script() expected the broken quoting. This patch includes code to gracefully handle the broken quoting when git has been upgraded while rebase was stopped. It does this by recording a version number (currently 1) in $GIT_DIR/rebase-merge/interactive to indicate that the author-script was created with correct quoting. Previously this file was always empty. The fallback path also fixes any missing "'" at the end of the GIT_AUTHOR_DATE line. The fallback code has been manually tested by reverting the quoting fixes in write_author_script() with and without reverting the previous fix for the missing "'" at the end of the GIT_AUTHOR_DATE line and running t3404-rebase-interactive.sh. Ideally rebase and am would share the same code for reading and writing the author script, but this commit just fixes the immediate bug. Helped-by: Johannes Schindelin <[email protected]> Signed-off-by: Phillip Wood <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 0bebe47 commit 4da373c

File tree

4 files changed

+97
-15
lines changed

4 files changed

+97
-15
lines changed

git-rebase--interactive.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -880,7 +880,7 @@ init_basic_state () {
880880
mkdir -p "$state_dir" || die "$(eval_gettext "Could not create temporary \$state_dir")"
881881
rm -f "$(git rev-parse --git-path REBASE_HEAD)"
882882

883-
: > "$state_dir"/interactive || die "$(gettext "Could not mark as interactive")"
883+
echo 1 > "$state_dir"/interactive || die "$(gettext "Could not mark as interactive")"
884884
write_basic_state
885885
}
886886

sequencer.c

Lines changed: 79 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ static GIT_PATH_FUNC(git_path_head_file, "sequencer/head")
4444
static GIT_PATH_FUNC(git_path_abort_safety_file, "sequencer/abort-safety")
4545

4646
static GIT_PATH_FUNC(rebase_path, "rebase-merge")
47+
/*
48+
* This file indicates that an interactive rebase is in progress, if it contians
49+
* an integer then that is a version indicator for the contents of files in
50+
* .git/rebase-merge
51+
*/
52+
static GIT_PATH_FUNC(rebase_path_interactive, "rebase-merge/interactive")
4753
/*
4854
* The file containing rebase commands, comments, and empty lines.
4955
* This file is created by "git rebase -i" then edited by the user. As
@@ -636,42 +642,79 @@ static int write_author_script(const char *message)
636642
else if (*message != '\'')
637643
strbuf_addch(&buf, *(message++));
638644
else
639-
strbuf_addf(&buf, "'\\\\%c'", *(message++));
645+
strbuf_addf(&buf, "'\\%c'", *(message++));
640646
strbuf_addstr(&buf, "'\nGIT_AUTHOR_EMAIL='");
641647
while (*message && *message != '\n' && *message != '\r')
642648
if (skip_prefix(message, "> ", &message))
643649
break;
644650
else if (*message != '\'')
645651
strbuf_addch(&buf, *(message++));
646652
else
647-
strbuf_addf(&buf, "'\\\\%c'", *(message++));
653+
strbuf_addf(&buf, "'\\%c'", *(message++));
648654
strbuf_addstr(&buf, "'\nGIT_AUTHOR_DATE='@");
649655
while (*message && *message != '\n' && *message != '\r')
650656
if (*message != '\'')
651657
strbuf_addch(&buf, *(message++));
652658
else
653-
strbuf_addf(&buf, "'\\\\%c'", *(message++));
659+
strbuf_addf(&buf, "'\\%c'", *(message++));
654660
strbuf_addch(&buf, '\'');
655661
res = write_message(buf.buf, buf.len, rebase_path_author_script(), 1);
656662
strbuf_release(&buf);
657663
return res;
658664
}
659665

666+
/*
667+
* write_author_script() used to fail to terminate the GIT_AUTHOR_DATE line with
668+
* a "'" and also escaped "'" incorrectly as "'\\\\''" rather than "'\\''". Fix
669+
* these problems before dequoting in when git was upgraded while rebase was
670+
* stopped.
671+
*/
672+
static int fix_bad_author_script(struct strbuf *script)
673+
{
674+
const char *next;
675+
size_t off = 0;
676+
677+
while ((next = strstr(script->buf + off, "'\\\\''"))) {
678+
off = next - script->buf + 4;
679+
strbuf_splice(script, next - script->buf, 5,"'\\''", 4);
680+
}
681+
682+
if ((next = strstr(script->buf, "\nGIT_AUTHOR_DATE='")) &&
683+
(next = strchr(++next, '\n')) &&
684+
++next - script->buf == script->len) {
685+
if (script->buf[script->len - 2] != '\'')
686+
strbuf_insert(script, script->len - 1, "'", 1);
687+
} else {
688+
return error(_("unable to parse '%s'"),
689+
rebase_path_author_script());
690+
}
691+
692+
return 0;
693+
}
694+
660695
/*
661696
* Read a list of environment variable assignments (such as the author-script
662697
* file) into an environment block. Returns -1 on error, 0 otherwise.
663698
*/
664-
static int read_env_script(struct argv_array *env)
699+
static int read_env_script(struct replay_opts *opts, struct argv_array *env)
665700
{
666701
struct strbuf script = STRBUF_INIT;
667702
int i, count = 0;
668-
char *p, *p2;
703+
const char *p2;
704+
char *p;
669705

670-
if (strbuf_read_file(&script, rebase_path_author_script(), 256) <= 0)
706+
if (strbuf_read_file(&script, rebase_path_author_script(), 256) <= 0) {
707+
strbuf_release(&script);
671708
return -1;
709+
}
710+
711+
if (!opts->version && fix_bad_author_script(&script)) {
712+
strbuf_release(&script);
713+
return -1;
714+
}
672715

673716
for (p = script.buf; *p; p++)
674-
if (skip_prefix(p, "'\\\\''", (const char **)&p2))
717+
if (skip_prefix(p, "'\\''", &p2))
675718
strbuf_splice(&script, p - script.buf, p2 - p, "'", 1);
676719
else if (*p == '\'')
677720
strbuf_splice(&script, p-- - script.buf, 1, "", 0);
@@ -701,7 +744,7 @@ static char *get_author(const char *message)
701744
}
702745

703746
/* Read author-script and return an ident line (author <email> timestamp) */
704-
static int read_author_ident(char **author)
747+
static int read_author_ident(struct replay_opts *opts, char **author)
705748
{
706749
const char *keys[] = {
707750
"GIT_AUTHOR_NAME=", "GIT_AUTHOR_EMAIL=", "GIT_AUTHOR_DATE="
@@ -717,6 +760,11 @@ static int read_author_ident(char **author)
717760
return -1;
718761
}
719762

763+
if (!opts->version && fix_bad_author_script(&buf)) {
764+
strbuf_release(&buf);
765+
return -1;
766+
}
767+
720768
for (in = buf.buf; i < 3 && in - buf.buf < buf.len; i++) {
721769
if (!skip_prefix(in, keys[i], (const char **)&in)) {
722770
strbuf_release(&buf);
@@ -801,7 +849,7 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
801849
struct object_id root_commit, *cache_tree_oid;
802850
int res = 0;
803851

804-
if (is_rebase_i(opts) && read_author_ident(&author))
852+
if (is_rebase_i(opts) && read_author_ident(opts, &author))
805853
return -1;
806854

807855
if (!defmsg)
@@ -839,7 +887,7 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
839887
cmd.err = -1;
840888
}
841889

842-
if (read_env_script(&cmd.env_array)) {
890+
if (read_env_script(opts, &cmd.env_array)) {
843891
const char *gpg_opt = gpg_sign_opt_quoted(opts);
844892

845893
return error(_(staged_changes_advice),
@@ -2238,6 +2286,27 @@ static int read_populate_opts(struct replay_opts *opts)
22382286
if (is_rebase_i(opts)) {
22392287
struct strbuf buf = STRBUF_INIT;
22402288

2289+
if (read_oneliner(&buf, rebase_path_interactive(), 0)) {
2290+
if (buf.len) {
2291+
char *end;
2292+
long version = strtol(buf.buf, &end, 10);
2293+
if (version < 1 ||version > INT_MAX ||
2294+
*end != '\0') {
2295+
strbuf_release(&buf);
2296+
return error(_("unable to parse '%s'"),
2297+
rebase_path_interactive());
2298+
}
2299+
opts->version = (int)version;
2300+
} else {
2301+
opts->version = 0;
2302+
}
2303+
strbuf_reset(&buf);
2304+
} else {
2305+
strbuf_release(&buf);
2306+
return error(_("unable to read '%s'"),
2307+
rebase_path_interactive());
2308+
}
2309+
22412310
if (read_oneliner(&buf, rebase_path_gpg_sign_opt(), 1)) {
22422311
if (!starts_with(buf.buf, "-S"))
22432312
strbuf_reset(&buf);

sequencer.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ struct replay_opts {
3434
int keep_redundant_commits;
3535
int verbose;
3636

37+
int version;
3738
int mainline;
3839

3940
char *gpg_sign;

t/t3404-rebase-interactive.sh

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ test_expect_success 'rebase --keep-empty' '
7575
test_line_count = 6 actual
7676
'
7777

78+
SQ="'"
7879
test_expect_success 'rebase -i with the exec command' '
7980
git checkout master &&
8081
(
@@ -1361,7 +1362,6 @@ test_expect_success 'editor saves as CR/LF' '
13611362
)
13621363
'
13631364

1364-
SQ="'"
13651365
test_expect_success 'rebase -i --gpg-sign=<key-id>' '
13661366
test_when_finished "test_might_fail git rebase --abort" &&
13671367
set_fake_editor &&
@@ -1382,9 +1382,21 @@ test_expect_success 'rebase -i --gpg-sign=<key-id> overrides commit.gpgSign' '
13821382
test_expect_success 'valid author header after --root swap' '
13831383
rebase_setup_and_clean author-header no-conflict-branch &&
13841384
set_fake_editor &&
1385-
FAKE_LINES="2 1" git rebase -i --root &&
1386-
git cat-file commit HEAD^ >out &&
1387-
grep "^author ..*> [0-9][0-9]* [-+][0-9][0-9][0-9][0-9]$" out
1385+
git commit --amend --author="Au ${SQ}thor <[email protected]>" --no-edit &&
1386+
git cat-file commit HEAD | grep ^author >expected &&
1387+
FAKE_LINES="5 1" git rebase -i --root &&
1388+
git cat-file commit HEAD^ | grep ^author >actual &&
1389+
test_cmp expected actual
1390+
'
1391+
1392+
test_expect_success 'valid author header when author contains single quote' '
1393+
rebase_setup_and_clean author-header no-conflict-branch &&
1394+
set_fake_editor &&
1395+
git commit --amend --author="Au ${SQ}thor <[email protected]>" --no-edit &&
1396+
git cat-file commit HEAD | grep ^author >expected &&
1397+
FAKE_LINES="1 5" git rebase -i --root &&
1398+
git cat-file commit HEAD | grep ^author >actual &&
1399+
test_cmp expected actual
13881400
'
13891401

13901402
test_done

0 commit comments

Comments
 (0)