rebase -i: Handle "combination of <n> commits" with GETTEXT_POISON

We previously relied on the localized versions of # This is a combination of <N> commits (which we write into the commit messages during fixup/squash chains) to contain <N> encoded in ASCII. This is not true in general, and certainly not true when compiled with GETTEXT_POISON=TryToKillMe, as demonstrated by the regression test we just introduced in t3418. So let's decouple keeping track of the count from the (localized) commit messages by introducing a new file called 'current-fixups' that keeps track of the current fixup/squash chain. This file contains a bit more than just the count (it contains a list of "fixup <commit>"/"squash <commit>" lines). This is done on purpose, as it will come in handy for a fix for the bug where `git rebase --skip` on a final fixup/squash will leave the commit message in limbo. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Johannes Schindelin committed Apr 27, 2018 at 22:48 UTC e12a7ef5972423c822834f9c514a54e32ea99e65
2 files changed +49 -35
sequencer.c
+44 -34
@@ -74,13 +74,6 @@ static GIT_PATH_FUNC(rebase_path_message, "rebase-merge/message")
74 * previous commit and from the first squash/fixup commit are written
75 * to it. The commit message for each subsequent squash/fixup commit
76 * is appended to the file as it is processed.
77 - *
78 - * The first line of the file is of the form
79 - * # This is a combination of $count commits.
80 - * where $count is the number of commits whose messages have been
81 - * written to the file so far (including the initial "pick" commit).
82 - * Each time that a commit message is processed, this line is read and
83 - * updated. It is deleted just before the combined commit is made.
77 */
78 static GIT_PATH_FUNC(rebase_path_squash_msg, "rebase-merge/message-squash")
79 /*
@@ -91,6 +84,11 @@ static GIT_PATH_FUNC(rebase_path_squash_msg, "rebase-merge/message-squash")
84 * commit without opening the editor.)
85 */
86 static GIT_PATH_FUNC(rebase_path_fixup_msg, "rebase-merge/message-fixup")
87 +/*
88 + * This file contains the list fixup/squash commands that have been
89 + * accumulated into message-fixup or message-squash so far.
90 + */
91 +static GIT_PATH_FUNC(rebase_path_current_fixups, "rebase-merge/current-fixups")
92 /*
93 * A script to set the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, and
94 * GIT_AUTHOR_DATE that will be used for the commit that is currently
@@ -252,6 +250,7 @@ int sequencer_remove_state(struct replay_opts *opts)
250 for (i = 0; i < opts->xopts_nr; i++)
251 free(opts->xopts[i]);
252 free(opts->xopts);
253 + strbuf_release(&opts->current_fixups);
254
255 strbuf_addstr(&dir, get_dir(opts));
256 remove_dir_recursively(&dir, 0);
@@ -1328,34 +1327,23 @@ static int update_squash_messages(enum todo_command command,
1327 struct commit *commit, struct replay_opts *opts)
1328 {
1329 struct strbuf buf = STRBUF_INIT;
1331 - int count, res;
1330 + int res;
1331 const char *message, *body;
1332
1334 - if (file_exists(rebase_path_squash_msg())) {
1333 + if (opts->current_fixup_count > 0) {
1334 struct strbuf header = STRBUF_INIT;
1336 - char *eol, *p;
1335 + char *eol;
1336
1338 - if (strbuf_read_file(&buf, rebase_path_squash_msg(), 2048) <= 0)
1337 + if (strbuf_read_file(&buf, rebase_path_squash_msg(), 9) <= 0)
1338 return error(_("could not read '%s'"),
1339 rebase_path_squash_msg());
1340
1342 - p = buf.buf + 1;
1343 - eol = strchrnul(buf.buf, '\n');
1344 - if (buf.buf[0] != comment_line_char ||
1345 - (p += strcspn(p, "0123456789\n")) == eol)
1346 - return error(_("unexpected 1st line of squash message:"
1347 - "\n\n\t%.*s"),
1348 - (int)(eol - buf.buf), buf.buf);
1349 - count = strtol(p, NULL, 10);
1350 -
1351 - if (count < 1)
1352 - return error(_("invalid 1st line of squash message:\n"
1353 - "\n\t%.*s"),
1354 - (int)(eol - buf.buf), buf.buf);
1341 + eol = buf.buf[0] != comment_line_char ?
1342 + buf.buf : strchrnul(buf.buf, '\n');
1343
1344 strbuf_addf(&header, "%c ", comment_line_char);
1357 - strbuf_addf(&header,
1358 - _("This is a combination of %d commits."), ++count);
1345 + strbuf_addf(&header, _("This is a combination of %d commits."),
1346 + opts->current_fixup_count + 2);
1347 strbuf_splice(&buf, 0, eol - buf.buf, header.buf, header.len);
1348 strbuf_release(&header);
1349 } else {
@@ -1378,10 +1366,8 @@ static int update_squash_messages(enum todo_command command,
1366 rebase_path_fixup_msg());
1367 }
1368
1381 - count = 2;
1369 strbuf_addf(&buf, "%c ", comment_line_char);
1383 - strbuf_addf(&buf, _("This is a combination of %d commits."),
1384 - count);
1370 + strbuf_addf(&buf, _("This is a combination of %d commits."), 2);
1371 strbuf_addf(&buf, "\n%c ", comment_line_char);
1372 strbuf_addstr(&buf, _("This is the 1st commit message:"));
1373 strbuf_addstr(&buf, "\n\n");
@@ -1398,13 +1384,14 @@ static int update_squash_messages(enum todo_command command,
1384 if (command == TODO_SQUASH) {
1385 unlink(rebase_path_fixup_msg());
1386 strbuf_addf(&buf, "\n%c ", comment_line_char);
1401 - strbuf_addf(&buf, _("This is the commit message #%d:"), count);
1387 + strbuf_addf(&buf, _("This is the commit message #%d:"),
1388 + ++opts->current_fixup_count);
1389 strbuf_addstr(&buf, "\n\n");
1390 strbuf_addstr(&buf, body);
1391 } else if (command == TODO_FIXUP) {
1392 strbuf_addf(&buf, "\n%c ", comment_line_char);
1393 strbuf_addf(&buf, _("The commit message #%d will be skipped:"),
1407 - count);
1394 + ++opts->current_fixup_count);
1395 strbuf_addstr(&buf, "\n\n");
1396 strbuf_add_commented_lines(&buf, body, strlen(body));
1397 } else
@@ -1413,6 +1400,17 @@ static int update_squash_messages(enum todo_command command,
1400
1401 res = write_message(buf.buf, buf.len, rebase_path_squash_msg(), 0);
1402 strbuf_release(&buf);
1403 +
1404 + if (!res) {
1405 + strbuf_addf(&opts->current_fixups, "%s%s %s",
1406 + opts->current_fixups.len ? "\n" : "",
1407 + command_to_string(command),
1408 + oid_to_hex(&commit->object.oid));
1409 + res = write_message(opts->current_fixups.buf,
1410 + opts->current_fixups.len,
1411 + rebase_path_current_fixups(), 0);
1412 + }
1413 +
1414 return res;
1415 }
1416
@@ -1675,6 +1673,9 @@ fast_forward_edit:
1673 if (!res && final_fixup) {
1674 unlink(rebase_path_fixup_msg());
1675 unlink(rebase_path_squash_msg());
1676 + unlink(rebase_path_current_fixups());
1677 + strbuf_reset(&opts->current_fixups);
1678 + opts->current_fixup_count = 0;
1679 }
1680
1681 leave:
@@ -2046,6 +2047,16 @@ static int read_populate_opts(struct replay_opts *opts)
2047 read_strategy_opts(opts, &buf);
2048 strbuf_release(&buf);
2049
2050 + if (read_oneliner(&opts->current_fixups,
2051 + rebase_path_current_fixups(), 1)) {
2052 + const char *p = opts->current_fixups.buf;
2053 + opts->current_fixup_count = 1;
2054 + while ((p = strchr(p, '\n'))) {
2055 + opts->current_fixup_count++;
2056 + p++;
2057 + }
2058 + }
2059 +
2060 return 0;
2061 }
2062
@@ -2392,10 +2403,9 @@ static int error_with_patch(struct commit *commit,
2403 static int error_failed_squash(struct commit *commit,
2404 struct replay_opts *opts, int subject_len, const char *subject)
2405 {
2395 - if (rename(rebase_path_squash_msg(), rebase_path_message()))
2396 - return error(_("could not rename '%s' to '%s'"),
2406 + if (copy_file(rebase_path_message(), rebase_path_squash_msg(), 0666))
2407 + return error(_("could not copy '%s' to '%s'"),
2408 rebase_path_squash_msg(), rebase_path_message());
2398 - unlink(rebase_path_fixup_msg());
2409 unlink(git_path_merge_msg());
2410 if (copy_file(git_path_merge_msg(), rebase_path_message(), 0666))
2411 return error(_("could not copy '%s' to '%s'"),
sequencer.h
+5 -1
@@ -44,10 +44,14 @@ struct replay_opts {
44 char **xopts;
45 size_t xopts_nr, xopts_alloc;
46
47 + /* Used by fixup/squash */
48 + struct strbuf current_fixups;
49 + int current_fixup_count;
50 +
51 /* Only used by REPLAY_NONE */
52 struct rev_info *revs;
53 };
50 -#define REPLAY_OPTS_INIT { -1 }
54 +#define REPLAY_OPTS_INIT { .action = -1, .current_fixups = STRBUF_INIT }
55
56 /* Call this to setup defaults before parsing command line options */
57 void sequencer_init_config(struct replay_opts *opts);