Subject: [PATCH v7 3/3] replay: add replay.refAction config option

From: Siddharth Asthana <[email-masked]>
Date: Thu, 6 Nov 2025 00:46:01 +0530

Add a configuration variable to control the default behavior of git replay
for updating references. This allows users who prefer the traditional
pipeline output to set it once in their config instead of passing
--ref-action=print with every command.

The config variable uses string values that mirror the behavior modes:
  * replay.refAction = update (default): atomic ref updates
  * replay.refAction = print: output commands for pipeline

Helped-by: Junio C Hamano <[email-masked]>
Helped-by: Elijah Newren <[email-masked]>
Helped-by: Christian Couder <[email-masked]>
Helped-by: Phillip Wood <[email-masked]>
Signed-off-by: Siddharth Asthana <[email-masked]>
---
 Documentation/config/replay.adoc | 11 ++++++++
 Documentation/git-replay.adoc    |  2 ++
 builtin/replay.c                 | 24 ++++++++++++++---
 t/t3650-replay-basics.sh         | 46 ++++++++++++++++++++++++++++++++
 4 files changed, 79 insertions(+), 4 deletions(-)
 create mode 100644 Documentation/config/replay.adoc

diff --git a/Documentation/config/replay.adoc b/Documentation/config/replay.adoc
new file mode 100644
index 0000000000..7d549d2f0e
--- /dev/null
+++ b/Documentation/config/replay.adoc
@@ -0,0 +1,11 @@
+replay.refAction::
+	Specifies the default mode for handling reference updates in
+	`git replay`. The value can be:
++
+--
+	* `update`: Update refs directly using an atomic transaction (default behavior).
+	* `print`: Output update-ref commands for pipeline use.
+--
++
+This setting can be overridden with the `--ref-action` command-line option.
+When not configured, `git replay` defaults to `update` mode.
diff --git a/Documentation/git-replay.adoc b/Documentation/git-replay.adoc
index 2ef74ddb12..dcb26e8a8e 100644
--- a/Documentation/git-replay.adoc
+++ b/Documentation/git-replay.adoc
@@ -51,6 +51,8 @@ which uses the target only as a starting point without updating it.
 	* `print`: Output update-ref commands for pipeline use. This is the
 	  traditional behavior where output can be piped to `git update-ref --stdin`.
 --
++
+The default mode can be configured via the `replay.refAction` configuration variable.
 
 <revision-range>::
 	Range of commits to replay. More than one <revision-range> can
diff --git a/builtin/replay.c b/builtin/replay.c
index 94e60b5b10..6606a2c94b 100644
--- a/builtin/replay.c
+++ b/builtin/replay.c
@@ -8,6 +8,7 @@
 #include "git-compat-util.h"
 
 #include "builtin.h"
+#include "config.h"
 #include "environment.h"
 #include "hex.h"
 #include "lockfile.h"
@@ -298,6 +299,22 @@ static enum ref_action_mode parse_ref_action_mode(const char *ref_action, const
 	die(_("invalid %s value: '%s'"), source, ref_action);
 }
 
+static enum ref_action_mode get_ref_action_mode(struct repository *repo, const char *ref_action)
+{
+	const char *config_value = NULL;
+
+	/* Command line option takes precedence */
+	if (ref_action)
+		return parse_ref_action_mode(ref_action, "--ref-action");
+
+	/* Check config value */
+	if (!repo_config_get_string_tmp(repo, "replay.refAction", &config_value))
+		return parse_ref_action_mode(config_value, "replay.refAction");
+
+	/* Default to update mode */
+	return REF_ACTION_UPDATE;
+}
+
 static int handle_ref_update(enum ref_action_mode mode,
 			     struct ref_transaction *transaction,
 			     const char *refname,
@@ -332,7 +349,7 @@ int cmd_replay(int argc,
 	const char *onto_name = NULL;
 	int contained = 0;
 	const char *ref_action = NULL;
-	enum ref_action_mode ref_mode = REF_ACTION_UPDATE;
+	enum ref_action_mode ref_mode;
 
 	struct rev_info revs;
 	struct commit *last_commit = NULL;
@@ -378,9 +395,8 @@ int cmd_replay(int argc,
 	die_for_incompatible_opt2(!!advance_name_opt, "--advance",
 				  contained, "--contained");
 
-	/* Parse ref action mode */
-	if (ref_action)
-		ref_mode = parse_ref_action_mode(ref_action, "--ref-action");
+	/* Parse ref action mode from command line or config */
+	ref_mode = get_ref_action_mode(repo, ref_action);
 
 	advance_name = xstrdup_or_null(advance_name_opt);
 
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
index ec79234c80..cf3aacf355 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -268,4 +268,50 @@ test_expect_success 'reflog message for --advance mode' '
 	test_cmp expect-reflog reflog-msg
 '
 
+test_expect_success 'replay.refAction=print config option' '
+	# Store original state
+	START=$(git rev-parse topic2) &&
+	test_when_finished "git branch -f topic2 $START" &&
+
+	# Test with config set to print
+	test_config replay.refAction print &&
+	git replay --onto main topic1..topic2 >output &&
+	test_line_count = 1 output &&
+	test_grep "^update refs/heads/topic2 " output
+'
+
+test_expect_success 'replay.refAction=update config option' '
+	# Store original state
+	START=$(git rev-parse topic2) &&
+	test_when_finished "git branch -f topic2 $START" &&
+
+	# Test with config set to update
+	test_config replay.refAction update &&
+	git replay --onto main topic1..topic2 >output &&
+	test_must_be_empty output &&
+
+	# Verify ref was updated
+	git log --format=%s topic2 >actual &&
+	test_write_lines E D M L B A >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'command-line --ref-action overrides config' '
+	# Store original state
+	START=$(git rev-parse topic2) &&
+	test_when_finished "git branch -f topic2 $START" &&
+
+	# Set config to update but use --ref-action=print
+	test_config replay.refAction update &&
+	git replay --ref-action=print --onto main topic1..topic2 >output &&
+	test_line_count = 1 output &&
+	test_grep "^update refs/heads/topic2 " output
+'
+
+test_expect_success 'invalid replay.refAction value' '
+	test_config replay.refAction invalid &&
+	test_must_fail git replay --onto main topic1..topic2 2>error &&
+	test_grep "invalid.*replay.refAction.*value" error
+'
+
 test_done
-- 
2.51.0