rebase -i: reread the todo list if `exec` touched it

In the scripted version of the interactive rebase, there was no internal representation of the todo list; it was re-read before every command. That allowed the hack that an `exec` command could append (or even completely rewrite) the todo list. This hack was broken by the partial conversion of the interactive rebase to C, and this patch reinstates it. We also add a small test to verify that this fix does not regress in the future. Signed-off-by: Stephen Hicks <sdh@google.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Stephen Hicks committed Apr 26, 2017 at 21:17 UTC 54fd3243daec7d59394f25846450d83c0012e57c
2 files changed +36
sequencer.c
+22
@@ -1200,6 +1200,7 @@ struct todo_list {
1200 struct todo_item *items;
1201 int nr, alloc, current;
1202 int done_nr, total_nr;
1203 + struct stat_data stat;
1204 };
1205
1206 #define TODO_LIST_INIT { STRBUF_INIT }
@@ -1330,6 +1331,7 @@ static int count_commands(struct todo_list *todo_list)
1331 static int read_populate_todo(struct todo_list *todo_list,
1332 struct replay_opts *opts)
1333 {
1334 + struct stat st;
1335 const char *todo_file = get_todo_path(opts);
1336 int fd, res;
1337
@@ -1343,6 +1345,11 @@ static int read_populate_todo(struct todo_list *todo_list,
1345 }
1346 close(fd);
1347
1348 + res = stat(todo_file, &st);
1349 + if (res)
1350 + return error(_("could not stat '%s'"), todo_file);
1351 + fill_stat_data(&todo_list->stat, &st);
1352 +
1353 res = parse_insn_buffer(todo_list->buf.buf, todo_list);
1354 if (res) {
1355 if (is_rebase_i(opts))
@@ -2028,10 +2035,25 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts)
2035 } else if (item->command == TODO_EXEC) {
2036 char *end_of_arg = (char *)(item->arg + item->arg_len);
2037 int saved = *end_of_arg;
2038 + struct stat st;
2039
2040 *end_of_arg = '\0';
2041 res = do_exec(item->arg);
2042 *end_of_arg = saved;
2043 +
2044 + /* Reread the todo file if it has changed. */
2045 + if (res)
2046 + ; /* fall through */
2047 + else if (stat(get_todo_path(opts), &st))
2048 + res = error_errno(_("could not stat '%s'"),
2049 + get_todo_path(opts));
2050 + else if (match_stat_data(&todo_list->stat, &st)) {
2051 + todo_list_release(todo_list);
2052 + if (read_populate_todo(todo_list, opts))
2053 + res = -1; /* message was printed */
2054 + /* `current` will be incremented below */
2055 + todo_list->current = -1;
2056 + }
2057 } else if (!is_noop(item->command))
2058 return error(_("unknown command %d"), item->command);
2059
t/t3429-rebase-edit-todo.sh new
+14
@@ -0,0 +1,14 @@
1 +#!/bin/sh
2 +
3 +test_description='rebase should reread the todo file if an exec modifies it'
4 +
5 +. ./test-lib.sh
6 +
7 +test_expect_success 'rebase exec modifies rebase-todo' '
8 + test_commit initial &&
9 + todo=.git/rebase-merge/git-rebase-todo &&
10 + git rebase HEAD -x "echo exec touch F >>$todo" &&
11 + test -e F
12 +'
13 +
14 +test_done