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

From: Siddharth Asthana <[email-masked]>
Date: Thu, 23 Oct 2025 00:20:45 +0530

Add a configuration option 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 option uses string values that mirror the behavior modes:
  * replay.refAction = update (default): atomic ref updates
  * replay.refAction = print: output commands for pipeline

The command-line --ref-action option always overrides the config setting,
allowing users to temporarily change behavior for a single invocation.

Implementation details:

In cmd_replay(), after parsing command-line options, we check if
--ref-action was provided. If not, we read the configuration using
repo_config_get_string_tmp(). If the config variable is set, we validate
the value and use it to set the ref_action_str:

  Config value      Internal mode    Behavior
  ──────────────────────────────────────────────────────────────
  "update"          "update"         Atomic ref updates (default)
  "print"           "print"          Pipeline output
  (not set)         "update"         Atomic ref updates (default)
  (invalid)         error            Die with helpful message

If an invalid value is provided, we die() immediately with an error
message explaining the valid options. This catches configuration errors
early and provides clear guidance to users.

The command-line --ref-action option, when provided, overrides the
config value. This precedence allows users to set their preferred default
while still having per-invocation control:

  git config replay.refAction print         # Set default
  git replay --ref-action=update --onto main topic  # Override once

The config and command-line option use the same value names ('update'
and 'print') for consistency and clarity. This makes it immediately
obvious how the config maps to the command-line option, addressing
feedback about the relationship between configuration and command-line
options being clear to users.

Examples:

$ git config --global replay.refAction print
$ git replay --onto main topic1..topic2 | git update-ref --stdin

$ git replay --ref-action=update --onto main topic1..topic2

$ git config replay.refAction update
$ git replay --onto main topic1..topic2  # Updates refs directly

The implementation follows Git's standard configuration precedence:
command-line options override config values, which matches user
expectations across all Git commands.

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 +++++++++
 builtin/replay.c                 | 16 +++++++++++-
 t/t3650-replay-basics.sh         | 42 ++++++++++++++++++++++++++++++++
 3 files changed, 68 insertions(+), 1 deletion(-)
 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/builtin/replay.c b/builtin/replay.c
index 1246add636..bb0420dc99 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"
@@ -367,7 +368,20 @@ int cmd_replay(int argc,
 	die_for_incompatible_opt2(!!advance_name_opt, "--advance",
 				  contained, "--contained");
 
-	/* Default to update mode if not specified */
+	/* Set default mode from config if not specified on command line */
+	if (!ref_action_str) {
+		const char *config_value = NULL;
+		if (!repo_config_get_string_tmp(repo, "replay.refAction", &config_value)) {
+			if (!strcmp(config_value, "update"))
+				ref_action_str = "update";
+			else if (!strcmp(config_value, "print"))
+				ref_action_str = "print";
+			else
+				die(_("invalid value for replay.refAction: '%s'"), config_value);
+		}
+	}
+
+	/* Default to update mode if still not set */
 	if (!ref_action_str)
 		ref_action_str = "update";
 
diff --git a/t/t3650-replay-basics.sh b/t/t3650-replay-basics.sh
index 54c86b87d8..307beb667e 100755
--- a/t/t3650-replay-basics.sh
+++ b/t/t3650-replay-basics.sh
@@ -217,4 +217,46 @@ test_expect_success 'merge.directoryRenames=false' '
 		--onto rename-onto rename-onto..rename-from
 '
 
+test_expect_success 'replay.refAction config option' '
+	# Store original state
+	START=$(git rev-parse topic2) &&
+	test_when_finished "git branch -f topic2 $START && git config --unset replay.refAction" &&
+
+	# Set config to print
+	git config replay.refAction print &&
+	git replay --onto main topic1..topic2 >output &&
+	test_line_count = 1 output &&
+	grep "^update refs/heads/topic2 " output &&
+
+	# Reset and test update mode
+	git branch -f topic2 $START &&
+	git 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 && git config --unset replay.refAction" &&
+
+	# Set config to update but use --ref-action=print
+	git config replay.refAction update &&
+	git replay --ref-action=print --onto main topic1..topic2 >output &&
+	test_line_count = 1 output &&
+	grep "^update refs/heads/topic2 " output
+'
+
+test_expect_success 'invalid replay.refAction value' '
+	test_when_finished "git config --unset replay.refAction" &&
+	git config replay.refAction invalid &&
+	test_must_fail git replay --onto main topic1..topic2 2>error &&
+	grep "invalid value for replay.refAction" error
+'
+
 test_done
-- 
2.51.0