rebase: remember fixup -c after skipping fixup/squash

When the final command in a chain of "fixup" and "squash" commands is skipped, we should prompt the user to edit the commit message if the chain contains a "fixup -c" command that was not skipped. Unfortunately, commit_staged_changes() only looks for completed "squash" commands and so does not prompt the user to edit the message. Fix this by recording whether a fixup command has the "-c" flag set and then checking whether we have seen either a "fixup -c" or a "squash" command. Add regression tests for skipping a command in the middle of the chain (which currently works but has no test coverage), and for skipping the final command (which is fixed by this patch). Signed-off-by: Phillip Wood <phillip.wood@dunelm.org.uk> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Phillip Wood committed Jul 26, 2026 at 16:39 UTC 4a3a8ee96c7c749b99c91345db190a2c3720abeb
2 files changed +63 -4
sequencer.c
+16 -4
@@ -1926,6 +1926,13 @@ static int seen_squash(struct replay_ctx *ctx)
1926 strstr(ctx->current_fixups.buf, "\nsquash");
1927 }
1928
1929 +/* Does the current fixup chain contain a "fixup -c" command? */
1930 +static int seen_fixup_edit_msg(struct replay_ctx *ctx)
1931 +{
1932 + return starts_with(ctx->current_fixups.buf, "fixup -c") ||
1933 + strstr(ctx->current_fixups.buf, "\nfixup -c");
1934 +}
1935 +
1936 static void update_comment_bufs(struct strbuf *buf1, struct strbuf *buf2, int n)
1937 {
1938 strbuf_setlen(buf1, strlen(comment_line_str) + 1);
@@ -2148,9 +2155,14 @@ static int update_squash_messages(struct repository *r,
2155 strbuf_release(&buf);
2156
2157 if (!res) {
2151 - strbuf_addf(&ctx->current_fixups, "%s%s %s",
2158 + const char *fixup_flag = "";
2159 +
2160 + if (is_fixup_flag(command, flag) && (flag & TODO_EDIT_FIXUP_MSG))
2161 + fixup_flag = " -c";
2162 +
2163 + strbuf_addf(&ctx->current_fixups, "%s%s%s %s",
2164 ctx->current_fixups.len ? "\n" : "",
2153 - command_to_string(command),
2165 + command_to_string(command), fixup_flag,
2166 oid_to_hex(&commit->object.oid));
2167 res = write_message(ctx->current_fixups.buf,
2168 ctx->current_fixups.len,
@@ -5391,8 +5403,8 @@ static int commit_staged_changes(struct repository *r,
5403 * message, no need to bother the user with
5404 * opening the commit message in the editor.
5405 */
5394 - if (!starts_with(p, "squash ") &&
5395 - !strstr(p, "\nsquash "))
5406 + if (!seen_squash(ctx) &&
5407 + !seen_fixup_edit_msg(ctx))
5408 flags = (flags & ~EDIT_MSG) | CLEANUP_MSG;
5409 } else if (is_fixup(peek_command(todo_list, 0))) {
5410 /*
t/t3437-rebase-fixup-options.sh
+47
@@ -186,6 +186,53 @@ test_expect_success 'multiple fixup -c opens editor once' '
186 test_commit_message HEAD expected-message
187 '
188
189 +test_expect_success 'fixup -c is remembered after skipping final fixup' '
190 + test_when_finished "test_might_fail git rebase --abort" &&
191 + cat >todo <<-\EOF &&
192 + pick B
193 + fixup -c A1
194 + fixup A3
195 + EOF
196 + (
197 + set_fake_editor &&
198 + set_replace_editor todo &&
199 + test_must_fail git rebase -i A A &&
200 + git show && cat .git/rebase-merge/message-squash &&
201 + FAKE_COMMIT_AMEND=edited git rebase --skip
202 + ) &&
203 + test_commit_message HEAD <<-\EOF
204 + new subject
205 +
206 + new
207 + body
208 +
209 + edited
210 + EOF
211 +'
212 +test_expect_success 'fixup -c is remembered after skipping later fixup' '
213 + test_when_finished "test_might_fail git rebase --abort" &&
214 + cat >todo <<-\EOF &&
215 + pick B
216 + fixup -c A1
217 + fixup A3
218 + fixup A2
219 + EOF
220 + (
221 + set_fake_editor &&
222 + set_replace_editor todo &&
223 + test_must_fail git rebase -i A A &&
224 + FAKE_COMMIT_AMEND=edited git rebase --skip
225 + ) &&
226 + test_commit_message HEAD <<-\EOF
227 + new subject
228 +
229 + new
230 + body
231 +
232 + edited
233 + EOF
234 +'
235 +
236 test_expect_success 'sequence squash, fixup & fixup -c gives combined message' '
237 test_when_finished "test_might_fail git rebase --abort" &&
238 git checkout --detach A3 &&