builtin/replay: extract core logic to replay revisions
We're about to move the core logic used to replay revisions onto a new base into the "libgit.a" library. Prepare for this by pulling out the logic into a new function `replay_revisions()` that: 1. Takes a set of revisions to replay and some options that tell it how it ought to replay the revisions. 2. Replays the commits. 3. Records any reference updates that would be caused by replaying the commits in a structure that is owned by the caller. The logic itself will be moved into a separate file in the next commit. This change is not expected to cause user-visible change in behaviour. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Patrick Steinhardt committed
Jan 13, 2026 at 10:54 UTC
1454743eb84eea1f7e4ea23e0556ed5ef9888894
1 file changed
+162
-107
builtin/replay.c
+162
-107
@@ -177,8 +177,9 @@ static void set_up_replay_mode(struct repository *repo,
177
if (!rinfo.positive_refexprs)
178
die(_("need some commits to replay"));
179
180
- die_for_incompatible_opt2(!!onto_name, "--onto",
181
- !!*advance_name, "--advance");
180
+ if (!onto_name == !*advance_name)
181
+ BUG("one and only one of onto_name and *advance_name must be given");
182
+
183
if (onto_name) {
184
*onto = peel_committish(repo, onto_name, "--onto");
185
if (rinfo.positive_refexprs <
@@ -253,6 +254,134 @@ static struct commit *pick_regular_commit(struct repository *repo,
254
return create_commit(repo, result->tree, pickme, replayed_base);
255
}
256
257
+struct replay_revisions_options {
258
+ const char *advance;
259
+ const char *onto;
260
+ int contained;
261
+};
262
+
263
+struct replay_result {
264
+ struct replay_ref_update {
265
+ char *refname;
266
+ struct object_id old_oid;
267
+ struct object_id new_oid;
268
+ } *updates;
269
+ size_t updates_nr, updates_alloc;
270
+};
271
+
272
+static void replay_result_release(struct replay_result *result)
273
+{
274
+ for (size_t i = 0; i < result->updates_nr; i++)
275
+ free(result->updates[i].refname);
276
+ free(result->updates);
277
+}
278
+
279
+static void replay_result_queue_update(struct replay_result *result,
280
+ const char *refname,
281
+ const struct object_id *old_oid,
282
+ const struct object_id *new_oid)
283
+{
284
+ ALLOC_GROW(result->updates, result->updates_nr + 1, result->updates_alloc);
285
+ result->updates[result->updates_nr].refname = xstrdup(refname);
286
+ result->updates[result->updates_nr].old_oid = *old_oid;
287
+ result->updates[result->updates_nr].new_oid = *new_oid;
288
+ result->updates_nr++;
289
+}
290
+
291
+static int replay_revisions(struct rev_info *revs,
292
+ struct replay_revisions_options *opts,
293
+ struct replay_result *out)
294
+{
295
+ kh_oid_map_t *replayed_commits = NULL;
296
+ struct strset *update_refs = NULL;
297
+ struct commit *last_commit = NULL;
298
+ struct commit *commit;
299
+ struct commit *onto = NULL;
300
+ struct merge_options merge_opt;
301
+ struct merge_result result;
302
+ char *advance;
303
+ int ret;
304
+
305
+ advance = xstrdup_or_null(opts->advance);
306
+ set_up_replay_mode(revs->repo, &revs->cmdline, opts->onto, &advance,
307
+ &onto, &update_refs);
308
+
309
+ /* FIXME: Should allow replaying commits with the first as a root commit */
310
+
311
+ if (prepare_revision_walk(revs) < 0) {
312
+ ret = error(_("error preparing revisions"));
313
+ goto out;
314
+ }
315
+
316
+ init_basic_merge_options(&merge_opt, revs->repo);
317
+ memset(&result, 0, sizeof(result));
318
+ merge_opt.show_rename_progress = 0;
319
+ last_commit = onto;
320
+ replayed_commits = kh_init_oid_map();
321
+ while ((commit = get_revision(revs))) {
322
+ const struct name_decoration *decoration;
323
+ khint_t pos;
324
+ int hr;
325
+
326
+ if (!commit->parents)
327
+ die(_("replaying down from root commit is not supported yet!"));
328
+ if (commit->parents->next)
329
+ die(_("replaying merge commits is not supported yet!"));
330
+
331
+ last_commit = pick_regular_commit(revs->repo, commit, replayed_commits,
332
+ onto, &merge_opt, &result);
333
+ if (!last_commit)
334
+ break;
335
+
336
+ /* Record commit -> last_commit mapping */
337
+ pos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr);
338
+ if (hr == 0)
339
+ BUG("Duplicate rewritten commit: %s\n",
340
+ oid_to_hex(&commit->object.oid));
341
+ kh_value(replayed_commits, pos) = last_commit;
342
+
343
+ /* Update any necessary branches */
344
+ if (advance)
345
+ continue;
346
+ decoration = get_name_decoration(&commit->object);
347
+ if (!decoration)
348
+ continue;
349
+ while (decoration) {
350
+ if (decoration->type == DECORATION_REF_LOCAL &&
351
+ (opts->contained || strset_contains(update_refs,
352
+ decoration->name))) {
353
+ replay_result_queue_update(out, decoration->name,
354
+ &commit->object.oid,
355
+ &last_commit->object.oid);
356
+ }
357
+ decoration = decoration->next;
358
+ }
359
+ }
360
+
361
+ if (!result.clean) {
362
+ ret = 1;
363
+ goto out;
364
+ }
365
+
366
+ /* In --advance mode, advance the target ref */
367
+ if (advance)
368
+ replay_result_queue_update(out, advance,
369
+ &onto->object.oid,
370
+ &last_commit->object.oid);
371
+
372
+ ret = 0;
373
+
374
+out:
375
+ if (update_refs) {
376
+ strset_clear(update_refs);
377
+ free(update_refs);
378
+ }
379
+ kh_destroy_oid_map(replayed_commits);
380
+ merge_finalize(&merge_opt, &result);
381
+ free(advance);
382
+ return ret;
383
+}
384
+
385
static enum ref_action_mode parse_ref_action_mode(const char *ref_action, const char *source)
386
{
387
if (!ref_action || !strcmp(ref_action, "update"))
@@ -306,21 +435,11 @@ int cmd_replay(int argc,
435
const char *prefix,
436
struct repository *repo)
437
{
309
- const char *advance_name_opt = NULL;
310
- char *advance_name = NULL;
311
- struct commit *onto = NULL;
312
- const char *onto_name = NULL;
313
- int contained = 0;
438
+ struct replay_revisions_options opts = { 0 };
439
+ struct replay_result result = { 0 };
440
const char *ref_action = NULL;
441
enum ref_action_mode ref_mode;
316
-
442
struct rev_info revs;
318
- struct commit *last_commit = NULL;
319
- struct commit *commit;
320
- struct merge_options merge_opt;
321
- struct merge_result result;
322
- struct strset *update_refs = NULL;
323
- kh_oid_map_t *replayed_commits;
443
struct ref_transaction *transaction = NULL;
444
struct strbuf transaction_err = STRBUF_INIT;
445
struct strbuf reflog_msg = STRBUF_INIT;
@@ -333,13 +452,13 @@ int cmd_replay(int argc,
452
NULL
453
};
454
struct option replay_options[] = {
336
- OPT_STRING(0, "advance", &advance_name_opt,
455
+ OPT_STRING(0, "advance", &opts.advance,
456
N_("branch"),
457
N_("make replay advance given branch")),
339
- OPT_STRING(0, "onto", &onto_name,
458
+ OPT_STRING(0, "onto", &opts.onto,
459
N_("revision"),
460
N_("replay onto given commit")),
342
- OPT_BOOL(0, "contained", &contained,
461
+ OPT_BOOL(0, "contained", &opts.contained,
462
N_("update all branches that point at commits in <revision-range>")),
463
OPT_STRING(0, "ref-action", &ref_action,
464
N_("mode"),
@@ -350,19 +469,19 @@ int cmd_replay(int argc,
469
argc = parse_options(argc, argv, prefix, replay_options, replay_usage,
470
PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT);
471
353
- if (!onto_name && !advance_name_opt) {
472
+ if (!opts.onto && !opts.advance) {
473
error(_("option --onto or --advance is mandatory"));
474
usage_with_options(replay_usage, replay_options);
475
}
476
358
- die_for_incompatible_opt2(!!advance_name_opt, "--advance",
359
- contained, "--contained");
477
+ die_for_incompatible_opt2(!!opts.advance, "--advance",
478
+ opts.contained, "--contained");
479
+ die_for_incompatible_opt2(!!opts.advance, "--advance",
480
+ !!opts.onto, "--onto");
481
482
/* Parse ref action mode from command line or config */
483
ref_mode = get_ref_action_mode(repo, ref_action);
484
364
- advance_name = xstrdup_or_null(advance_name_opt);
365
-
485
repo_init_revisions(repo, &revs, prefix);
486
487
/*
@@ -414,18 +533,19 @@ int cmd_replay(int argc,
533
revs.simplify_history = 0;
534
}
535
417
- set_up_replay_mode(repo, &revs.cmdline,
418
- onto_name, &advance_name,
419
- &onto, &update_refs);
420
-
421
- /* FIXME: Should allow replaying commits with the first as a root commit */
536
+ ret = replay_revisions(&revs, &opts, &result);
537
+ if (ret)
538
+ goto cleanup;
539
540
/* Build reflog message */
424
- if (advance_name_opt)
425
- strbuf_addf(&reflog_msg, "replay --advance %s", advance_name_opt);
426
- else
427
- strbuf_addf(&reflog_msg, "replay --onto %s",
428
- oid_to_hex(&onto->object.oid));
541
+ if (opts.advance) {
542
+ strbuf_addf(&reflog_msg, "replay --advance %s", opts.advance);
543
+ } else {
544
+ struct object_id oid;
545
+ if (repo_get_oid_committish(repo, opts.onto, &oid))
546
+ BUG("--onto commit should have been resolved beforehand already");
547
+ strbuf_addf(&reflog_msg, "replay --onto %s", oid_to_hex(&oid));
548
+ }
549
550
/* Initialize ref transaction if using update mode */
551
if (ref_mode == REF_ACTION_UPDATE) {
@@ -438,78 +558,19 @@ int cmd_replay(int argc,
558
}
559
}
560
441
- if (prepare_revision_walk(&revs) < 0) {
442
- ret = error(_("error preparing revisions"));
443
- goto cleanup;
444
- }
445
-
446
- init_basic_merge_options(&merge_opt, repo);
447
- memset(&result, 0, sizeof(result));
448
- merge_opt.show_rename_progress = 0;
449
- last_commit = onto;
450
- replayed_commits = kh_init_oid_map();
451
- while ((commit = get_revision(&revs))) {
452
- const struct name_decoration *decoration;
453
- khint_t pos;
454
- int hr;
455
-
456
- if (!commit->parents)
457
- die(_("replaying down from root commit is not supported yet!"));
458
- if (commit->parents->next)
459
- die(_("replaying merge commits is not supported yet!"));
460
-
461
- last_commit = pick_regular_commit(repo, commit, replayed_commits,
462
- onto, &merge_opt, &result);
463
- if (!last_commit)
464
- break;
465
-
466
- /* Record commit -> last_commit mapping */
467
- pos = kh_put_oid_map(replayed_commits, commit->object.oid, &hr);
468
- if (hr == 0)
469
- BUG("Duplicate rewritten commit: %s\n",
470
- oid_to_hex(&commit->object.oid));
471
- kh_value(replayed_commits, pos) = last_commit;
472
-
473
- /* Update any necessary branches */
474
- if (advance_name)
475
- continue;
476
- decoration = get_name_decoration(&commit->object);
477
- if (!decoration)
478
- continue;
479
- while (decoration) {
480
- if (decoration->type == DECORATION_REF_LOCAL &&
481
- (contained || strset_contains(update_refs,
482
- decoration->name))) {
483
- if (handle_ref_update(ref_mode, transaction,
484
- decoration->name,
485
- &last_commit->object.oid,
486
- &commit->object.oid,
487
- reflog_msg.buf,
488
- &transaction_err) < 0) {
489
- ret = error(_("failed to update ref '%s': %s"),
490
- decoration->name, transaction_err.buf);
491
- goto cleanup;
492
- }
493
- }
494
- decoration = decoration->next;
495
- }
496
- }
497
-
498
- /* In --advance mode, advance the target ref */
499
- if (result.clean == 1 && advance_name) {
500
- if (handle_ref_update(ref_mode, transaction, advance_name,
501
- &last_commit->object.oid,
502
- &onto->object.oid,
503
- reflog_msg.buf,
504
- &transaction_err) < 0) {
561
+ for (size_t i = 0; i < result.updates_nr; i++) {
562
+ ret = handle_ref_update(ref_mode, transaction, result.updates[i].refname,
563
+ &result.updates[i].new_oid, &result.updates[i].old_oid,
564
+ reflog_msg.buf, &transaction_err);
565
+ if (ret) {
566
ret = error(_("failed to update ref '%s': %s"),
506
- advance_name, transaction_err.buf);
567
+ result.updates[i].refname, transaction_err.buf);
568
goto cleanup;
569
}
570
}
571
572
/* Commit the ref transaction if we have one */
512
- if (transaction && result.clean == 1) {
573
+ if (transaction) {
574
if (ref_transaction_commit(transaction, &transaction_err)) {
575
ret = error(_("failed to commit ref transaction: %s"),
576
transaction_err.buf);
@@ -517,24 +578,18 @@ int cmd_replay(int argc,
578
}
579
}
580
520
- merge_finalize(&merge_opt, &result);
521
- kh_destroy_oid_map(replayed_commits);
522
- if (update_refs) {
523
- strset_clear(update_refs);
524
- free(update_refs);
525
- }
526
- ret = result.clean;
581
+ ret = 0;
582
583
cleanup:
584
if (transaction)
585
ref_transaction_free(transaction);
586
+ replay_result_release(&result);
587
strbuf_release(&transaction_err);
588
strbuf_release(&reflog_msg);
589
release_revisions(&revs);
534
- free(advance_name);
590
591
/* Return */
592
if (ret < 0)
593
exit(128);
539
- return ret ? 0 : 1;
594
+ return ret;
595
}