builtin/history: perform revwalk checks before asking for user input

When setting up the revision walk in git-history(1) we also perform some verifications whether the request actually looks sane. Unfortunately, these verifications come _after_ we have already asked the user for the commit message of the commit that is to be rewritten. So in case any of the verifications fails, the user will have lost their modifications. Extract the function to set up the revision walk and call it before we ask for user input to fix this. Adapt one of the tests that is expected to fail because of this check to use false(1) as editor. If the editor had been executed by Git, it would fail with the error message "Aborting commit as launching the editor failed." Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Feb 16, 2026 at 07:45 UTC 76a3f28243ebb0be492f30210f2426a9f511f920
2 files changed +44 -27
builtin/history.c
+43 -26
@@ -177,30 +177,15 @@ static int parse_ref_action(const struct option *opt, const char *value, int uns
177 return 0;
178 }
179
180 -static int handle_reference_updates(enum ref_action action,
181 - struct repository *repo,
182 - struct commit *original,
183 - struct commit *rewritten,
184 - const char *reflog_msg)
180 +static int setup_revwalk(struct repository *repo,
181 + enum ref_action action,
182 + struct commit *original,
183 + struct rev_info *revs)
184 {
186 - const struct name_decoration *decoration;
187 - struct replay_revisions_options opts = { 0 };
188 - struct replay_result result = { 0 };
189 - struct ref_transaction *transaction = NULL;
185 struct strvec args = STRVEC_INIT;
191 - struct strbuf err = STRBUF_INIT;
192 - struct commit *head = NULL;
193 - struct rev_info revs;
194 - char hex[GIT_MAX_HEXSZ + 1];
195 - bool detached_head;
196 - int head_flags = 0;
186 int ret;
187
199 - refs_read_ref_full(get_main_ref_store(repo), "HEAD",
200 - RESOLVE_REF_NO_RECURSE, NULL, &head_flags);
201 - detached_head = !(head_flags & REF_ISSYMREF);
202 -
203 - repo_init_revisions(repo, &revs, NULL);
188 + repo_init_revisions(repo, revs, NULL);
189 strvec_push(&args, "ignored");
190 strvec_push(&args, "--reverse");
191 strvec_push(&args, "--topo-order");
@@ -224,6 +209,7 @@ static int handle_reference_updates(enum ref_action action,
209 */
210 if (action == REF_ACTION_HEAD) {
211 struct commit_list *from_list = NULL;
212 + struct commit *head;
213
214 head = lookup_commit_reference_by_name("HEAD");
215 if (!head) {
@@ -250,20 +236,47 @@ static int handle_reference_updates(enum ref_action action,
236 strvec_push(&args, "HEAD");
237 }
238
253 - setup_revisions_from_strvec(&args, &revs, NULL);
239 + setup_revisions_from_strvec(&args, revs, NULL);
240 if (args.nr != 1)
241 BUG("revisions were set up with invalid argument");
242
243 + ret = 0;
244 +
245 +out:
246 + strvec_clear(&args);
247 + return ret;
248 +}
249 +
250 +static int handle_reference_updates(struct rev_info *revs,
251 + enum ref_action action,
252 + struct commit *original,
253 + struct commit *rewritten,
254 + const char *reflog_msg)
255 +{
256 + const struct name_decoration *decoration;
257 + struct replay_revisions_options opts = { 0 };
258 + struct replay_result result = { 0 };
259 + struct ref_transaction *transaction = NULL;
260 + struct strbuf err = STRBUF_INIT;
261 + char hex[GIT_MAX_HEXSZ + 1];
262 + bool detached_head;
263 + int head_flags = 0;
264 + int ret;
265 +
266 + refs_read_ref_full(get_main_ref_store(revs->repo), "HEAD",
267 + RESOLVE_REF_NO_RECURSE, NULL, &head_flags);
268 + detached_head = !(head_flags & REF_ISSYMREF);
269 +
270 opts.onto = oid_to_hex_r(hex, &rewritten->object.oid);
271
259 - ret = replay_revisions(&revs, &opts, &result);
272 + ret = replay_revisions(revs, &opts, &result);
273 if (ret)
274 goto out;
275
276 switch (action) {
277 case REF_ACTION_BRANCHES:
278 case REF_ACTION_HEAD:
266 - transaction = ref_store_transaction_begin(get_main_ref_store(repo), 0, &err);
279 + transaction = ref_store_transaction_begin(get_main_ref_store(revs->repo), 0, &err);
280 if (!transaction) {
281 ret = error(_("failed to begin ref transaction: %s"), err.buf);
282 goto out;
@@ -343,9 +356,7 @@ static int handle_reference_updates(enum ref_action action,
356 out:
357 ref_transaction_free(transaction);
358 replay_result_release(&result);
346 - release_revisions(&revs);
359 strbuf_release(&err);
348 - strvec_clear(&args);
360 return ret;
361 }
362
@@ -367,6 +378,7 @@ static int cmd_history_reword(int argc,
378 };
379 struct strbuf reflog_msg = STRBUF_INIT;
380 struct commit *original, *rewritten;
381 + struct rev_info revs;
382 int ret;
383
384 argc = parse_options(argc, argv, prefix, options, usage, 0);
@@ -385,6 +397,10 @@ static int cmd_history_reword(int argc,
397 goto out;
398 }
399
400 + ret = setup_revwalk(repo, action, original, &revs);
401 + if (ret)
402 + goto out;
403 +
404 ret = commit_tree_with_edited_message(repo, "reworded", original, &rewritten);
405 if (ret < 0) {
406 ret = error(_("failed writing reworded commit"));
@@ -393,7 +409,7 @@ static int cmd_history_reword(int argc,
409
410 strbuf_addf(&reflog_msg, "reword: updating %s", argv[0]);
411
396 - ret = handle_reference_updates(action, repo, original, rewritten,
412 + ret = handle_reference_updates(&revs, action, original, rewritten,
413 reflog_msg.buf);
414 if (ret < 0) {
415 ret = error(_("failed replaying descendants"));
@@ -404,6 +420,7 @@ static int cmd_history_reword(int argc,
420
421 out:
422 strbuf_release(&reflog_msg);
423 + release_revisions(&revs);
424 return ret;
425 }
426
t/t3451-history-reword.sh
+1 -1
@@ -263,7 +263,7 @@ test_expect_success '--ref-action=head updates only HEAD' '
263
264 # When told to update HEAD, only, the command will refuse to
265 # rewrite commits that are not an ancestor of HEAD.
266 - test_must_fail git history reword --ref-action=head theirs 2>err &&
266 + test_must_fail git -c core.editor=false history reword --ref-action=head theirs 2>err &&
267 test_grep "rewritten commit must be an ancestor of HEAD" err &&
268
269 reword_with_message --ref-action=head base >updates <<-\EOF &&