replay: allow callers to control what happens with empty commits
When replaying commits it may happen that some of the commits become empty relative to their parent. Such commits are for now automatically dropped by the replay subsystem without much control from the user. Introduce a new enum that allows the caller to drop, keep or abort in this case. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Patrick Steinhardt committed
Apr 27, 2026 at 07:53 UTC
0cf4ad7cf555ac2ed6556a3169d30b2eabb68d41
2 files changed
+43
-5
replay.c
+24
-5
@@ -269,7 +269,8 @@ static struct commit *pick_regular_commit(struct repository *repo,
269
struct commit *onto,
270
struct merge_options *merge_opt,
271
struct merge_result *result,
272
- enum replay_mode mode)
272
+ enum replay_mode mode,
273
+ enum replay_empty_commit_action empty)
274
{
275
struct commit *base, *replayed_base;
276
struct tree *pickme_tree, *base_tree, *replayed_base_tree;
@@ -321,12 +322,25 @@ static struct commit *pick_regular_commit(struct repository *repo,
322
}
323
merge_opt->ancestor = NULL;
324
merge_opt->branch2 = NULL;
325
+
326
if (!result->clean)
327
return NULL;
326
- /* Drop commits that become empty */
328
+
329
+ /* Handle commits that become empty */
330
if (oideq(&replayed_base_tree->object.oid, &result->tree->object.oid) &&
328
- !oideq(&pickme_tree->object.oid, &base_tree->object.oid))
329
- return replayed_base;
331
+ !oideq(&pickme_tree->object.oid, &base_tree->object.oid)) {
332
+ switch (empty) {
333
+ case REPLAY_EMPTY_COMMIT_DROP:
334
+ return replayed_base;
335
+ case REPLAY_EMPTY_COMMIT_KEEP:
336
+ break;
337
+ case REPLAY_EMPTY_COMMIT_ABORT:
338
+ result->clean = error(_("commit %s became empty after replay"),
339
+ oid_to_hex(&pickme->object.oid));
340
+ return NULL;
341
+ }
342
+ }
343
+
344
return create_commit(repo, result->tree, pickme, replayed_base, mode);
345
}
346
@@ -417,7 +431,7 @@ int replay_revisions(struct rev_info *revs,
431
432
last_commit = pick_regular_commit(revs->repo, commit, replayed_commits,
433
mode == REPLAY_MODE_REVERT ? last_commit : onto,
420
- &merge_opt, &result, mode);
434
+ &merge_opt, &result, mode, opts->empty);
435
if (!last_commit)
436
break;
437
@@ -458,6 +472,11 @@ int replay_revisions(struct rev_info *revs,
472
}
473
}
474
475
+ if (result.clean < 0) {
476
+ ret = -1;
477
+ goto out;
478
+ }
479
+
480
if (!result.clean) {
481
ret = 1;
482
goto out;
replay.h
+19
@@ -6,6 +6,19 @@
6
struct repository;
7
struct rev_info;
8
9
+/*
10
+ * Controls what happens when a replayed commit becomes empty (i.e. its tree
11
+ * is identical to its parent's tree after the replay).
12
+ */
13
+enum replay_empty_commit_action {
14
+ /* Silently discard the empty commit. */
15
+ REPLAY_EMPTY_COMMIT_DROP,
16
+ /* Keep the empty commit as-is. */
17
+ REPLAY_EMPTY_COMMIT_KEEP,
18
+ /* Abort with an error. */
19
+ REPLAY_EMPTY_COMMIT_ABORT,
20
+};
21
+
22
/*
23
* A set of options that can be passed to `replay_revisions()`.
24
*/
@@ -43,6 +56,12 @@ struct replay_revisions_options {
56
* Requires `onto` to be set.
57
*/
58
int contained;
59
+
60
+ /*
61
+ * Controls what to do when a replayed commit becomes empty.
62
+ * Defaults to REPLAY_EMPTY_COMMIT_DROP.
63
+ */
64
+ enum replay_empty_commit_action empty;
65
};
66
67
/* This struct is used as an out-parameter by `replay_revisions()`. */