Skip to content

Commit fd2e861

Browse files
committed
Merge branch 'pw/rebase-i-author-script-fix' into pu
Recent "git rebase -i" update started to write bogusly formatted author-script, with a matching broken reading code. These are being fixed. Undecided. Is it the list consensus to favor this "with extra code, read the script written by bad writer" approach? * pw/rebase-i-author-script-fix: sequencer: fix quoting in write_author_script sequencer: handle errors in read_author_ident()
2 parents 8332dc3 + 4da373c commit fd2e861

File tree

4 files changed

+125
-38
lines changed

4 files changed

+125
-38
lines changed

git-rebase--preserve-merges.sh

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

856-
: > "$state_dir"/interactive || die "$(gettext "Could not mark as interactive")"
856+
echo 1 > "$state_dir"/interactive || die "$(gettext "Could not mark as interactive")"
857857
write_basic_state
858858
}
859859

sequencer.c

Lines changed: 106 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@ static GIT_PATH_FUNC(git_path_head_file, "sequencer/head")
4848
static GIT_PATH_FUNC(git_path_abort_safety_file, "sequencer/abort-safety")
4949

5050
static GIT_PATH_FUNC(rebase_path, "rebase-merge")
51+
/*
52+
* This file indicates that an interactive rebase is in progress, if it contians
53+
* an integer then that is a version indicator for the contents of files in
54+
* .git/rebase-merge
55+
*/
56+
static GIT_PATH_FUNC(rebase_path_interactive, "rebase-merge/interactive")
5157
/*
5258
* The file containing rebase commands, comments, and empty lines.
5359
* This file is created by "git rebase -i" then edited by the user. As
@@ -645,42 +651,79 @@ static int write_author_script(const char *message)
645651
else if (*message != '\'')
646652
strbuf_addch(&buf, *(message++));
647653
else
648-
strbuf_addf(&buf, "'\\\\%c'", *(message++));
654+
strbuf_addf(&buf, "'\\%c'", *(message++));
649655
strbuf_addstr(&buf, "'\nGIT_AUTHOR_EMAIL='");
650656
while (*message && *message != '\n' && *message != '\r')
651657
if (skip_prefix(message, "> ", &message))
652658
break;
653659
else if (*message != '\'')
654660
strbuf_addch(&buf, *(message++));
655661
else
656-
strbuf_addf(&buf, "'\\\\%c'", *(message++));
662+
strbuf_addf(&buf, "'\\%c'", *(message++));
657663
strbuf_addstr(&buf, "'\nGIT_AUTHOR_DATE='@");
658664
while (*message && *message != '\n' && *message != '\r')
659665
if (*message != '\'')
660666
strbuf_addch(&buf, *(message++));
661667
else
662-
strbuf_addf(&buf, "'\\\\%c'", *(message++));
668+
strbuf_addf(&buf, "'\\%c'", *(message++));
663669
strbuf_addch(&buf, '\'');
664670
res = write_message(buf.buf, buf.len, rebase_path_author_script(), 1);
665671
strbuf_release(&buf);
666672
return res;
667673
}
668674

675+
/*
676+
* write_author_script() used to fail to terminate the GIT_AUTHOR_DATE line with
677+
* a "'" and also escaped "'" incorrectly as "'\\\\''" rather than "'\\''". Fix
678+
* these problems before dequoting in when git was upgraded while rebase was
679+
* stopped.
680+
*/
681+
static int fix_bad_author_script(struct strbuf *script)
682+
{
683+
const char *next;
684+
size_t off = 0;
685+
686+
while ((next = strstr(script->buf + off, "'\\\\''"))) {
687+
off = next - script->buf + 4;
688+
strbuf_splice(script, next - script->buf, 5,"'\\''", 4);
689+
}
690+
691+
if ((next = strstr(script->buf, "\nGIT_AUTHOR_DATE='")) &&
692+
(next = strchr(++next, '\n')) &&
693+
++next - script->buf == script->len) {
694+
if (script->buf[script->len - 2] != '\'')
695+
strbuf_insert(script, script->len - 1, "'", 1);
696+
} else {
697+
return error(_("unable to parse '%s'"),
698+
rebase_path_author_script());
699+
}
700+
701+
return 0;
702+
}
703+
669704
/*
670705
* Read a list of environment variable assignments (such as the author-script
671706
* file) into an environment block. Returns -1 on error, 0 otherwise.
672707
*/
673-
static int read_env_script(struct argv_array *env)
708+
static int read_env_script(struct replay_opts *opts, struct argv_array *env)
674709
{
675710
struct strbuf script = STRBUF_INIT;
676711
int i, count = 0;
677-
char *p, *p2;
712+
const char *p2;
713+
char *p;
678714

679-
if (strbuf_read_file(&script, rebase_path_author_script(), 256) <= 0)
715+
if (strbuf_read_file(&script, rebase_path_author_script(), 256) <= 0) {
716+
strbuf_release(&script);
680717
return -1;
718+
}
719+
720+
if (!opts->version && fix_bad_author_script(&script)) {
721+
strbuf_release(&script);
722+
return -1;
723+
}
681724

682725
for (p = script.buf; *p; p++)
683-
if (skip_prefix(p, "'\\\\''", (const char **)&p2))
726+
if (skip_prefix(p, "'\\''", &p2))
684727
strbuf_splice(&script, p - script.buf, p2 - p, "'", 1);
685728
else if (*p == '\'')
686729
strbuf_splice(&script, p-- - script.buf, 1, "", 0);
@@ -710,57 +753,64 @@ static char *get_author(const char *message)
710753
}
711754

712755
/* Read author-script and return an ident line (author <email> timestamp) */
713-
static const char *read_author_ident(struct strbuf *buf)
756+
static int read_author_ident(struct replay_opts *opts, char **author)
714757
{
715758
const char *keys[] = {
716759
"GIT_AUTHOR_NAME=", "GIT_AUTHOR_EMAIL=", "GIT_AUTHOR_DATE="
717760
};
761+
struct strbuf buf = STRBUF_INIT;
718762
struct strbuf out = STRBUF_INIT;
719763
char *in, *eol;
720764
const char *val[3];
721765
int i = 0;
722766

723-
if (strbuf_read_file(buf, rebase_path_author_script(), 256) <= 0)
724-
return NULL;
767+
if (strbuf_read_file(&buf, rebase_path_author_script(), 256) <= 0) {
768+
strbuf_release(&buf);
769+
return -1;
770+
}
771+
772+
if (!opts->version && fix_bad_author_script(&buf)) {
773+
strbuf_release(&buf);
774+
return -1;
775+
}
725776

726-
/* dequote values and construct ident line in-place */
727-
for (in = buf->buf; i < 3 && in - buf->buf < buf->len; i++) {
777+
for (in = buf.buf; i < 3 && in - buf.buf < buf.len; i++) {
728778
if (!skip_prefix(in, keys[i], (const char **)&in)) {
729-
warning(_("could not parse '%s' (looking for '%s'"),
730-
rebase_path_author_script(), keys[i]);
731-
return NULL;
779+
strbuf_release(&buf);
780+
return error(_("could not parse '%s' (looking for '%s')"),
781+
rebase_path_author_script(), keys[i]);
732782
}
733-
734783
eol = strchrnul(in, '\n');
735784
*eol = '\0';
736785
if (!sq_dequote(in)) {
737-
warning(_("bad quoting on %s value in '%s'"),
738-
keys[i], rebase_path_author_script());
739-
return NULL;
786+
strbuf_release(&buf);
787+
return error(_("bad quoting on %s value in '%s'"),
788+
keys[i], rebase_path_author_script());
740789
}
741790
val[i] = in;
742791
in = eol + 1;
743792
}
744793

745794
if (i < 3) {
746-
warning(_("could not parse '%s' (looking for '%s')"),
747-
rebase_path_author_script(), keys[i]);
748-
return NULL;
795+
strbuf_release(&buf);
796+
return error(_("could not parse '%s' (looking for '%s')"),
797+
rebase_path_author_script(), keys[i]);
749798
}
750799

751800
/* validate date since fmt_ident() will die() on bad value */
752801
if (parse_date(val[2], &out)){
753-
warning(_("invalid date format '%s' in '%s'"),
802+
error(_("invalid date format '%s' in '%s'"),
754803
val[2], rebase_path_author_script());
755804
strbuf_release(&out);
756-
return NULL;
805+
strbuf_release(&buf);
806+
return -1;
757807
}
758808

759809
strbuf_reset(&out);
760810
strbuf_addstr(&out, fmt_ident(val[0], val[1], val[2], 0));
761-
strbuf_swap(buf, &out);
762-
strbuf_release(&out);
763-
return buf->buf;
811+
*author = strbuf_detach(&out, NULL);
812+
strbuf_release(&buf);
813+
return 0;
764814
}
765815

766816
static const char staged_changes_advice[] =
@@ -820,12 +870,14 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
820870
const char *value;
821871

822872
if ((flags & CREATE_ROOT_COMMIT) && !(flags & AMEND_MSG)) {
823-
struct strbuf msg = STRBUF_INIT, script = STRBUF_INIT;
824-
const char *author = is_rebase_i(opts) ?
825-
read_author_ident(&script) : NULL;
873+
struct strbuf msg = STRBUF_INIT;
874+
char *author = NULL;
826875
struct object_id root_commit, *cache_tree_oid;
827876
int res = 0;
828877

878+
if (is_rebase_i(opts) && read_author_ident(opts, &author))
879+
return -1;
880+
829881
if (!defmsg)
830882
BUG("root commit without message");
831883

@@ -843,7 +895,7 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
843895
opts->gpg_sign);
844896

845897
strbuf_release(&msg);
846-
strbuf_release(&script);
898+
free(author);
847899
if (!res) {
848900
update_ref(NULL, "CHERRY_PICK_HEAD", &root_commit, NULL,
849901
REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR);
@@ -855,7 +907,7 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
855907

856908
cmd.git_cmd = 1;
857909

858-
if (is_rebase_i(opts) && read_env_script(&cmd.env_array)) {
910+
if (is_rebase_i(opts) && read_env_script(opts, &cmd.env_array)) {
859911
const char *gpg_opt = gpg_sign_opt_quoted(opts);
860912

861913
return error(_(staged_changes_advice),
@@ -2251,6 +2303,27 @@ static int read_populate_opts(struct replay_opts *opts)
22512303
if (is_rebase_i(opts)) {
22522304
struct strbuf buf = STRBUF_INIT;
22532305

2306+
if (read_oneliner(&buf, rebase_path_interactive(), 0)) {
2307+
if (buf.len) {
2308+
char *end;
2309+
long version = strtol(buf.buf, &end, 10);
2310+
if (version < 1 ||version > INT_MAX ||
2311+
*end != '\0') {
2312+
strbuf_release(&buf);
2313+
return error(_("unable to parse '%s'"),
2314+
rebase_path_interactive());
2315+
}
2316+
opts->version = (int)version;
2317+
} else {
2318+
opts->version = 0;
2319+
}
2320+
strbuf_reset(&buf);
2321+
} else {
2322+
strbuf_release(&buf);
2323+
return error(_("unable to read '%s'"),
2324+
rebase_path_interactive());
2325+
}
2326+
22542327
if (read_oneliner(&buf, rebase_path_gpg_sign_opt(), 1)) {
22552328
if (!starts_with(buf.buf, "-S"))
22562329
strbuf_reset(&buf);
@@ -3103,7 +3176,7 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,
31033176
/* Octopus merge */
31043177
struct child_process cmd = CHILD_PROCESS_INIT;
31053178

3106-
if (read_env_script(&cmd.env_array)) {
3179+
if (read_env_script(opts, &cmd.env_array)) {
31073180
const char *gpg_opt = gpg_sign_opt_quoted(opts);
31083181

31093182
ret = error(_(staged_changes_advice), gpg_opt, gpg_opt);

sequencer.h

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

38+
int version;
3839
int mainline;
3940

4041
char *gpg_sign;

t/t3404-rebase-interactive.sh

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

78+
SQ="'"
79+
7880
cat > expect <<EOF
7981
Nothing to do
8082
EOF
@@ -1416,7 +1418,6 @@ test_expect_success 'editor saves as CR/LF' '
14161418
)
14171419
'
14181420

1419-
SQ="'"
14201421
test_expect_success 'rebase -i --gpg-sign=<key-id>' '
14211422
test_when_finished "test_might_fail git rebase --abort" &&
14221423
set_fake_editor &&
@@ -1437,9 +1438,21 @@ test_expect_success 'rebase -i --gpg-sign=<key-id> overrides commit.gpgSign' '
14371438
test_expect_success 'valid author header after --root swap' '
14381439
rebase_setup_and_clean author-header no-conflict-branch &&
14391440
set_fake_editor &&
1440-
FAKE_LINES="2 1" git rebase -i --root &&
1441-
git cat-file commit HEAD^ >out &&
1442-
grep "^author ..*> [0-9][0-9]* [-+][0-9][0-9][0-9][0-9]$" out
1441+
git commit --amend --author="Au ${SQ}thor <[email protected]>" --no-edit &&
1442+
git cat-file commit HEAD | grep ^author >expected &&
1443+
FAKE_LINES="5 1" git rebase -i --root &&
1444+
git cat-file commit HEAD^ | grep ^author >actual &&
1445+
test_cmp expected actual
1446+
'
1447+
1448+
test_expect_success 'valid author header when author contains single quote' '
1449+
rebase_setup_and_clean author-header no-conflict-branch &&
1450+
set_fake_editor &&
1451+
git commit --amend --author="Au ${SQ}thor <[email protected]>" --no-edit &&
1452+
git cat-file commit HEAD | grep ^author >expected &&
1453+
FAKE_LINES="1 5" git rebase -i --root &&
1454+
git cat-file commit HEAD | grep ^author >actual &&
1455+
test_cmp expected actual
14431456
'
14441457

14451458
test_done

0 commit comments

Comments
 (0)