Git development
 help / color / mirror / Atom feed
* [PATCH 00/12] builtin/rerere: convert to use subcommands
@ 2026-10-27 10:46 Patrick Steinhardt
  2026-10-27 10:46 ` [PATCH 01/12] rerere: rename `repo_rerere()` Patrick Steinhardt
                   ` (13 more replies)
  0 siblings, 14 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-27 10:46 UTC (permalink / raw)
  To: git

Hi,

while reviewing some patch series around git-rerere(1) recently I was a
bit appalled by our custom subcommand parsing that we do here. This
patch series here thus cleans up and modernizes that part a bit so that
git-rerere(1) behaves like every other Git command that happens to have
subcommands.

This series is based on 37b3475fb53 (The 9th batch, 2026-10-25) with
ps/rerere-remove-global-state at b948c10c8b3 (rerere: get rid of global
rerere cache state, 2026-10-23) merged into it.

Thanks!

Patrick

---
Patrick Steinhardt (12):
      rerere: rename `repo_rerere()`
      rerere: convert flags into an enum
      builtin/rerere: drop `the_repository`
      builtin/rerere: stop passing flags to subcommands that ignore them
      builtin/rerere: turn "forget" into a proper subcommand
      builtin/rerere: turn "clear" into a proper subcommand
      builtin/rerere: turn "status" into a proper subcommand
      builtin/rerere: turn "remaining" into a proper subcommand
      builtin/rerere: turn "diff" into a proper subcommand
      builtin/rerere: turn "gc" into a proper subcommand
      builtin/rerere: use `OPT_SUBCOMMAND()`
      builtin/rerere: introduce proper usage strings for subcommands

 Documentation/git-rerere.adoc |   8 +-
 apply.c                       |   2 +-
 builtin/am.c                  |   4 +-
 builtin/commit.c              |   2 +-
 builtin/merge.c               |   2 +-
 builtin/rerere.c              | 319 ++++++++++++++++++++++++++++++++----------
 builtin/stash.c               |   2 +-
 rerere.c                      |   7 +-
 rerere.h                      |  21 +--
 sequencer.c                   |   4 +-
 t/t4200-rerere.sh             |   5 -
 11 files changed, 275 insertions(+), 101 deletions(-)


---
base-commit: 710910c03a22ac663885049213b9fef5853ea8e0
change-id: 20261027-pks-rerere-subcommands-318d41553438


^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH 01/12] rerere: rename `repo_rerere()`
  2026-10-27 10:46 [PATCH 00/12] builtin/rerere: convert to use subcommands Patrick Steinhardt
@ 2026-10-27 10:46 ` Patrick Steinhardt
  2026-10-27 19:48   ` Junio C Hamano
  2026-10-27 10:46 ` [PATCH 02/12] rerere: convert flags into an enum Patrick Steinhardt
                   ` (12 subsequent siblings)
  13 siblings, 1 reply; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-27 10:46 UTC (permalink / raw)
  To: git

The `repo_rerere()` function is the only function in "rerere.h" that
does not have a `rerere_` prefix. And while it does indeed receive a
repository as first parameter, that's not a good-enough reason to give
it a `repo_` prefix.

Rename the function to `rerere_record()` to be consistent with all the
other functions.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 apply.c          | 2 +-
 builtin/am.c     | 4 ++--
 builtin/commit.c | 2 +-
 builtin/merge.c  | 2 +-
 builtin/rerere.c | 2 +-
 builtin/stash.c  | 2 +-
 rerere.c         | 2 +-
 rerere.h         | 2 +-
 sequencer.c      | 4 ++--
 9 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/apply.c b/apply.c
index a0b7119c639..d0b100ca065 100644
--- a/apply.c
+++ b/apply.c
@@ -4866,7 +4866,7 @@ static int write_out_results(struct apply_state *state, struct patch *list)
 		 * tree with conflict markers, but that isn't written with --cached.
 		 */
 		if (!state->cached)
-			repo_rerere(state->repo, 0);
+			rerere_record(state->repo, 0);
 	}
 
 	return errs;
diff --git a/builtin/am.c b/builtin/am.c
index e9623b83077..1d156cf877c 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1649,7 +1649,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
 		o.verbosity = 0;
 
 	if (merge_ort_generic(&o, &our_tree, &their_tree, 1, bases, &result)) {
-		repo_rerere(the_repository, state->allow_rerere_autoupdate);
+		rerere_record(the_repository, state->allow_rerere_autoupdate);
 		free(their_tree_name);
 		return error(_("Failed to merge in the changes."));
 	}
@@ -1979,7 +1979,7 @@ static void am_resolve(struct am_state *state, int allow_empty)
 			goto next;
 	}
 
-	repo_rerere(the_repository, 0);
+	rerere_record(the_repository, 0);
 
 	do_commit(state);
 
diff --git a/builtin/commit.c b/builtin/commit.c
index 8ffd42681bb..c472e8f6638 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1962,7 +1962,7 @@ int cmd_commit(int argc,
 
 	git_test_write_commit_graph_or_die(the_repository);
 
-	repo_rerere(the_repository, 0);
+	rerere_record(the_repository, 0);
 	run_auto_maintenance(the_repository, quiet);
 	run_commit_hook(use_editor, repo_get_index_file(the_repository),
 			NULL, "post-commit", NULL);
diff --git a/builtin/merge.c b/builtin/merge.c
index d7bf209114f..aaf15fdd69d 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -1061,7 +1061,7 @@ static int suggest_conflicts(void)
 	fputs(msgbuf.buf, fp);
 	strbuf_release(&msgbuf);
 	fclose(fp);
-	repo_rerere(the_repository, allow_rerere_auto);
+	rerere_record(the_repository, allow_rerere_auto);
 	printf(_("Automatic merge failed; "
 			"fix conflicts and then commit the result.\n"));
 	return 1;
diff --git a/builtin/rerere.c b/builtin/rerere.c
index 2c17f02f56d..1717fb7a65d 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -83,7 +83,7 @@ int cmd_rerere(int argc,
 		flags = RERERE_NOAUTOUPDATE;
 
 	if (argc < 1)
-		return repo_rerere(the_repository, flags);
+		return rerere_record(the_repository, flags);
 
 	if (!strcmp(argv[0], "forget")) {
 		struct pathspec pathspec;
diff --git a/builtin/stash.c b/builtin/stash.c
index e1a3a76bd04..1b9c7f0eb6b 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -688,7 +688,7 @@ static enum stash_apply_result do_apply_stash(const char *prefix,
 		ret = error(_("could not write index"));
 
 	if (ret) {
-		repo_rerere(the_repository, 0);
+		rerere_record(the_repository, 0);
 
 		if (index)
 			fprintf_ln(stderr, _("Index was not unstashed."));
diff --git a/rerere.c b/rerere.c
index e0897612085..fd1c1ed6015 100644
--- a/rerere.c
+++ b/rerere.c
@@ -956,7 +956,7 @@ int rerere_cache_setup(struct rerere_cache *cache,
  * perform mergy operations, possibly leaving conflicted index entries
  * and working tree files.
  */
-int repo_rerere(struct repository *r, int flags)
+int rerere_record(struct repository *r, int flags)
 {
 	struct string_list merge_rr = STRING_LIST_INIT_DUP;
 	struct rerere_cache cache;
diff --git a/rerere.h b/rerere.h
index d3cbd10e13f..a19d2407b59 100644
--- a/rerere.h
+++ b/rerere.h
@@ -45,7 +45,7 @@ int rerere_cache_setup(struct rerere_cache *cache,
 		       int flags);
 void rerere_cache_release(struct rerere_cache *cache);
 
-int repo_rerere(struct repository *, int);
+int rerere_record(struct repository *, int);
 /*
  * Given the conflict ID and the name of a "file" used for replaying
  * the recorded resolution (e.g. "preimage", "postimage"), return the
diff --git a/sequencer.c b/sequencer.c
index 508ec717920..58e58503794 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -2556,7 +2556,7 @@ static enum pick_result do_pick_commit(struct repository *r,
 		      : _("could not apply %s... %s"),
 		      short_commit_name(r, commit), msg.subject);
 		print_advice(r, res == 1, opts);
-		repo_rerere(r, opts->allow_rerere_auto);
+		rerere_record(r, opts->allow_rerere_auto);
 		goto leave;
 	}
 
@@ -4488,7 +4488,7 @@ static int do_merge(struct repository *r,
 
 	rollback_lock_file(&lock);
 	if (ret)
-		repo_rerere(r, opts->allow_rerere_auto);
+		rerere_record(r, opts->allow_rerere_auto);
 	else
 		/*
 		 * In case of problems, we now want to return a positive

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 02/12] rerere: convert flags into an enum
  2026-10-27 10:46 [PATCH 00/12] builtin/rerere: convert to use subcommands Patrick Steinhardt
  2026-10-27 10:46 ` [PATCH 01/12] rerere: rename `repo_rerere()` Patrick Steinhardt
@ 2026-10-27 10:46 ` Patrick Steinhardt
  2026-10-27 14:47   ` Mark C. Chu-Carroll
  2026-10-27 10:46 ` [PATCH 03/12] builtin/rerere: drop `the_repository` Patrick Steinhardt
                   ` (11 subsequent siblings)
  13 siblings, 1 reply; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-27 10:46 UTC (permalink / raw)
  To: git

Our modern coding guidelines say that flags should typically be passed
around as enums so that it becomes trivial to see what kind of flags are
expected. Adapt the rerere flags accordingly.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 rerere.c |  7 ++++---
 rerere.h | 21 ++++++++++++---------
 2 files changed, 16 insertions(+), 12 deletions(-)

diff --git a/rerere.c b/rerere.c
index fd1c1ed6015..1ae7983445a 100644
--- a/rerere.c
+++ b/rerere.c
@@ -908,7 +908,7 @@ static int is_rerere_enabled(struct repository *r)
 int rerere_cache_setup(struct rerere_cache *cache,
 		       struct repository *r,
 		       struct string_list *merge_rr,
-		       int flags)
+		       enum rerere_flags flags)
 {
 	int lock_timeout_ms = 1000;
 	int fd;
@@ -956,7 +956,7 @@ int rerere_cache_setup(struct rerere_cache *cache,
  * perform mergy operations, possibly leaving conflicted index entries
  * and working tree files.
  */
-int rerere_record(struct repository *r, int flags)
+int rerere_record(struct repository *r, enum rerere_flags flags)
 {
 	struct string_list merge_rr = STRING_LIST_INIT_DUP;
 	struct rerere_cache cache;
@@ -1341,7 +1341,8 @@ bool rerere_gc_needed(struct repository *r, size_t limit)
 	return needed;
 }
 
-void rerere_gc(struct repository *r, struct string_list *rr, int flags)
+void rerere_gc(struct repository *r, struct string_list *rr,
+	       enum rerere_flags flags)
 {
 	struct rerere_cache cache;
 	struct string_list to_remove = STRING_LIST_INIT_DUP;
diff --git a/rerere.h b/rerere.h
index a19d2407b59..362b348af25 100644
--- a/rerere.h
+++ b/rerere.h
@@ -9,12 +9,6 @@
 struct pathspec;
 struct repository;
 
-#define RERERE_AUTOUPDATE   01
-#define RERERE_NOAUTOUPDATE 02
-#define RERERE_READONLY     04
-/* If MERGE_RR.lock is taken, return -1 as if rerere were disabled */
-#define RERERE_NOWAIT       010
-
 /*
  * Marks paths that have been hand-resolved and added to the
  * index. Set in the util field of such paths after calling
@@ -39,13 +33,21 @@ struct rerere_id {
 	int variant;
 };
 
+enum rerere_flags {
+	RERERE_AUTOUPDATE = (1 << 0),
+	RERERE_NOAUTOUPDATE = (1 << 1),
+	RERERE_READONLY = (1 << 2),
+	/* If MERGE_RR.lock is taken, return -1 as if rerere were disabled */
+	RERERE_NOWAIT = (1 << 3),
+};
+
 int rerere_cache_setup(struct rerere_cache *cache,
 		       struct repository *r,
 		       struct string_list *merge_rr,
-		       int flags);
+		       enum rerere_flags flags);
 void rerere_cache_release(struct rerere_cache *cache);
 
-int rerere_record(struct repository *, int);
+int rerere_record(struct repository *r, enum rerere_flags flags);
 /*
  * Given the conflict ID and the name of a "file" used for replaying
  * the recorded resolution (e.g. "preimage", "postimage"), return the
@@ -58,7 +60,8 @@ const char *rerere_path(struct repository *,
 int rerere_forget(struct repository *, struct pathspec *);
 int rerere_remaining(struct repository *, struct string_list *);
 void rerere_clear(struct repository *, struct string_list *);
-void rerere_gc(struct repository *, struct string_list *, int);
+void rerere_gc(struct repository *, struct string_list *,
+	       enum rerere_flags flags);
 
 /*
  * Check whether garbage collection for rerere entries is needed, which is

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 03/12] builtin/rerere: drop `the_repository`
  2026-10-27 10:46 [PATCH 00/12] builtin/rerere: convert to use subcommands Patrick Steinhardt
  2026-10-27 10:46 ` [PATCH 01/12] rerere: rename `repo_rerere()` Patrick Steinhardt
  2026-10-27 10:46 ` [PATCH 02/12] rerere: convert flags into an enum Patrick Steinhardt
@ 2026-10-27 10:46 ` Patrick Steinhardt
  2026-10-27 10:46 ` [PATCH 04/12] builtin/rerere: stop passing flags to subcommands that ignore them Patrick Steinhardt
                   ` (10 subsequent siblings)
  13 siblings, 0 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-27 10:46 UTC (permalink / raw)
  To: git

There are a couple of references to `the_repository` in git-rerere(1).
As that command cannot run outside of a repository these references can
be trivially replaced by the repository that's passed in as parameter to
`cmd_rerere()`. We don't have to worry about the repository being `NULL`
when called with "-h", either, as there are no references to the repo
before we call `parse_options()`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rerere.c | 24 +++++++++++-------------
 1 file changed, 11 insertions(+), 13 deletions(-)

diff --git a/builtin/rerere.c b/builtin/rerere.c
index 1717fb7a65d..556f267f37d 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -1,5 +1,3 @@
-#define USE_THE_REPOSITORY_VARIABLE
-
 #include "builtin.h"
 #include "config.h"
 #include "gettext.h"
@@ -57,7 +55,7 @@ static int diff_two(const char *file1, const char *label1,
 int cmd_rerere(int argc,
 	       const char **argv,
 	       const char *prefix,
-	       struct repository *repo UNUSED)
+	       struct repository *repo)
 {
 	struct string_list merge_rr = STRING_LIST_INIT_DUP;
 	int autoupdate = -1, skip_locked = 0, flags = 0;
@@ -75,7 +73,7 @@ int cmd_rerere(int argc,
 	if (skip_locked && (argc < 1 || strcmp(argv[0], "gc")))
 		die(_("the option '%s' requires '%s'"), "--skip-locked", "gc");
 
-	repo_config(the_repository, git_xmerge_config, NULL);
+	repo_config(repo, git_xmerge_config, NULL);
 
 	if (autoupdate == 1)
 		flags = RERERE_AUTOUPDATE;
@@ -83,7 +81,7 @@ int cmd_rerere(int argc,
 		flags = RERERE_NOAUTOUPDATE;
 
 	if (argc < 1)
-		return rerere_record(the_repository, flags);
+		return rerere_record(repo, flags);
 
 	if (!strcmp(argv[0], "forget")) {
 		struct pathspec pathspec;
@@ -94,21 +92,21 @@ int cmd_rerere(int argc,
 		parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD,
 			       prefix, argv + 1);
 
-		ret = rerere_forget(the_repository, &pathspec);
+		ret = rerere_forget(repo, &pathspec);
 
 		clear_pathspec(&pathspec);
 		return ret;
 	}
 
 	if (!strcmp(argv[0], "clear")) {
-		rerere_clear(the_repository, &merge_rr);
+		rerere_clear(repo, &merge_rr);
 	} else if (!strcmp(argv[0], "gc"))
-		rerere_gc(the_repository, &merge_rr,
+		rerere_gc(repo, &merge_rr,
 			  skip_locked ? RERERE_NOWAIT : 0);
 	else if (!strcmp(argv[0], "status")) {
 		struct rerere_cache cache;
 
-		if (rerere_cache_setup(&cache, the_repository, &merge_rr,
+		if (rerere_cache_setup(&cache, repo, &merge_rr,
 				       flags | RERERE_READONLY) < 0)
 			return 0;
 		for (size_t i = 0; i < merge_rr.nr; i++)
@@ -116,7 +114,7 @@ int cmd_rerere(int argc,
 
 		rerere_cache_release(&cache);
 	} else if (!strcmp(argv[0], "remaining")) {
-		rerere_remaining(the_repository, &merge_rr);
+		rerere_remaining(repo, &merge_rr);
 		for (size_t i = 0; i < merge_rr.nr; i++) {
 			if (merge_rr.items[i].util != RERERE_RESOLVED)
 				printf("%s\n", merge_rr.items[i].string);
@@ -129,16 +127,16 @@ int cmd_rerere(int argc,
 		struct strbuf buf = STRBUF_INIT;
 		struct rerere_cache cache;
 
-		if (rerere_cache_setup(&cache, the_repository, &merge_rr,
+		if (rerere_cache_setup(&cache, repo, &merge_rr,
 				      flags | RERERE_READONLY) < 0)
 			return 0;
 		for (size_t i = 0; i < merge_rr.nr; i++) {
 			const char *path = merge_rr.items[i].string;
 			const struct rerere_id *id = merge_rr.items[i].util;
-			if (diff_two(rerere_path(the_repository, &buf, id, "preimage"),
+			if (diff_two(rerere_path(repo, &buf, id, "preimage"),
 				     path, path, path))
 				die(_("unable to generate diff for '%s'"),
-				    rerere_path(the_repository, &buf, id, NULL));
+				    rerere_path(repo, &buf, id, NULL));
 		}
 
 		rerere_cache_release(&cache);

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 04/12] builtin/rerere: stop passing flags to subcommands that ignore them
  2026-10-27 10:46 [PATCH 00/12] builtin/rerere: convert to use subcommands Patrick Steinhardt
                   ` (2 preceding siblings ...)
  2026-10-27 10:46 ` [PATCH 03/12] builtin/rerere: drop `the_repository` Patrick Steinhardt
@ 2026-10-27 10:46 ` Patrick Steinhardt
  2026-10-27 10:46 ` [PATCH 05/12] builtin/rerere: turn "forget" into a proper subcommand Patrick Steinhardt
                   ` (9 subsequent siblings)
  13 siblings, 0 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-27 10:46 UTC (permalink / raw)
  To: git

The git-rerere(1) command has a "--[no-]rerere-autoupdate" flag. If
passed, we explicitly enable or disable recording clean merge
resolutions that are staged in the index. This flag only matters to
git-rerere(1) when not executed with any subcommand, but we also forward
the flag to `git rerere status` and `git rerere diff`.

This is mostly harmless as we would ignore those flags anyway, but it
makes subsequent refactorings harder to understand. Prepare for these
refactorings by not using the flag in those subcommands anymore.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rerere.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/builtin/rerere.c b/builtin/rerere.c
index 556f267f37d..d65870779f3 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -107,7 +107,7 @@ int cmd_rerere(int argc,
 		struct rerere_cache cache;
 
 		if (rerere_cache_setup(&cache, repo, &merge_rr,
-				       flags | RERERE_READONLY) < 0)
+				       RERERE_READONLY) < 0)
 			return 0;
 		for (size_t i = 0; i < merge_rr.nr; i++)
 			printf("%s\n", merge_rr.items[i].string);
@@ -128,7 +128,7 @@ int cmd_rerere(int argc,
 		struct rerere_cache cache;
 
 		if (rerere_cache_setup(&cache, repo, &merge_rr,
-				      flags | RERERE_READONLY) < 0)
+				       RERERE_READONLY) < 0)
 			return 0;
 		for (size_t i = 0; i < merge_rr.nr; i++) {
 			const char *path = merge_rr.items[i].string;

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 05/12] builtin/rerere: turn "forget" into a proper subcommand
  2026-10-27 10:46 [PATCH 00/12] builtin/rerere: convert to use subcommands Patrick Steinhardt
                   ` (3 preceding siblings ...)
  2026-10-27 10:46 ` [PATCH 04/12] builtin/rerere: stop passing flags to subcommands that ignore them Patrick Steinhardt
@ 2026-10-27 10:46 ` Patrick Steinhardt
  2026-10-27 10:46 ` [PATCH 06/12] builtin/rerere: turn "clear" " Patrick Steinhardt
                   ` (8 subsequent siblings)
  13 siblings, 0 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-27 10:46 UTC (permalink / raw)
  To: git

The git-rerere(1) command has its own, home-grown implementation of
subcommands. Of course this implementation is inconsistent with how
subcommands typically work:

  - Options from other subcommands apply to each other.

  - We don't have proper usage strings.

  - Running `git rerere <subcmd> -h` doesn't print the options that are
    supported by that subcommand, but instead it just prints the help of
    git-rerere(1) itself.

A subsequent commit will thus introduce proper subcommands as supported
by our parse-options subsystem. Refactor the "forget" subcommand to use
parse-options as a preparatory step. The other commands will follow in
subsequent commits until all of them have been converted, and then we
switch over to `OPT_SUBCOMMAND()`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rerere.c | 38 ++++++++++++++++++++++++--------------
 1 file changed, 24 insertions(+), 14 deletions(-)

diff --git a/builtin/rerere.c b/builtin/rerere.c
index d65870779f3..0f55619bf62 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -52,6 +52,28 @@ static int diff_two(const char *file1, const char *label1,
 	return ret;
 }
 
+static int cmd_rerere_forget(int argc,
+			     const char **argv,
+			     const char *prefix,
+			     struct repository *repo)
+{
+	struct option options[] = {
+		OPT_END(),
+	};
+	struct pathspec pathspec;
+	int ret;
+
+	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	if (!argc)
+		warning(_("'git rerere forget' without paths is deprecated"));
+	parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD, prefix, argv);
+
+	ret = rerere_forget(repo, &pathspec);
+
+	clear_pathspec(&pathspec);
+	return ret;
+}
+
 int cmd_rerere(int argc,
 	       const char **argv,
 	       const char *prefix,
@@ -83,20 +105,8 @@ int cmd_rerere(int argc,
 	if (argc < 1)
 		return rerere_record(repo, flags);
 
-	if (!strcmp(argv[0], "forget")) {
-		struct pathspec pathspec;
-		int ret;
-
-		if (argc < 2)
-			warning(_("'git rerere forget' without paths is deprecated"));
-		parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD,
-			       prefix, argv + 1);
-
-		ret = rerere_forget(repo, &pathspec);
-
-		clear_pathspec(&pathspec);
-		return ret;
-	}
+	if (!strcmp(argv[0], "forget"))
+		return cmd_rerere_forget(argc, argv, prefix, repo);
 
 	if (!strcmp(argv[0], "clear")) {
 		rerere_clear(repo, &merge_rr);

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 06/12] builtin/rerere: turn "clear" into a proper subcommand
  2026-10-27 10:46 [PATCH 00/12] builtin/rerere: convert to use subcommands Patrick Steinhardt
                   ` (4 preceding siblings ...)
  2026-10-27 10:46 ` [PATCH 05/12] builtin/rerere: turn "forget" into a proper subcommand Patrick Steinhardt
@ 2026-10-27 10:46 ` Patrick Steinhardt
  2026-10-27 10:46 ` [PATCH 07/12] builtin/rerere: turn "status" " Patrick Steinhardt
                   ` (7 subsequent siblings)
  13 siblings, 0 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-27 10:46 UTC (permalink / raw)
  To: git

As explained in a preceding commit, a subsequent commit will introduce
proper subcommands as supported by our parse-options subsystem. Refactor
the "clear" subcommand to use parse-options as a preparatory step.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rerere.c | 25 ++++++++++++++++++++++---
 1 file changed, 22 insertions(+), 3 deletions(-)

diff --git a/builtin/rerere.c b/builtin/rerere.c
index 0f55619bf62..96e98bc411c 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -52,6 +52,26 @@ static int diff_two(const char *file1, const char *label1,
 	return ret;
 }
 
+static int cmd_rerere_clear(int argc,
+			    const char **argv,
+			    const char *prefix,
+			    struct repository *repo)
+{
+	struct option options[] = {
+		OPT_END(),
+	};
+	struct string_list merge_rr = STRING_LIST_INIT_DUP;
+
+	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	if (argc)
+		usage(_("too many arguments"));
+
+	rerere_clear(repo, &merge_rr);
+
+	string_list_clear(&merge_rr, 1);
+	return 0;
+}
+
 static int cmd_rerere_forget(int argc,
 			     const char **argv,
 			     const char *prefix,
@@ -107,9 +127,8 @@ int cmd_rerere(int argc,
 
 	if (!strcmp(argv[0], "forget"))
 		return cmd_rerere_forget(argc, argv, prefix, repo);
-
-	if (!strcmp(argv[0], "clear")) {
-		rerere_clear(repo, &merge_rr);
+	else if (!strcmp(argv[0], "clear")) {
+		return cmd_rerere_clear(argc, argv, prefix, repo);
 	} else if (!strcmp(argv[0], "gc"))
 		rerere_gc(repo, &merge_rr,
 			  skip_locked ? RERERE_NOWAIT : 0);

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 07/12] builtin/rerere: turn "status" into a proper subcommand
  2026-10-27 10:46 [PATCH 00/12] builtin/rerere: convert to use subcommands Patrick Steinhardt
                   ` (5 preceding siblings ...)
  2026-10-27 10:46 ` [PATCH 06/12] builtin/rerere: turn "clear" " Patrick Steinhardt
@ 2026-10-27 10:46 ` Patrick Steinhardt
  2026-10-27 10:46 ` [PATCH 08/12] builtin/rerere: turn "remaining" " Patrick Steinhardt
                   ` (6 subsequent siblings)
  13 siblings, 0 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-27 10:46 UTC (permalink / raw)
  To: git

As explained in a preceding commit, a subsequent commit will introduce
proper subcommands as supported by our parse-options subsystem. Refactor
the "status" subcommand to use parse-options as a preparatory step.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rerere.c | 40 +++++++++++++++++++++++++++++-----------
 1 file changed, 29 insertions(+), 11 deletions(-)

diff --git a/builtin/rerere.c b/builtin/rerere.c
index 96e98bc411c..e2955fea20e 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -94,6 +94,32 @@ static int cmd_rerere_forget(int argc,
 	return ret;
 }
 
+static int cmd_rerere_status(int argc,
+			     const char **argv,
+			     const char *prefix,
+			     struct repository *repo)
+{
+	struct option options[] = {
+		OPT_END(),
+	};
+	struct string_list merge_rr = STRING_LIST_INIT_DUP;
+	struct rerere_cache cache;
+
+	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	if (argc)
+		usage(_("too many arguments"));
+
+	if (rerere_cache_setup(&cache, repo, &merge_rr,
+			       RERERE_READONLY) < 0)
+		return 0;
+	for (size_t i = 0; i < merge_rr.nr; i++)
+		printf("%s\n", merge_rr.items[i].string);
+
+	string_list_clear(&merge_rr, 1);
+	rerere_cache_release(&cache);
+	return 0;
+}
+
 int cmd_rerere(int argc,
 	       const char **argv,
 	       const char *prefix,
@@ -132,17 +158,9 @@ int cmd_rerere(int argc,
 	} else if (!strcmp(argv[0], "gc"))
 		rerere_gc(repo, &merge_rr,
 			  skip_locked ? RERERE_NOWAIT : 0);
-	else if (!strcmp(argv[0], "status")) {
-		struct rerere_cache cache;
-
-		if (rerere_cache_setup(&cache, repo, &merge_rr,
-				       RERERE_READONLY) < 0)
-			return 0;
-		for (size_t i = 0; i < merge_rr.nr; i++)
-			printf("%s\n", merge_rr.items[i].string);
-
-		rerere_cache_release(&cache);
-	} else if (!strcmp(argv[0], "remaining")) {
+	else if (!strcmp(argv[0], "status"))
+		return cmd_rerere_status(argc, argv, prefix, repo);
+	else if (!strcmp(argv[0], "remaining")) {
 		rerere_remaining(repo, &merge_rr);
 		for (size_t i = 0; i < merge_rr.nr; i++) {
 			if (merge_rr.items[i].util != RERERE_RESOLVED)

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 08/12] builtin/rerere: turn "remaining" into a proper subcommand
  2026-10-27 10:46 [PATCH 00/12] builtin/rerere: convert to use subcommands Patrick Steinhardt
                   ` (6 preceding siblings ...)
  2026-10-27 10:46 ` [PATCH 07/12] builtin/rerere: turn "status" " Patrick Steinhardt
@ 2026-10-27 10:46 ` Patrick Steinhardt
  2026-10-27 10:46 ` [PATCH 09/12] builtin/rerere: turn "diff" " Patrick Steinhardt
                   ` (5 subsequent siblings)
  13 siblings, 0 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-27 10:46 UTC (permalink / raw)
  To: git

As explained in a preceding commit, a subsequent commit will introduce
proper subcommands as supported by our parse-options subsystem. Refactor
the "remaining" subcommand to use parse-options as a preparatory step.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rerere.c | 42 +++++++++++++++++++++++++++++++-----------
 1 file changed, 31 insertions(+), 11 deletions(-)

diff --git a/builtin/rerere.c b/builtin/rerere.c
index e2955fea20e..31c1b37540e 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -94,6 +94,34 @@ static int cmd_rerere_forget(int argc,
 	return ret;
 }
 
+static int cmd_rerere_remaining(int argc,
+				const char **argv,
+				const char *prefix,
+				struct repository *repo)
+{
+	struct option options[] = {
+		OPT_END(),
+	};
+	struct string_list merge_rr = STRING_LIST_INIT_DUP;
+
+	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	if (argc)
+		usage(_("too many arguments"));
+
+	rerere_remaining(repo, &merge_rr);
+	for (size_t i = 0; i < merge_rr.nr; i++) {
+		if (merge_rr.items[i].util != RERERE_RESOLVED)
+			printf("%s\n", merge_rr.items[i].string);
+		else
+			/* prepare for later call to
+			 * string_list_clear() */
+			merge_rr.items[i].util = NULL;
+	}
+
+	string_list_clear(&merge_rr, 1);
+	return 0;
+}
+
 static int cmd_rerere_status(int argc,
 			     const char **argv,
 			     const char *prefix,
@@ -160,17 +188,9 @@ int cmd_rerere(int argc,
 			  skip_locked ? RERERE_NOWAIT : 0);
 	else if (!strcmp(argv[0], "status"))
 		return cmd_rerere_status(argc, argv, prefix, repo);
-	else if (!strcmp(argv[0], "remaining")) {
-		rerere_remaining(repo, &merge_rr);
-		for (size_t i = 0; i < merge_rr.nr; i++) {
-			if (merge_rr.items[i].util != RERERE_RESOLVED)
-				printf("%s\n", merge_rr.items[i].string);
-			else
-				/* prepare for later call to
-				 * string_list_clear() */
-				merge_rr.items[i].util = NULL;
-		}
-	} else if (!strcmp(argv[0], "diff")) {
+	else if (!strcmp(argv[0], "remaining"))
+		return cmd_rerere_remaining(argc, argv, prefix, repo);
+	else if (!strcmp(argv[0], "diff")) {
 		struct strbuf buf = STRBUF_INIT;
 		struct rerere_cache cache;
 

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 09/12] builtin/rerere: turn "diff" into a proper subcommand
  2026-10-27 10:46 [PATCH 00/12] builtin/rerere: convert to use subcommands Patrick Steinhardt
                   ` (7 preceding siblings ...)
  2026-10-27 10:46 ` [PATCH 08/12] builtin/rerere: turn "remaining" " Patrick Steinhardt
@ 2026-10-27 10:46 ` Patrick Steinhardt
  2026-10-27 10:46 ` [PATCH 10/12] builtin/rerere: turn "gc" " Patrick Steinhardt
                   ` (4 subsequent siblings)
  13 siblings, 0 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-27 10:46 UTC (permalink / raw)
  To: git

As explained in a preceding commit, a subsequent commit will introduce
proper subcommands as supported by our parse-options subsystem. Refactor
the "diff" subcommand to use parse-options as a preparatory step.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rerere.c | 56 +++++++++++++++++++++++++++++++++++++-------------------
 1 file changed, 37 insertions(+), 19 deletions(-)

diff --git a/builtin/rerere.c b/builtin/rerere.c
index 31c1b37540e..87ee4eddd0a 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -72,6 +72,40 @@ static int cmd_rerere_clear(int argc,
 	return 0;
 }
 
+static int cmd_rerere_diff(int argc,
+			   const char **argv,
+			   const char *prefix,
+			   struct repository *repo)
+{
+	struct option options[] = {
+		OPT_END(),
+	};
+	struct string_list merge_rr = STRING_LIST_INIT_DUP;
+	struct strbuf buf = STRBUF_INIT;
+	struct rerere_cache cache;
+
+	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	if (argc)
+		usage(_("too many arguments"));
+
+	if (rerere_cache_setup(&cache, repo, &merge_rr,
+			       RERERE_READONLY) < 0)
+		return 0;
+
+	for (size_t i = 0; i < merge_rr.nr; i++) {
+		const char *path = merge_rr.items[i].string;
+		const struct rerere_id *id = merge_rr.items[i].util;
+		if (diff_two(rerere_path(repo, &buf, id, "preimage"), path, path, path))
+			die(_("unable to generate diff for '%s'"),
+			    rerere_path(repo, &buf, id, NULL));
+	}
+
+	string_list_clear(&merge_rr, 1);
+	rerere_cache_release(&cache);
+	strbuf_release(&buf);
+	return 0;
+}
+
 static int cmd_rerere_forget(int argc,
 			     const char **argv,
 			     const char *prefix,
@@ -190,25 +224,9 @@ int cmd_rerere(int argc,
 		return cmd_rerere_status(argc, argv, prefix, repo);
 	else if (!strcmp(argv[0], "remaining"))
 		return cmd_rerere_remaining(argc, argv, prefix, repo);
-	else if (!strcmp(argv[0], "diff")) {
-		struct strbuf buf = STRBUF_INIT;
-		struct rerere_cache cache;
-
-		if (rerere_cache_setup(&cache, repo, &merge_rr,
-				       RERERE_READONLY) < 0)
-			return 0;
-		for (size_t i = 0; i < merge_rr.nr; i++) {
-			const char *path = merge_rr.items[i].string;
-			const struct rerere_id *id = merge_rr.items[i].util;
-			if (diff_two(rerere_path(repo, &buf, id, "preimage"),
-				     path, path, path))
-				die(_("unable to generate diff for '%s'"),
-				    rerere_path(repo, &buf, id, NULL));
-		}
-
-		rerere_cache_release(&cache);
-		strbuf_release(&buf);
-	} else
+	else if (!strcmp(argv[0], "diff"))
+		return cmd_rerere_diff(argc, argv, prefix, repo);
+	else
 		usage_with_options(rerere_usage, options);
 
 	string_list_clear(&merge_rr, 1);

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 10/12] builtin/rerere: turn "gc" into a proper subcommand
  2026-10-27 10:46 [PATCH 00/12] builtin/rerere: convert to use subcommands Patrick Steinhardt
                   ` (8 preceding siblings ...)
  2026-10-27 10:46 ` [PATCH 09/12] builtin/rerere: turn "diff" " Patrick Steinhardt
@ 2026-10-27 10:46 ` Patrick Steinhardt
  2026-10-27 10:46 ` [PATCH 11/12] builtin/rerere: use `OPT_SUBCOMMAND()` Patrick Steinhardt
                   ` (3 subsequent siblings)
  13 siblings, 0 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-27 10:46 UTC (permalink / raw)
  To: git

As explained in a preceding commit, a subsequent commit will introduce
proper subcommands as supported by our parse-options subsystem. Refactor
the "gc" subcommand to use parse-options as a preparatory step.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rerere.c  | 43 ++++++++++++++++++++++++++++++-------------
 t/t4200-rerere.sh |  5 -----
 2 files changed, 30 insertions(+), 18 deletions(-)

diff --git a/builtin/rerere.c b/builtin/rerere.c
index 87ee4eddd0a..67626e13845 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -128,6 +128,30 @@ static int cmd_rerere_forget(int argc,
 	return ret;
 }
 
+static int cmd_rerere_gc(int argc,
+			 const char **argv,
+			 const char *prefix,
+			 struct repository *repo)
+{
+	int skip_locked = 0;
+	struct option options[] = {
+		OPT_HIDDEN_BOOL(0, "skip-locked", &skip_locked,
+				N_("skip gc while another process holds the lock")),
+		OPT_END(),
+	};
+	struct string_list merge_rr = STRING_LIST_INIT_DUP;
+
+	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	if (argc)
+		usage(_("too many arguments"));
+
+	rerere_gc(repo, &merge_rr,
+		  skip_locked ? RERERE_NOWAIT : 0);
+
+	string_list_clear(&merge_rr, 1);
+	return 0;
+}
+
 static int cmd_rerere_remaining(int argc,
 				const char **argv,
 				const char *prefix,
@@ -187,21 +211,16 @@ int cmd_rerere(int argc,
 	       const char *prefix,
 	       struct repository *repo)
 {
-	struct string_list merge_rr = STRING_LIST_INIT_DUP;
-	int autoupdate = -1, skip_locked = 0, flags = 0;
+	int autoupdate = -1, flags = 0;
 
 	struct option options[] = {
 		OPT_SET_INT(0, "rerere-autoupdate", &autoupdate,
 			N_("register clean resolutions in index"), 1),
-		OPT_HIDDEN_BOOL(0, "skip-locked", &skip_locked,
-			N_("skip gc while another process holds the lock")),
 		OPT_END(),
 	};
 
-	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
-
-	if (skip_locked && (argc < 1 || strcmp(argv[0], "gc")))
-		die(_("the option '%s' requires '%s'"), "--skip-locked", "gc");
+	argc = parse_options(argc, argv, prefix, options, rerere_usage,
+			     PARSE_OPT_KEEP_UNKNOWN_OPT);
 
 	repo_config(repo, git_xmerge_config, NULL);
 
@@ -215,11 +234,10 @@ int cmd_rerere(int argc,
 
 	if (!strcmp(argv[0], "forget"))
 		return cmd_rerere_forget(argc, argv, prefix, repo);
-	else if (!strcmp(argv[0], "clear")) {
+	else if (!strcmp(argv[0], "clear"))
 		return cmd_rerere_clear(argc, argv, prefix, repo);
-	} else if (!strcmp(argv[0], "gc"))
-		rerere_gc(repo, &merge_rr,
-			  skip_locked ? RERERE_NOWAIT : 0);
+	else if (!strcmp(argv[0], "gc"))
+		return cmd_rerere_gc(argc, argv, prefix, repo);
 	else if (!strcmp(argv[0], "status"))
 		return cmd_rerere_status(argc, argv, prefix, repo);
 	else if (!strcmp(argv[0], "remaining"))
@@ -229,6 +247,5 @@ int cmd_rerere(int argc,
 	else
 		usage_with_options(rerere_usage, options);
 
-	string_list_clear(&merge_rr, 1);
 	return 0;
 }
diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh
index ab412ecc6e8..0d4cbf5114a 100755
--- a/t/t4200-rerere.sh
+++ b/t/t4200-rerere.sh
@@ -263,11 +263,6 @@ test_expect_success 'gc --skip-locked prunes while MERGE_RR is not locked' '
 	test_path_is_missing $rr2/preimage
 '
 
-test_expect_success '--skip-locked is only accepted by gc' '
-	test_must_fail git rerere --skip-locked clear 2>err &&
-	test_grep "option .--skip-locked. requires .gc." err
-'
-
 test_expect_success 'a held lock is waited out within rerere.lockTimeout' '
 	git reset --hard &&
 	rm -rf $rr &&

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 11/12] builtin/rerere: use `OPT_SUBCOMMAND()`
  2026-10-27 10:46 [PATCH 00/12] builtin/rerere: convert to use subcommands Patrick Steinhardt
                   ` (9 preceding siblings ...)
  2026-10-27 10:46 ` [PATCH 10/12] builtin/rerere: turn "gc" " Patrick Steinhardt
@ 2026-10-27 10:46 ` Patrick Steinhardt
  2026-10-27 10:46 ` [PATCH 12/12] builtin/rerere: introduce proper usage strings for subcommands Patrick Steinhardt
                   ` (2 subsequent siblings)
  13 siblings, 0 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-27 10:46 UTC (permalink / raw)
  To: git

In git-rerere(1) we have our own custom logic to parse subcommands. In
preceding commits we have refactored all of those subcommands so that
they are ready to be used as `OPT_SUBCOMMAND()`s. Switch over to use
these so that we get rid of our custom subcommand parsing.

Unfortunately this means that we have a backwards-incompatible change
because we no longer accept the "--[no-]rerere-autoupdate" flag for all
subcommands. But this was arguably broken and misleading as we ignore
this flag for any other subcommand aynway. So we cheat a bit and simply
call it a bug that those flags were accepted in the first place.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rerere.c | 84 +++++++++++++++++++++++++++++++++++++-------------------
 1 file changed, 56 insertions(+), 28 deletions(-)

diff --git a/builtin/rerere.c b/builtin/rerere.c
index 67626e13845..80ecca1fe0d 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -5,6 +5,7 @@
 #include "rerere.h"
 #include "strbuf.h"
 #include "string-list.h"
+#include "strvec.h"
 #include "xdiff/xdiff.h"
 #include "xdiff-interface.h"
 #include "pathspec.h"
@@ -152,6 +153,31 @@ static int cmd_rerere_gc(int argc,
 	return 0;
 }
 
+static int cmd_rerere_record(int argc,
+			     const char **argv,
+			     const char *prefix,
+			     struct repository *repo)
+{
+	int autoupdate = -1;
+	struct option options[] = {
+		OPT_SET_INT(0, "rerere-autoupdate", &autoupdate,
+			N_("register clean resolutions in index"), 1),
+		OPT_END(),
+	};
+	enum rerere_flags flags = 0;
+
+	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	if (argc)
+		usage(_("too many arguments"));
+
+	if (autoupdate == 1)
+		flags = RERERE_AUTOUPDATE;
+	if (autoupdate == 0)
+		flags = RERERE_NOAUTOUPDATE;
+
+	return rerere_record(repo, flags);
+}
+
 static int cmd_rerere_remaining(int argc,
 				const char **argv,
 				const char *prefix,
@@ -211,41 +237,43 @@ int cmd_rerere(int argc,
 	       const char *prefix,
 	       struct repository *repo)
 {
-	int autoupdate = -1, flags = 0;
-
+	parse_opt_subcommand_fn *fn = NULL;
 	struct option options[] = {
-		OPT_SET_INT(0, "rerere-autoupdate", &autoupdate,
-			N_("register clean resolutions in index"), 1),
+		OPT_SUBCOMMAND("clear", &fn, cmd_rerere_clear),
+		OPT_SUBCOMMAND("diff", &fn, cmd_rerere_diff),
+		OPT_SUBCOMMAND("forget", &fn, cmd_rerere_forget),
+		OPT_SUBCOMMAND("gc", &fn, cmd_rerere_gc),
+		OPT_SUBCOMMAND("remaining", &fn, cmd_rerere_remaining),
+		OPT_SUBCOMMAND("status", &fn, cmd_rerere_status),
 		OPT_END(),
 	};
 
 	argc = parse_options(argc, argv, prefix, options, rerere_usage,
-			     PARSE_OPT_KEEP_UNKNOWN_OPT);
-
+			     PARSE_OPT_KEEP_UNKNOWN_OPT | PARSE_OPT_SUBCOMMAND_OPTIONAL);
 	repo_config(repo, git_xmerge_config, NULL);
 
-	if (autoupdate == 1)
-		flags = RERERE_AUTOUPDATE;
-	if (autoupdate == 0)
-		flags = RERERE_NOAUTOUPDATE;
+	if (!fn) {
+		struct strvec args = STRVEC_INIT;
+		const char **args_copy;
+		int ret;
 
-	if (argc < 1)
-		return rerere_record(repo, flags);
-
-	if (!strcmp(argv[0], "forget"))
-		return cmd_rerere_forget(argc, argv, prefix, repo);
-	else if (!strcmp(argv[0], "clear"))
-		return cmd_rerere_clear(argc, argv, prefix, repo);
-	else if (!strcmp(argv[0], "gc"))
-		return cmd_rerere_gc(argc, argv, prefix, repo);
-	else if (!strcmp(argv[0], "status"))
-		return cmd_rerere_status(argc, argv, prefix, repo);
-	else if (!strcmp(argv[0], "remaining"))
-		return cmd_rerere_remaining(argc, argv, prefix, repo);
-	else if (!strcmp(argv[0], "diff"))
-		return cmd_rerere_diff(argc, argv, prefix, repo);
-	else
-		usage_with_options(rerere_usage, options);
+		strvec_push(&args, "record");
+		strvec_pushv(&args, argv);
 
-	return 0;
+		/*
+		 * We need to copy the array as it will be modified by
+		 * `parse_options()`, and if so we leak the strings
+		 * referenced by `args`.
+		 */
+		CALLOC_ARRAY(args_copy, args.nr);
+		COPY_ARRAY(args_copy, args.v, args.nr);
+
+		ret = cmd_rerere_record(args.nr, args_copy, prefix, repo);
+
+		strvec_clear(&args);
+		free(args_copy);
+		return ret;
+	}
+
+	return fn(argc, argv, prefix, repo);
 }

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH 12/12] builtin/rerere: introduce proper usage strings for subcommands
  2026-10-27 10:46 [PATCH 00/12] builtin/rerere: convert to use subcommands Patrick Steinhardt
                   ` (10 preceding siblings ...)
  2026-10-27 10:46 ` [PATCH 11/12] builtin/rerere: use `OPT_SUBCOMMAND()` Patrick Steinhardt
@ 2026-10-27 10:46 ` Patrick Steinhardt
  2026-10-27 19:52 ` [PATCH 00/12] builtin/rerere: convert to use subcommands Junio C Hamano
  2026-10-29  9:57 ` [PATCH v2 " Patrick Steinhardt
  13 siblings, 0 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-27 10:46 UTC (permalink / raw)
  To: git

Adapt git-rerere(1) to have proper usage strings for each of the
subcommands.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/git-rerere.adoc |  8 +++++-
 builtin/rerere.c              | 61 ++++++++++++++++++++++++++++++++++++-------
 2 files changed, 58 insertions(+), 11 deletions(-)

diff --git a/Documentation/git-rerere.adoc b/Documentation/git-rerere.adoc
index 4e6ab9a27c9..e04e9ef3ef8 100644
--- a/Documentation/git-rerere.adoc
+++ b/Documentation/git-rerere.adoc
@@ -8,7 +8,13 @@ git-rerere - Reuse recorded resolution of conflicted merges
 SYNOPSIS
 --------
 [verse]
-'git rerere' [clear | forget <pathspec>... | diff | status | remaining | gc]
+'git rerere' [--[no-]rerere-autoupdate]
+'git rerere' clear
+'git rerere' diff
+'git rerere' forget <pathspec>...
+'git rerere' gc
+'git rerere' remaining
+'git rerere' status
 
 DESCRIPTION
 -----------
diff --git a/builtin/rerere.c b/builtin/rerere.c
index 80ecca1fe0d..97a08ecd6e7 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -10,10 +10,13 @@
 #include "xdiff-interface.h"
 #include "pathspec.h"
 
-static const char * const rerere_usage[] = {
-	N_("git rerere [clear | forget <pathspec>... | diff | status | remaining | gc]"),
-	NULL,
-};
+#define RERERE_USAGE N_("git rerere [--[no-]rerere-autoupdate]")
+#define RERERE_CLEAR_USAGE N_("git rerere clear")
+#define RERERE_DIFF_USAGE N_("git rerere diff")
+#define RERERE_FORGET_USAGE N_("git rerere forget <pathspec>...")
+#define RERERE_GC_USAGE N_("git rerere gc")
+#define RERERE_REMAINING_USAGE N_("git rerere remaining")
+#define RERERE_STATUS_USAGE N_("git rerere status")
 
 static int outf(void *dummy UNUSED, mmfile_t *ptr, int nbuf)
 {
@@ -61,9 +64,13 @@ static int cmd_rerere_clear(int argc,
 	struct option options[] = {
 		OPT_END(),
 	};
+	const char * const rerere_clear_usage[] = {
+		RERERE_CLEAR_USAGE,
+		NULL,
+	};
 	struct string_list merge_rr = STRING_LIST_INIT_DUP;
 
-	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	argc = parse_options(argc, argv, prefix, options, rerere_clear_usage, 0);
 	if (argc)
 		usage(_("too many arguments"));
 
@@ -81,11 +88,15 @@ static int cmd_rerere_diff(int argc,
 	struct option options[] = {
 		OPT_END(),
 	};
+	const char * const rerere_diff_usage[] = {
+		RERERE_DIFF_USAGE,
+		NULL,
+	};
 	struct string_list merge_rr = STRING_LIST_INIT_DUP;
 	struct strbuf buf = STRBUF_INIT;
 	struct rerere_cache cache;
 
-	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	argc = parse_options(argc, argv, prefix, options, rerere_diff_usage, 0);
 	if (argc)
 		usage(_("too many arguments"));
 
@@ -115,10 +126,14 @@ static int cmd_rerere_forget(int argc,
 	struct option options[] = {
 		OPT_END(),
 	};
+	const char * const rerere_forget_usage[] = {
+		RERERE_FORGET_USAGE,
+		NULL,
+	};
 	struct pathspec pathspec;
 	int ret;
 
-	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	argc = parse_options(argc, argv, prefix, options, rerere_forget_usage, 0);
 	if (!argc)
 		warning(_("'git rerere forget' without paths is deprecated"));
 	parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD, prefix, argv);
@@ -140,9 +155,13 @@ static int cmd_rerere_gc(int argc,
 				N_("skip gc while another process holds the lock")),
 		OPT_END(),
 	};
+	const char * const rerere_gc_usage[] = {
+		RERERE_GC_USAGE,
+		NULL,
+	};
 	struct string_list merge_rr = STRING_LIST_INIT_DUP;
 
-	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	argc = parse_options(argc, argv, prefix, options, rerere_gc_usage, 0);
 	if (argc)
 		usage(_("too many arguments"));
 
@@ -164,6 +183,10 @@ static int cmd_rerere_record(int argc,
 			N_("register clean resolutions in index"), 1),
 		OPT_END(),
 	};
+	const char * const rerere_usage[] = {
+		RERERE_USAGE,
+		NULL,
+	};
 	enum rerere_flags flags = 0;
 
 	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
@@ -186,9 +209,13 @@ static int cmd_rerere_remaining(int argc,
 	struct option options[] = {
 		OPT_END(),
 	};
+	const char * const rerere_remaining_usage[] = {
+		RERERE_REMAINING_USAGE,
+		NULL,
+	};
 	struct string_list merge_rr = STRING_LIST_INIT_DUP;
 
-	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	argc = parse_options(argc, argv, prefix, options, rerere_remaining_usage, 0);
 	if (argc)
 		usage(_("too many arguments"));
 
@@ -214,10 +241,14 @@ static int cmd_rerere_status(int argc,
 	struct option options[] = {
 		OPT_END(),
 	};
+	const char * const rerere_status_usage[] = {
+		RERERE_STATUS_USAGE,
+		NULL,
+	};
 	struct string_list merge_rr = STRING_LIST_INIT_DUP;
 	struct rerere_cache cache;
 
-	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	argc = parse_options(argc, argv, prefix, options, rerere_status_usage, 0);
 	if (argc)
 		usage(_("too many arguments"));
 
@@ -247,6 +278,16 @@ int cmd_rerere(int argc,
 		OPT_SUBCOMMAND("status", &fn, cmd_rerere_status),
 		OPT_END(),
 	};
+	const char * const rerere_usage[] = {
+		RERERE_USAGE,
+		RERERE_CLEAR_USAGE,
+		RERERE_DIFF_USAGE,
+		RERERE_FORGET_USAGE,
+		RERERE_GC_USAGE,
+		RERERE_REMAINING_USAGE,
+		RERERE_STATUS_USAGE,
+		NULL,
+	};
 
 	argc = parse_options(argc, argv, prefix, options, rerere_usage,
 			     PARSE_OPT_KEEP_UNKNOWN_OPT | PARSE_OPT_SUBCOMMAND_OPTIONAL);

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* Re: [PATCH 02/12] rerere: convert flags into an enum
  2026-10-27 10:46 ` [PATCH 02/12] rerere: convert flags into an enum Patrick Steinhardt
@ 2026-10-27 14:47   ` Mark C. Chu-Carroll
  2026-10-27 19:40     ` Junio C Hamano
  0 siblings, 1 reply; 35+ messages in thread
From: Mark C. Chu-Carroll @ 2026-10-27 14:47 UTC (permalink / raw)
  To: Patrick Steinhardt, git


On Tue, 27 Oct 2026 11:46:45 +0100, Patrick Steinhardt <ps@pks.im> wrote:
> Our modern coding guidelines say that flags should typically be passed
> around as enums so that it becomes trivial to see what kind of flags are
> expected. Adapt the rerere flags accordingly.
> 
>   <snip>
> +enum rerere_flags {
> +	RERERE_AUTOUPDATE = (1 << 0),
> +	RERERE_NOAUTOUPDATE = (1 << 1),
> +	RERERE_READONLY = (1 << 2),
> +	/* If MERGE_RR.lock is taken, return -1 as if rerere were disabled */
> +	RERERE_NOWAIT = (1 << 3),
> +};

If we're moving to using enum flags, then wouldn't it be better to
explicitly add a "RERE_MERGE_LOCK_TOKEN" to the enum for that case, 
rather than have this value only documented by a comment?

  -Mark

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 02/12] rerere: convert flags into an enum
  2026-10-27 14:47   ` Mark C. Chu-Carroll
@ 2026-10-27 19:40     ` Junio C Hamano
  2026-10-28  8:33       ` Patrick Steinhardt
  0 siblings, 1 reply; 35+ messages in thread
From: Junio C Hamano @ 2026-10-27 19:40 UTC (permalink / raw)
  To: Mark C. Chu-Carroll; +Cc: Patrick Steinhardt, git

"Mark C. Chu-Carroll" <markchucarroll@fastmail.com> writes:

> On Tue, 27 Oct 2026 11:46:45 +0100, Patrick Steinhardt <ps@pks.im> wrote:
>> Our modern coding guidelines say that flags should typically be passed
>> around as enums so that it becomes trivial to see what kind of flags are
>> expected. Adapt the rerere flags accordingly.
>> 
>>   <snip>
>> +enum rerere_flags {
>> +	RERERE_AUTOUPDATE = (1 << 0),
>> +	RERERE_NOAUTOUPDATE = (1 << 1),
>> +	RERERE_READONLY = (1 << 2),
>> +	/* If MERGE_RR.lock is taken, return -1 as if rerere were disabled */
>> +	RERERE_NOWAIT = (1 << 3),
>> +};
>
> If we're moving to using enum flags, then wouldn't it be better to
> explicitly add a "RERE_MERGE_LOCK_TOKEN" to the enum for that case, 
> rather than have this value only documented by a comment?

Perhaps you misunderstand what the comment says.  It is an
explanation for the next line, i.e., it tells the API users "If you
want the 'immediate failure when locked' behaviour, pass the _NOWAIT
bit".

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 01/12] rerere: rename `repo_rerere()`
  2026-10-27 10:46 ` [PATCH 01/12] rerere: rename `repo_rerere()` Patrick Steinhardt
@ 2026-10-27 19:48   ` Junio C Hamano
  2026-10-28  8:33     ` Patrick Steinhardt
  0 siblings, 1 reply; 35+ messages in thread
From: Junio C Hamano @ 2026-10-27 19:48 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git

Patrick Steinhardt <ps@pks.im> writes:

> The `repo_rerere()` function is the only function in "rerere.h" that
> does not have a `rerere_` prefix. And while it does indeed receive a
> repository as first parameter, that's not a good-enough reason to give
> it a `repo_` prefix.
>
> Rename the function to `rerere_record()` to be consistent with all the
> other functions.

I do not think the convention to give "repo_" prefix to variants of
functions that worked only on the_repository should stay with us
forever, and losing repo prefix from this function would be an
excellent first move towards that future.

I howevr take isseus with the verb "record" in its new name.  What
the function does is not just record.  It autonomously finds out
what situation it is in and does the logical next thing, which
varies.  It resolves when a recorded resolution exist.  it records
when seeing a particular conflict for the first time.  There was no
single good verb to express that.  Perhaps "to rerere" or "to
perform rerere" can be invented as a verb to describe such an
intelligent operation?



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 00/12] builtin/rerere: convert to use subcommands
  2026-10-27 10:46 [PATCH 00/12] builtin/rerere: convert to use subcommands Patrick Steinhardt
                   ` (11 preceding siblings ...)
  2026-10-27 10:46 ` [PATCH 12/12] builtin/rerere: introduce proper usage strings for subcommands Patrick Steinhardt
@ 2026-10-27 19:52 ` Junio C Hamano
  2026-10-29  9:57 ` [PATCH v2 " Patrick Steinhardt
  13 siblings, 0 replies; 35+ messages in thread
From: Junio C Hamano @ 2026-10-27 19:52 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git

Patrick Steinhardt <ps@pks.im> writes:

> while reviewing some patch series around git-rerere(1) recently I was a
> bit appalled by our custom subcommand parsing that we do here. This
> patch series here thus cleans up and modernizes that part a bit so that
> git-rerere(1) behaves like every other Git command that happens to have
> subcommands.
>
> This series is based on 37b3475fb53 (The 9th batch, 2026-10-25) with
> ps/rerere-remove-global-state at b948c10c8b3 (rerere: get rid of global
> rerere cache state, 2026-10-23) merged into it.

As the inventor of the mechanism, I took special interest in the
topic, and overall found them well done.  I wasn't even aware that
we were so close to get rid of USE_THE_REPOSITORY_VARIABLE in
builtin/rerere.c with such a small change (even though as I've said
I do not think it is high priority to make cmd_foo() not rely on
the_repository).  Very nicely done.

Thanks.  

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 01/12] rerere: rename `repo_rerere()`
  2026-10-27 19:48   ` Junio C Hamano
@ 2026-10-28  8:33     ` Patrick Steinhardt
  2026-10-28 16:25       ` Junio C Hamano
  0 siblings, 1 reply; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-28  8:33 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Tue, Oct 27, 2026 at 12:48:36PM -0700, Junio C Hamano wrote:
> Patrick Steinhardt <ps@pks.im> writes:
> 
> > The `repo_rerere()` function is the only function in "rerere.h" that
> > does not have a `rerere_` prefix. And while it does indeed receive a
> > repository as first parameter, that's not a good-enough reason to give
> > it a `repo_` prefix.
> >
> > Rename the function to `rerere_record()` to be consistent with all the
> > other functions.
> 
> I do not think the convention to give "repo_" prefix to variants of
> functions that worked only on the_repository should stay with us
> forever, and losing repo prefix from this function would be an
> excellent first move towards that future.
> 
> I howevr take isseus with the verb "record" in its new name.  What
> the function does is not just record.  It autonomously finds out
> what situation it is in and does the logical next thing, which
> varies.  It resolves when a recorded resolution exist.  it records
> when seeing a particular conflict for the first time.  There was no
> single good verb to express that.  Perhaps "to rerere" or "to
> perform rerere" can be invented as a verb to describe such an
> intelligent operation?

I was picking "record" because that's also what git-rerere(1) says:

  This command assists the developer in this process by recording
  conflicted automerge results and corresponding hand resolve results on
  the initial manual merge, and applying previously recorded hand
  resolutions to their corresponding automerge results.

But I guess your point is valid, as we don't only do that, but also
update merge conflicts locally. I'd pick your "perform" proposal in that
case and rename it to `rerere_perform()` instead. That name is somewhat
vague, but I guess that cannot be helped because it actually does a
bunch of things at once.

I guess that can be helped with documentation though.

Patrick

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 02/12] rerere: convert flags into an enum
  2026-10-27 19:40     ` Junio C Hamano
@ 2026-10-28  8:33       ` Patrick Steinhardt
  2026-10-28 14:11         ` Mark C. Chu-Carroll
  2026-10-28 16:25         ` Junio C Hamano
  0 siblings, 2 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-28  8:33 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Mark C. Chu-Carroll, git

On Tue, Oct 27, 2026 at 12:40:45PM -0700, Junio C Hamano wrote:
> "Mark C. Chu-Carroll" <markchucarroll@fastmail.com> writes:
> 
> > On Tue, 27 Oct 2026 11:46:45 +0100, Patrick Steinhardt <ps@pks.im> wrote:
> >> Our modern coding guidelines say that flags should typically be passed
> >> around as enums so that it becomes trivial to see what kind of flags are
> >> expected. Adapt the rerere flags accordingly.
> >> 
> >>   <snip>
> >> +enum rerere_flags {
> >> +	RERERE_AUTOUPDATE = (1 << 0),
> >> +	RERERE_NOAUTOUPDATE = (1 << 1),
> >> +	RERERE_READONLY = (1 << 2),
> >> +	/* If MERGE_RR.lock is taken, return -1 as if rerere were disabled */
> >> +	RERERE_NOWAIT = (1 << 3),
> >> +};
> >
> > If we're moving to using enum flags, then wouldn't it be better to
> > explicitly add a "RERE_MERGE_LOCK_TOKEN" to the enum for that case, 
> > rather than have this value only documented by a comment?
> 
> Perhaps you misunderstand what the comment says.  It is an
> explanation for the next line, i.e., it tells the API users "If you
> want the 'immediate failure when locked' behaviour, pass the _NOWAIT
> bit".

Yeah, this. I guess we can help that though by documenting all of the
flags while at it.

Patrick

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 02/12] rerere: convert flags into an enum
  2026-10-28  8:33       ` Patrick Steinhardt
@ 2026-10-28 14:11         ` Mark C. Chu-Carroll
  2026-10-28 16:25         ` Junio C Hamano
  1 sibling, 0 replies; 35+ messages in thread
From: Mark C. Chu-Carroll @ 2026-10-28 14:11 UTC (permalink / raw)
  To: Patrick Steinhardt, Junio C Hamano; +Cc: git


On Wed, 28 Oct 2026 09:33:25 +0100, Patrick Steinhardt <ps@pks.im> wrote:
> On Tue, Oct 27, 2026 at 12:40:45PM -0700, Junio C Hamano wrote:
> > "Mark C. Chu-Carroll" <markchucarroll@fastmail.com> writes:
> > 
> > > On Tue, 27 Oct 2026 11:46:45 +0100, Patrick Steinhardt <ps@pks.im> wrote:
> > >> Our modern coding guidelines say that flags should typically be passed
> > >> around as enums so that it becomes trivial to see what kind of flags are
> > >> expected. Adapt the rerere flags accordingly.
> > >> 
> > >>   <snip>
> > >> +enum rerere_flags {
> > >> +	RERERE_AUTOUPDATE = (1 << 0),
> > >> +	RERERE_NOAUTOUPDATE = (1 << 1),
> > >> +	RERERE_READONLY = (1 << 2),
> > >> +	/* If MERGE_RR.lock is taken, return -1 as if rerere were disabled */
> > >> +	RERERE_NOWAIT = (1 << 3),
> > >> +};
> > >
> > > If we're moving to using enum flags, then wouldn't it be better to
> > > explicitly add a "RERE_MERGE_LOCK_TOKEN" to the enum for that case, 
> > > rather than have this value only documented by a comment?
> > 
> > Perhaps you misunderstand what the comment says.  It is an
> > explanation for the next line, i.e., it tells the API users "If you
> > want the 'immediate failure when locked' behaviour, pass the _NOWAIT
> > bit".
> 
> Yeah, this. I guess we can help that though by documenting all of the
> flags while at it.

Ah, yeah. I misunderstood that. Obvious in retrospect, but I was
puzzling over that for a while. 

    -Mark



^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 01/12] rerere: rename `repo_rerere()`
  2026-10-28  8:33     ` Patrick Steinhardt
@ 2026-10-28 16:25       ` Junio C Hamano
  0 siblings, 0 replies; 35+ messages in thread
From: Junio C Hamano @ 2026-10-28 16:25 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: git

Patrick Steinhardt <ps@pks.im> writes:

> I was picking "record" because that's also what git-rerere(1) says:
>
>   This command assists the developer in this process by recording
>   conflicted automerge results and corresponding hand resolve results on
>   the initial manual merge, and applying previously recorded hand
>   resolutions to their corresponding automerge results.
>
> But I guess your point is valid, as we don't only do that, but also
> update merge conflicts locally. I'd pick your "perform" proposal in that

Yup, the description above says "recording" and "applying".

> case and rename it to `rerere_perform()` instead. That name is somewhat
> vague, but I guess that cannot be helped because it actually does a
> bunch of things at once.

Let's just call it rerere(), then.

It was the original name for the operation before it was renamed in
35843b1123 (rerere.c: remove implicit dependency on the_index,
2018-09-21), which taught it to take an instance of a repository
structure.

Going back to that original name is in line with what I said
earlier.  The "repo_" prefix is a mistaken trend, and once
everything becomes free from the_repository, it would become
redundant and unnecessary.

Thanks.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* Re: [PATCH 02/12] rerere: convert flags into an enum
  2026-10-28  8:33       ` Patrick Steinhardt
  2026-10-28 14:11         ` Mark C. Chu-Carroll
@ 2026-10-28 16:25         ` Junio C Hamano
  1 sibling, 0 replies; 35+ messages in thread
From: Junio C Hamano @ 2026-10-28 16:25 UTC (permalink / raw)
  To: Patrick Steinhardt; +Cc: Mark C. Chu-Carroll, git

Patrick Steinhardt <ps@pks.im> writes:

> On Tue, Oct 27, 2026 at 12:40:45PM -0700, Junio C Hamano wrote:
>> "Mark C. Chu-Carroll" <markchucarroll@fastmail.com> writes:
>> 
>> > On Tue, 27 Oct 2026 11:46:45 +0100, Patrick Steinhardt <ps@pks.im> wrote:
>> >> Our modern coding guidelines say that flags should typically be passed
>> >> around as enums so that it becomes trivial to see what kind of flags are
>> >> expected. Adapt the rerere flags accordingly.
>> >> 
>> >>   <snip>
>> >> +enum rerere_flags {
>> >> +	RERERE_AUTOUPDATE = (1 << 0),
>> >> +	RERERE_NOAUTOUPDATE = (1 << 1),
>> >> +	RERERE_READONLY = (1 << 2),
>> >> +	/* If MERGE_RR.lock is taken, return -1 as if rerere were disabled */
>> >> +	RERERE_NOWAIT = (1 << 3),
>> >> +};
>> >
>> > If we're moving to using enum flags, then wouldn't it be better to
>> > explicitly add a "RERE_MERGE_LOCK_TOKEN" to the enum for that case, 
>> > rather than have this value only documented by a comment?
>> 
>> Perhaps you misunderstand what the comment says.  It is an
>> explanation for the next line, i.e., it tells the API users "If you
>> want the 'immediate failure when locked' behaviour, pass the _NOWAIT
>> bit".
>
> Yeah, this. I guess we can help that though by documenting all of the
> flags while at it.

Great.

^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH v2 00/12] builtin/rerere: convert to use subcommands
  2026-10-27 10:46 [PATCH 00/12] builtin/rerere: convert to use subcommands Patrick Steinhardt
                   ` (12 preceding siblings ...)
  2026-10-27 19:52 ` [PATCH 00/12] builtin/rerere: convert to use subcommands Junio C Hamano
@ 2026-10-29  9:57 ` Patrick Steinhardt
  2026-10-29  9:57   ` [PATCH v2 01/12] rerere: rename `repo_rerere()` Patrick Steinhardt
                     ` (11 more replies)
  13 siblings, 12 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-29  9:57 UTC (permalink / raw)
  To: git; +Cc: Mark C. Chu-Carroll, Junio C Hamano

Hi,

while reviewing some patch series around git-rerere(1) recently I was a
bit appalled by our custom subcommand parsing that we do here. This
patch series here thus cleans up and modernizes that part a bit so that
git-rerere(1) behaves like every other Git command that happens to have
subcommands.

This series is based on 37b3475fb53 (The 9th batch, 2026-10-25) with
ps/rerere-remove-global-state at b948c10c8b3 (rerere: get rid of global
rerere cache state, 2026-10-23) merged into it.

Changes in v2:
  - Document rerere flags.
  - Rename `rerere_record()` to just `rerere()`.
  - Link to v1: https://patch.msgid.link/20261027-pks-rerere-subcommands-v1-0-d836c36ce0c5@pks.im

Thanks!

Patrick

---
Patrick Steinhardt (12):
      rerere: rename `repo_rerere()`
      rerere: convert flags into an enum
      builtin/rerere: drop `the_repository`
      builtin/rerere: stop passing flags to subcommands that ignore them
      builtin/rerere: turn "forget" into a proper subcommand
      builtin/rerere: turn "clear" into a proper subcommand
      builtin/rerere: turn "status" into a proper subcommand
      builtin/rerere: turn "remaining" into a proper subcommand
      builtin/rerere: turn "diff" into a proper subcommand
      builtin/rerere: turn "gc" into a proper subcommand
      builtin/rerere: use `OPT_SUBCOMMAND()`
      builtin/rerere: introduce proper usage strings for subcommands

 Documentation/git-rerere.adoc |   8 +-
 apply.c                       |   2 +-
 builtin/am.c                  |   4 +-
 builtin/commit.c              |   2 +-
 builtin/merge.c               |   2 +-
 builtin/rerere.c              | 319 ++++++++++++++++++++++++++++++++----------
 builtin/stash.c               |   2 +-
 rerere.c                      |  12 +-
 rerere.h                      |  37 +++--
 sequencer.c                   |   4 +-
 t/t4200-rerere.sh             |   5 -
 11 files changed, 291 insertions(+), 106 deletions(-)

Range-diff versus v1:

 1:  ce75cde848d !  1:  1f3fbbad575 rerere: rename `repo_rerere()`
    @@ Commit message
         repository as first parameter, that's not a good-enough reason to give
         it a `repo_` prefix.
     
    -    Rename the function to `rerere_record()` to be consistent with all the
    -    other functions.
    +    Rename the function to just `rerere()`. Move around and extend
    +    documentation of this function to better explain what it does.
     
         Signed-off-by: Patrick Steinhardt <ps@pks.im>
     
    @@ apply.c: static int write_out_results(struct apply_state *state, struct patch *l
      		 */
      		if (!state->cached)
     -			repo_rerere(state->repo, 0);
    -+			rerere_record(state->repo, 0);
    ++			rerere(state->repo, 0);
      	}
      
      	return errs;
    @@ builtin/am.c: static int fall_back_threeway(const struct am_state *state, const
      
      	if (merge_ort_generic(&o, &our_tree, &their_tree, 1, bases, &result)) {
     -		repo_rerere(the_repository, state->allow_rerere_autoupdate);
    -+		rerere_record(the_repository, state->allow_rerere_autoupdate);
    ++		rerere(the_repository, state->allow_rerere_autoupdate);
      		free(their_tree_name);
      		return error(_("Failed to merge in the changes."));
      	}
    @@ builtin/am.c: static void am_resolve(struct am_state *state, int allow_empty)
      	}
      
     -	repo_rerere(the_repository, 0);
    -+	rerere_record(the_repository, 0);
    ++	rerere(the_repository, 0);
      
      	do_commit(state);
      
    @@ builtin/commit.c: int cmd_commit(int argc,
      	git_test_write_commit_graph_or_die(the_repository);
      
     -	repo_rerere(the_repository, 0);
    -+	rerere_record(the_repository, 0);
    ++	rerere(the_repository, 0);
      	run_auto_maintenance(the_repository, quiet);
      	run_commit_hook(use_editor, repo_get_index_file(the_repository),
      			NULL, "post-commit", NULL);
    @@ builtin/merge.c: static int suggest_conflicts(void)
      	strbuf_release(&msgbuf);
      	fclose(fp);
     -	repo_rerere(the_repository, allow_rerere_auto);
    -+	rerere_record(the_repository, allow_rerere_auto);
    ++	rerere(the_repository, allow_rerere_auto);
      	printf(_("Automatic merge failed; "
      			"fix conflicts and then commit the result.\n"));
      	return 1;
    @@ builtin/rerere.c: int cmd_rerere(int argc,
      
      	if (argc < 1)
     -		return repo_rerere(the_repository, flags);
    -+		return rerere_record(the_repository, flags);
    ++		return rerere(the_repository, flags);
      
      	if (!strcmp(argv[0], "forget")) {
      		struct pathspec pathspec;
    @@ builtin/stash.c: static enum stash_apply_result do_apply_stash(const char *prefi
      
      	if (ret) {
     -		repo_rerere(the_repository, 0);
    -+		rerere_record(the_repository, 0);
    ++		rerere(the_repository, 0);
      
      		if (index)
      			fprintf_ln(stderr, _("Index was not unstashed."));
     
      ## rerere.c ##
     @@ rerere.c: int rerere_cache_setup(struct rerere_cache *cache,
    -  * perform mergy operations, possibly leaving conflicted index entries
    -  * and working tree files.
    -  */
    + 	return fd;
    + }
    + 
    +-/*
    +- * The main entry point that is called internally from codepaths that
    +- * perform mergy operations, possibly leaving conflicted index entries
    +- * and working tree files.
    +- */
     -int repo_rerere(struct repository *r, int flags)
    -+int rerere_record(struct repository *r, int flags)
    ++int rerere(struct repository *r, int flags)
      {
      	struct string_list merge_rr = STRING_LIST_INIT_DUP;
      	struct rerere_cache cache;
    @@ rerere.h: int rerere_cache_setup(struct rerere_cache *cache,
      void rerere_cache_release(struct rerere_cache *cache);
      
     -int repo_rerere(struct repository *, int);
    -+int rerere_record(struct repository *, int);
    ++/*
    ++ * The main entry point that is called internally from codepaths that
    ++ * perform mergy operations, possibly leaving conflicted index entries
    ++ * and working tree files. It records (and optionally updates) resolved
    ++ * conflicts and reuses recorded resolutions for unresolved conflicts.
    ++ */
    ++int rerere(struct repository *r, int flags);
    ++
      /*
       * Given the conflict ID and the name of a "file" used for replaying
       * the recorded resolution (e.g. "preimage", "postimage"), return the
    @@ sequencer.c: static enum pick_result do_pick_commit(struct repository *r,
      		      short_commit_name(r, commit), msg.subject);
      		print_advice(r, res == 1, opts);
     -		repo_rerere(r, opts->allow_rerere_auto);
    -+		rerere_record(r, opts->allow_rerere_auto);
    ++		rerere(r, opts->allow_rerere_auto);
      		goto leave;
      	}
      
    @@ sequencer.c: static int do_merge(struct repository *r,
      	rollback_lock_file(&lock);
      	if (ret)
     -		repo_rerere(r, opts->allow_rerere_auto);
    -+		rerere_record(r, opts->allow_rerere_auto);
    ++		rerere(r, opts->allow_rerere_auto);
      	else
      		/*
      		 * In case of problems, we now want to return a positive
 2:  18259e1556a !  2:  e910458a47c rerere: convert flags into an enum
    @@ Commit message
     
         Our modern coding guidelines say that flags should typically be passed
         around as enums so that it becomes trivial to see what kind of flags are
    -    expected. Adapt the rerere flags accordingly.
    +    expected. Adapt the rerere flags accordingly and document them.
     
         Signed-off-by: Patrick Steinhardt <ps@pks.im>
     
    @@ rerere.c: static int is_rerere_enabled(struct repository *r)
      	int lock_timeout_ms = 1000;
      	int fd;
     @@ rerere.c: int rerere_cache_setup(struct rerere_cache *cache,
    -  * perform mergy operations, possibly leaving conflicted index entries
    -  * and working tree files.
    -  */
    --int rerere_record(struct repository *r, int flags)
    -+int rerere_record(struct repository *r, enum rerere_flags flags)
    + 	return fd;
    + }
    + 
    +-int rerere(struct repository *r, int flags)
    ++int rerere(struct repository *r, enum rerere_flags flags)
      {
      	struct string_list merge_rr = STRING_LIST_INIT_DUP;
      	struct rerere_cache cache;
    @@ rerere.h: struct rerere_id {
      };
      
     +enum rerere_flags {
    ++	/*
    ++	 * Auto-update the index with previously recorded resolutions. This can
    ++	 * be used to explicitly override "rerere.autoUpdate".
    ++	 */
     +	RERERE_AUTOUPDATE = (1 << 0),
    ++
    ++	/* Reverse of RERERE_AUTOUPDATE. */
     +	RERERE_NOAUTOUPDATE = (1 << 1),
    ++
    ++	/* Skip acquiring the MERGE_RR.lock for a read-only operation. */
     +	RERERE_READONLY = (1 << 2),
    -+	/* If MERGE_RR.lock is taken, return -1 as if rerere were disabled */
    ++
    ++	/* If MERGE_RR.lock is taken, return -1 as if rerere were disabled. */
     +	RERERE_NOWAIT = (1 << 3),
     +};
     +
    @@ rerere.h: struct rerere_id {
     +		       enum rerere_flags flags);
      void rerere_cache_release(struct rerere_cache *cache);
      
    --int rerere_record(struct repository *, int);
    -+int rerere_record(struct repository *r, enum rerere_flags flags);
    + /*
    +@@ rerere.h: void rerere_cache_release(struct rerere_cache *cache);
    +  * and working tree files. It records (and optionally updates) resolved
    +  * conflicts and reuses recorded resolutions for unresolved conflicts.
    +  */
    +-int rerere(struct repository *r, int flags);
    ++int rerere(struct repository *r, enum rerere_flags flags);
    + 
      /*
       * Given the conflict ID and the name of a "file" used for replaying
    -  * the recorded resolution (e.g. "preimage", "postimage"), return the
     @@ rerere.h: const char *rerere_path(struct repository *,
      int rerere_forget(struct repository *, struct pathspec *);
      int rerere_remaining(struct repository *, struct string_list *);
 3:  89ae664d90d !  3:  5254d1ac3ba builtin/rerere: drop `the_repository`
    @@ builtin/rerere.c: int cmd_rerere(int argc,
      		flags = RERERE_NOAUTOUPDATE;
      
      	if (argc < 1)
    --		return rerere_record(the_repository, flags);
    -+		return rerere_record(repo, flags);
    +-		return rerere(the_repository, flags);
    ++		return rerere(repo, flags);
      
      	if (!strcmp(argv[0], "forget")) {
      		struct pathspec pathspec;
 4:  5eff73de7bc =  4:  8b24e253e6b builtin/rerere: stop passing flags to subcommands that ignore them
 5:  621c5f6ab1b !  5:  43dde532e63 builtin/rerere: turn "forget" into a proper subcommand
    @@ builtin/rerere.c: static int diff_two(const char *file1, const char *label1,
      	       const char *prefix,
     @@ builtin/rerere.c: int cmd_rerere(int argc,
      	if (argc < 1)
    - 		return rerere_record(repo, flags);
    + 		return rerere(repo, flags);
      
     -	if (!strcmp(argv[0], "forget")) {
     -		struct pathspec pathspec;
 6:  d01fa02064c =  6:  13fd07a2f53 builtin/rerere: turn "clear" into a proper subcommand
 7:  2bebea6449e =  7:  c5d48081cc9 builtin/rerere: turn "status" into a proper subcommand
 8:  5594019f31f =  8:  5c6891f6a48 builtin/rerere: turn "remaining" into a proper subcommand
 9:  e8a2f8ce209 =  9:  b7ed9ef810b builtin/rerere: turn "diff" into a proper subcommand
10:  deebcee0379 = 10:  0ceff6d241b builtin/rerere: turn "gc" into a proper subcommand
11:  56037e9a761 ! 11:  3b8b4b65a53 builtin/rerere: use `OPT_SUBCOMMAND()`
    @@ builtin/rerere.c: static int cmd_rerere_gc(int argc,
      	return 0;
      }
      
    -+static int cmd_rerere_record(int argc,
    -+			     const char **argv,
    -+			     const char *prefix,
    -+			     struct repository *repo)
    ++static int cmd_rerere_perform(int argc,
    ++			      const char **argv,
    ++			      const char *prefix,
    ++			      struct repository *repo)
     +{
     +	int autoupdate = -1;
     +	struct option options[] = {
    @@ builtin/rerere.c: static int cmd_rerere_gc(int argc,
     +	if (autoupdate == 0)
     +		flags = RERERE_NOAUTOUPDATE;
     +
    -+	return rerere_record(repo, flags);
    ++	return rerere(repo, flags);
     +}
     +
      static int cmd_rerere_remaining(int argc,
    @@ builtin/rerere.c: int cmd_rerere(int argc,
     +		int ret;
      
     -	if (argc < 1)
    --		return rerere_record(repo, flags);
    +-		return rerere(repo, flags);
     -
     -	if (!strcmp(argv[0], "forget"))
     -		return cmd_rerere_forget(argc, argv, prefix, repo);
    @@ builtin/rerere.c: int cmd_rerere(int argc,
     -		return cmd_rerere_diff(argc, argv, prefix, repo);
     -	else
     -		usage_with_options(rerere_usage, options);
    -+		strvec_push(&args, "record");
    ++		strvec_push(&args, "perform");
     +		strvec_pushv(&args, argv);
      
     -	return 0;
    @@ builtin/rerere.c: int cmd_rerere(int argc,
     +		CALLOC_ARRAY(args_copy, args.nr);
     +		COPY_ARRAY(args_copy, args.v, args.nr);
     +
    -+		ret = cmd_rerere_record(args.nr, args_copy, prefix, repo);
    ++		ret = cmd_rerere_perform(args.nr, args_copy, prefix, repo);
     +
     +		strvec_clear(&args);
     +		free(args_copy);
12:  102223da27b ! 12:  05adadc9dd8 builtin/rerere: introduce proper usage strings for subcommands
    @@ builtin/rerere.c: static int cmd_rerere_gc(int argc,
      	if (argc)
      		usage(_("too many arguments"));
      
    -@@ builtin/rerere.c: static int cmd_rerere_record(int argc,
    +@@ builtin/rerere.c: static int cmd_rerere_perform(int argc,
      			N_("register clean resolutions in index"), 1),
      		OPT_END(),
      	};

---
base-commit: 710910c03a22ac663885049213b9fef5853ea8e0
change-id: 20261027-pks-rerere-subcommands-318d41553438


^ permalink raw reply	[flat|nested] 35+ messages in thread

* [PATCH v2 01/12] rerere: rename `repo_rerere()`
  2026-10-29  9:57 ` [PATCH v2 " Patrick Steinhardt
@ 2026-10-29  9:57   ` Patrick Steinhardt
  2026-10-29  9:57   ` [PATCH v2 02/12] rerere: convert flags into an enum Patrick Steinhardt
                     ` (10 subsequent siblings)
  11 siblings, 0 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-29  9:57 UTC (permalink / raw)
  To: git; +Cc: Mark C. Chu-Carroll, Junio C Hamano

The `repo_rerere()` function is the only function in "rerere.h" that
does not have a `rerere_` prefix. And while it does indeed receive a
repository as first parameter, that's not a good-enough reason to give
it a `repo_` prefix.

Rename the function to just `rerere()`. Move around and extend
documentation of this function to better explain what it does.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 apply.c          | 2 +-
 builtin/am.c     | 4 ++--
 builtin/commit.c | 2 +-
 builtin/merge.c  | 2 +-
 builtin/rerere.c | 2 +-
 builtin/stash.c  | 2 +-
 rerere.c         | 7 +------
 rerere.h         | 9 ++++++++-
 sequencer.c      | 4 ++--
 9 files changed, 18 insertions(+), 16 deletions(-)

diff --git a/apply.c b/apply.c
index a0b7119c639..646fabeafa3 100644
--- a/apply.c
+++ b/apply.c
@@ -4866,7 +4866,7 @@ static int write_out_results(struct apply_state *state, struct patch *list)
 		 * tree with conflict markers, but that isn't written with --cached.
 		 */
 		if (!state->cached)
-			repo_rerere(state->repo, 0);
+			rerere(state->repo, 0);
 	}
 
 	return errs;
diff --git a/builtin/am.c b/builtin/am.c
index e9623b83077..4a5b37bee00 100644
--- a/builtin/am.c
+++ b/builtin/am.c
@@ -1649,7 +1649,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa
 		o.verbosity = 0;
 
 	if (merge_ort_generic(&o, &our_tree, &their_tree, 1, bases, &result)) {
-		repo_rerere(the_repository, state->allow_rerere_autoupdate);
+		rerere(the_repository, state->allow_rerere_autoupdate);
 		free(their_tree_name);
 		return error(_("Failed to merge in the changes."));
 	}
@@ -1979,7 +1979,7 @@ static void am_resolve(struct am_state *state, int allow_empty)
 			goto next;
 	}
 
-	repo_rerere(the_repository, 0);
+	rerere(the_repository, 0);
 
 	do_commit(state);
 
diff --git a/builtin/commit.c b/builtin/commit.c
index 8ffd42681bb..3a4b94ad623 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -1962,7 +1962,7 @@ int cmd_commit(int argc,
 
 	git_test_write_commit_graph_or_die(the_repository);
 
-	repo_rerere(the_repository, 0);
+	rerere(the_repository, 0);
 	run_auto_maintenance(the_repository, quiet);
 	run_commit_hook(use_editor, repo_get_index_file(the_repository),
 			NULL, "post-commit", NULL);
diff --git a/builtin/merge.c b/builtin/merge.c
index d7bf209114f..089e36e8575 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -1061,7 +1061,7 @@ static int suggest_conflicts(void)
 	fputs(msgbuf.buf, fp);
 	strbuf_release(&msgbuf);
 	fclose(fp);
-	repo_rerere(the_repository, allow_rerere_auto);
+	rerere(the_repository, allow_rerere_auto);
 	printf(_("Automatic merge failed; "
 			"fix conflicts and then commit the result.\n"));
 	return 1;
diff --git a/builtin/rerere.c b/builtin/rerere.c
index 2c17f02f56d..a499e7b9151 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -83,7 +83,7 @@ int cmd_rerere(int argc,
 		flags = RERERE_NOAUTOUPDATE;
 
 	if (argc < 1)
-		return repo_rerere(the_repository, flags);
+		return rerere(the_repository, flags);
 
 	if (!strcmp(argv[0], "forget")) {
 		struct pathspec pathspec;
diff --git a/builtin/stash.c b/builtin/stash.c
index e1a3a76bd04..91870a255fd 100644
--- a/builtin/stash.c
+++ b/builtin/stash.c
@@ -688,7 +688,7 @@ static enum stash_apply_result do_apply_stash(const char *prefix,
 		ret = error(_("could not write index"));
 
 	if (ret) {
-		repo_rerere(the_repository, 0);
+		rerere(the_repository, 0);
 
 		if (index)
 			fprintf_ln(stderr, _("Index was not unstashed."));
diff --git a/rerere.c b/rerere.c
index e0897612085..47a4a325bf5 100644
--- a/rerere.c
+++ b/rerere.c
@@ -951,12 +951,7 @@ int rerere_cache_setup(struct rerere_cache *cache,
 	return fd;
 }
 
-/*
- * The main entry point that is called internally from codepaths that
- * perform mergy operations, possibly leaving conflicted index entries
- * and working tree files.
- */
-int repo_rerere(struct repository *r, int flags)
+int rerere(struct repository *r, int flags)
 {
 	struct string_list merge_rr = STRING_LIST_INIT_DUP;
 	struct rerere_cache cache;
diff --git a/rerere.h b/rerere.h
index d3cbd10e13f..dd9da03d6db 100644
--- a/rerere.h
+++ b/rerere.h
@@ -45,7 +45,14 @@ int rerere_cache_setup(struct rerere_cache *cache,
 		       int flags);
 void rerere_cache_release(struct rerere_cache *cache);
 
-int repo_rerere(struct repository *, int);
+/*
+ * The main entry point that is called internally from codepaths that
+ * perform mergy operations, possibly leaving conflicted index entries
+ * and working tree files. It records (and optionally updates) resolved
+ * conflicts and reuses recorded resolutions for unresolved conflicts.
+ */
+int rerere(struct repository *r, int flags);
+
 /*
  * Given the conflict ID and the name of a "file" used for replaying
  * the recorded resolution (e.g. "preimage", "postimage"), return the
diff --git a/sequencer.c b/sequencer.c
index 508ec717920..ace1cfac1cb 100644
--- a/sequencer.c
+++ b/sequencer.c
@@ -2556,7 +2556,7 @@ static enum pick_result do_pick_commit(struct repository *r,
 		      : _("could not apply %s... %s"),
 		      short_commit_name(r, commit), msg.subject);
 		print_advice(r, res == 1, opts);
-		repo_rerere(r, opts->allow_rerere_auto);
+		rerere(r, opts->allow_rerere_auto);
 		goto leave;
 	}
 
@@ -4488,7 +4488,7 @@ static int do_merge(struct repository *r,
 
 	rollback_lock_file(&lock);
 	if (ret)
-		repo_rerere(r, opts->allow_rerere_auto);
+		rerere(r, opts->allow_rerere_auto);
 	else
 		/*
 		 * In case of problems, we now want to return a positive

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 02/12] rerere: convert flags into an enum
  2026-10-29  9:57 ` [PATCH v2 " Patrick Steinhardt
  2026-10-29  9:57   ` [PATCH v2 01/12] rerere: rename `repo_rerere()` Patrick Steinhardt
@ 2026-10-29  9:57   ` Patrick Steinhardt
  2026-10-29  9:57   ` [PATCH v2 03/12] builtin/rerere: drop `the_repository` Patrick Steinhardt
                     ` (9 subsequent siblings)
  11 siblings, 0 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-29  9:57 UTC (permalink / raw)
  To: git; +Cc: Mark C. Chu-Carroll, Junio C Hamano

Our modern coding guidelines say that flags should typically be passed
around as enums so that it becomes trivial to see what kind of flags are
expected. Adapt the rerere flags accordingly and document them.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 rerere.c |  7 ++++---
 rerere.h | 30 +++++++++++++++++++++---------
 2 files changed, 25 insertions(+), 12 deletions(-)

diff --git a/rerere.c b/rerere.c
index 47a4a325bf5..3652b7ac2d5 100644
--- a/rerere.c
+++ b/rerere.c
@@ -908,7 +908,7 @@ static int is_rerere_enabled(struct repository *r)
 int rerere_cache_setup(struct rerere_cache *cache,
 		       struct repository *r,
 		       struct string_list *merge_rr,
-		       int flags)
+		       enum rerere_flags flags)
 {
 	int lock_timeout_ms = 1000;
 	int fd;
@@ -951,7 +951,7 @@ int rerere_cache_setup(struct rerere_cache *cache,
 	return fd;
 }
 
-int rerere(struct repository *r, int flags)
+int rerere(struct repository *r, enum rerere_flags flags)
 {
 	struct string_list merge_rr = STRING_LIST_INIT_DUP;
 	struct rerere_cache cache;
@@ -1336,7 +1336,8 @@ bool rerere_gc_needed(struct repository *r, size_t limit)
 	return needed;
 }
 
-void rerere_gc(struct repository *r, struct string_list *rr, int flags)
+void rerere_gc(struct repository *r, struct string_list *rr,
+	       enum rerere_flags flags)
 {
 	struct rerere_cache cache;
 	struct string_list to_remove = STRING_LIST_INIT_DUP;
diff --git a/rerere.h b/rerere.h
index dd9da03d6db..f13790c5dcd 100644
--- a/rerere.h
+++ b/rerere.h
@@ -9,12 +9,6 @@
 struct pathspec;
 struct repository;
 
-#define RERERE_AUTOUPDATE   01
-#define RERERE_NOAUTOUPDATE 02
-#define RERERE_READONLY     04
-/* If MERGE_RR.lock is taken, return -1 as if rerere were disabled */
-#define RERERE_NOWAIT       010
-
 /*
  * Marks paths that have been hand-resolved and added to the
  * index. Set in the util field of such paths after calling
@@ -39,10 +33,27 @@ struct rerere_id {
 	int variant;
 };
 
+enum rerere_flags {
+	/*
+	 * Auto-update the index with previously recorded resolutions. This can
+	 * be used to explicitly override "rerere.autoUpdate".
+	 */
+	RERERE_AUTOUPDATE = (1 << 0),
+
+	/* Reverse of RERERE_AUTOUPDATE. */
+	RERERE_NOAUTOUPDATE = (1 << 1),
+
+	/* Skip acquiring the MERGE_RR.lock for a read-only operation. */
+	RERERE_READONLY = (1 << 2),
+
+	/* If MERGE_RR.lock is taken, return -1 as if rerere were disabled. */
+	RERERE_NOWAIT = (1 << 3),
+};
+
 int rerere_cache_setup(struct rerere_cache *cache,
 		       struct repository *r,
 		       struct string_list *merge_rr,
-		       int flags);
+		       enum rerere_flags flags);
 void rerere_cache_release(struct rerere_cache *cache);
 
 /*
@@ -51,7 +62,7 @@ void rerere_cache_release(struct rerere_cache *cache);
  * and working tree files. It records (and optionally updates) resolved
  * conflicts and reuses recorded resolutions for unresolved conflicts.
  */
-int rerere(struct repository *r, int flags);
+int rerere(struct repository *r, enum rerere_flags flags);
 
 /*
  * Given the conflict ID and the name of a "file" used for replaying
@@ -65,7 +76,8 @@ const char *rerere_path(struct repository *,
 int rerere_forget(struct repository *, struct pathspec *);
 int rerere_remaining(struct repository *, struct string_list *);
 void rerere_clear(struct repository *, struct string_list *);
-void rerere_gc(struct repository *, struct string_list *, int);
+void rerere_gc(struct repository *, struct string_list *,
+	       enum rerere_flags flags);
 
 /*
  * Check whether garbage collection for rerere entries is needed, which is

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 03/12] builtin/rerere: drop `the_repository`
  2026-10-29  9:57 ` [PATCH v2 " Patrick Steinhardt
  2026-10-29  9:57   ` [PATCH v2 01/12] rerere: rename `repo_rerere()` Patrick Steinhardt
  2026-10-29  9:57   ` [PATCH v2 02/12] rerere: convert flags into an enum Patrick Steinhardt
@ 2026-10-29  9:57   ` Patrick Steinhardt
  2026-10-29  9:57   ` [PATCH v2 04/12] builtin/rerere: stop passing flags to subcommands that ignore them Patrick Steinhardt
                     ` (8 subsequent siblings)
  11 siblings, 0 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-29  9:57 UTC (permalink / raw)
  To: git; +Cc: Mark C. Chu-Carroll, Junio C Hamano

There are a couple of references to `the_repository` in git-rerere(1).
As that command cannot run outside of a repository these references can
be trivially replaced by the repository that's passed in as parameter to
`cmd_rerere()`. We don't have to worry about the repository being `NULL`
when called with "-h", either, as there are no references to the repo
before we call `parse_options()`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rerere.c | 24 +++++++++++-------------
 1 file changed, 11 insertions(+), 13 deletions(-)

diff --git a/builtin/rerere.c b/builtin/rerere.c
index a499e7b9151..83fb3783c61 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -1,5 +1,3 @@
-#define USE_THE_REPOSITORY_VARIABLE
-
 #include "builtin.h"
 #include "config.h"
 #include "gettext.h"
@@ -57,7 +55,7 @@ static int diff_two(const char *file1, const char *label1,
 int cmd_rerere(int argc,
 	       const char **argv,
 	       const char *prefix,
-	       struct repository *repo UNUSED)
+	       struct repository *repo)
 {
 	struct string_list merge_rr = STRING_LIST_INIT_DUP;
 	int autoupdate = -1, skip_locked = 0, flags = 0;
@@ -75,7 +73,7 @@ int cmd_rerere(int argc,
 	if (skip_locked && (argc < 1 || strcmp(argv[0], "gc")))
 		die(_("the option '%s' requires '%s'"), "--skip-locked", "gc");
 
-	repo_config(the_repository, git_xmerge_config, NULL);
+	repo_config(repo, git_xmerge_config, NULL);
 
 	if (autoupdate == 1)
 		flags = RERERE_AUTOUPDATE;
@@ -83,7 +81,7 @@ int cmd_rerere(int argc,
 		flags = RERERE_NOAUTOUPDATE;
 
 	if (argc < 1)
-		return rerere(the_repository, flags);
+		return rerere(repo, flags);
 
 	if (!strcmp(argv[0], "forget")) {
 		struct pathspec pathspec;
@@ -94,21 +92,21 @@ int cmd_rerere(int argc,
 		parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD,
 			       prefix, argv + 1);
 
-		ret = rerere_forget(the_repository, &pathspec);
+		ret = rerere_forget(repo, &pathspec);
 
 		clear_pathspec(&pathspec);
 		return ret;
 	}
 
 	if (!strcmp(argv[0], "clear")) {
-		rerere_clear(the_repository, &merge_rr);
+		rerere_clear(repo, &merge_rr);
 	} else if (!strcmp(argv[0], "gc"))
-		rerere_gc(the_repository, &merge_rr,
+		rerere_gc(repo, &merge_rr,
 			  skip_locked ? RERERE_NOWAIT : 0);
 	else if (!strcmp(argv[0], "status")) {
 		struct rerere_cache cache;
 
-		if (rerere_cache_setup(&cache, the_repository, &merge_rr,
+		if (rerere_cache_setup(&cache, repo, &merge_rr,
 				       flags | RERERE_READONLY) < 0)
 			return 0;
 		for (size_t i = 0; i < merge_rr.nr; i++)
@@ -116,7 +114,7 @@ int cmd_rerere(int argc,
 
 		rerere_cache_release(&cache);
 	} else if (!strcmp(argv[0], "remaining")) {
-		rerere_remaining(the_repository, &merge_rr);
+		rerere_remaining(repo, &merge_rr);
 		for (size_t i = 0; i < merge_rr.nr; i++) {
 			if (merge_rr.items[i].util != RERERE_RESOLVED)
 				printf("%s\n", merge_rr.items[i].string);
@@ -129,16 +127,16 @@ int cmd_rerere(int argc,
 		struct strbuf buf = STRBUF_INIT;
 		struct rerere_cache cache;
 
-		if (rerere_cache_setup(&cache, the_repository, &merge_rr,
+		if (rerere_cache_setup(&cache, repo, &merge_rr,
 				      flags | RERERE_READONLY) < 0)
 			return 0;
 		for (size_t i = 0; i < merge_rr.nr; i++) {
 			const char *path = merge_rr.items[i].string;
 			const struct rerere_id *id = merge_rr.items[i].util;
-			if (diff_two(rerere_path(the_repository, &buf, id, "preimage"),
+			if (diff_two(rerere_path(repo, &buf, id, "preimage"),
 				     path, path, path))
 				die(_("unable to generate diff for '%s'"),
-				    rerere_path(the_repository, &buf, id, NULL));
+				    rerere_path(repo, &buf, id, NULL));
 		}
 
 		rerere_cache_release(&cache);

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 04/12] builtin/rerere: stop passing flags to subcommands that ignore them
  2026-10-29  9:57 ` [PATCH v2 " Patrick Steinhardt
                     ` (2 preceding siblings ...)
  2026-10-29  9:57   ` [PATCH v2 03/12] builtin/rerere: drop `the_repository` Patrick Steinhardt
@ 2026-10-29  9:57   ` Patrick Steinhardt
  2026-10-29  9:57   ` [PATCH v2 05/12] builtin/rerere: turn "forget" into a proper subcommand Patrick Steinhardt
                     ` (7 subsequent siblings)
  11 siblings, 0 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-29  9:57 UTC (permalink / raw)
  To: git; +Cc: Mark C. Chu-Carroll, Junio C Hamano

The git-rerere(1) command has a "--[no-]rerere-autoupdate" flag. If
passed, we explicitly enable or disable recording clean merge
resolutions that are staged in the index. This flag only matters to
git-rerere(1) when not executed with any subcommand, but we also forward
the flag to `git rerere status` and `git rerere diff`.

This is mostly harmless as we would ignore those flags anyway, but it
makes subsequent refactorings harder to understand. Prepare for these
refactorings by not using the flag in those subcommands anymore.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rerere.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/builtin/rerere.c b/builtin/rerere.c
index 83fb3783c61..405276cc94b 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -107,7 +107,7 @@ int cmd_rerere(int argc,
 		struct rerere_cache cache;
 
 		if (rerere_cache_setup(&cache, repo, &merge_rr,
-				       flags | RERERE_READONLY) < 0)
+				       RERERE_READONLY) < 0)
 			return 0;
 		for (size_t i = 0; i < merge_rr.nr; i++)
 			printf("%s\n", merge_rr.items[i].string);
@@ -128,7 +128,7 @@ int cmd_rerere(int argc,
 		struct rerere_cache cache;
 
 		if (rerere_cache_setup(&cache, repo, &merge_rr,
-				      flags | RERERE_READONLY) < 0)
+				       RERERE_READONLY) < 0)
 			return 0;
 		for (size_t i = 0; i < merge_rr.nr; i++) {
 			const char *path = merge_rr.items[i].string;

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 05/12] builtin/rerere: turn "forget" into a proper subcommand
  2026-10-29  9:57 ` [PATCH v2 " Patrick Steinhardt
                     ` (3 preceding siblings ...)
  2026-10-29  9:57   ` [PATCH v2 04/12] builtin/rerere: stop passing flags to subcommands that ignore them Patrick Steinhardt
@ 2026-10-29  9:57   ` Patrick Steinhardt
  2026-10-29  9:57   ` [PATCH v2 06/12] builtin/rerere: turn "clear" " Patrick Steinhardt
                     ` (6 subsequent siblings)
  11 siblings, 0 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-29  9:57 UTC (permalink / raw)
  To: git; +Cc: Mark C. Chu-Carroll, Junio C Hamano

The git-rerere(1) command has its own, home-grown implementation of
subcommands. Of course this implementation is inconsistent with how
subcommands typically work:

  - Options from other subcommands apply to each other.

  - We don't have proper usage strings.

  - Running `git rerere <subcmd> -h` doesn't print the options that are
    supported by that subcommand, but instead it just prints the help of
    git-rerere(1) itself.

A subsequent commit will thus introduce proper subcommands as supported
by our parse-options subsystem. Refactor the "forget" subcommand to use
parse-options as a preparatory step. The other commands will follow in
subsequent commits until all of them have been converted, and then we
switch over to `OPT_SUBCOMMAND()`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rerere.c | 38 ++++++++++++++++++++++++--------------
 1 file changed, 24 insertions(+), 14 deletions(-)

diff --git a/builtin/rerere.c b/builtin/rerere.c
index 405276cc94b..5d2ae154ff8 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -52,6 +52,28 @@ static int diff_two(const char *file1, const char *label1,
 	return ret;
 }
 
+static int cmd_rerere_forget(int argc,
+			     const char **argv,
+			     const char *prefix,
+			     struct repository *repo)
+{
+	struct option options[] = {
+		OPT_END(),
+	};
+	struct pathspec pathspec;
+	int ret;
+
+	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	if (!argc)
+		warning(_("'git rerere forget' without paths is deprecated"));
+	parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD, prefix, argv);
+
+	ret = rerere_forget(repo, &pathspec);
+
+	clear_pathspec(&pathspec);
+	return ret;
+}
+
 int cmd_rerere(int argc,
 	       const char **argv,
 	       const char *prefix,
@@ -83,20 +105,8 @@ int cmd_rerere(int argc,
 	if (argc < 1)
 		return rerere(repo, flags);
 
-	if (!strcmp(argv[0], "forget")) {
-		struct pathspec pathspec;
-		int ret;
-
-		if (argc < 2)
-			warning(_("'git rerere forget' without paths is deprecated"));
-		parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD,
-			       prefix, argv + 1);
-
-		ret = rerere_forget(repo, &pathspec);
-
-		clear_pathspec(&pathspec);
-		return ret;
-	}
+	if (!strcmp(argv[0], "forget"))
+		return cmd_rerere_forget(argc, argv, prefix, repo);
 
 	if (!strcmp(argv[0], "clear")) {
 		rerere_clear(repo, &merge_rr);

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 06/12] builtin/rerere: turn "clear" into a proper subcommand
  2026-10-29  9:57 ` [PATCH v2 " Patrick Steinhardt
                     ` (4 preceding siblings ...)
  2026-10-29  9:57   ` [PATCH v2 05/12] builtin/rerere: turn "forget" into a proper subcommand Patrick Steinhardt
@ 2026-10-29  9:57   ` Patrick Steinhardt
  2026-10-29  9:57   ` [PATCH v2 07/12] builtin/rerere: turn "status" " Patrick Steinhardt
                     ` (5 subsequent siblings)
  11 siblings, 0 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-29  9:57 UTC (permalink / raw)
  To: git; +Cc: Mark C. Chu-Carroll, Junio C Hamano

As explained in a preceding commit, a subsequent commit will introduce
proper subcommands as supported by our parse-options subsystem. Refactor
the "clear" subcommand to use parse-options as a preparatory step.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rerere.c | 25 ++++++++++++++++++++++---
 1 file changed, 22 insertions(+), 3 deletions(-)

diff --git a/builtin/rerere.c b/builtin/rerere.c
index 5d2ae154ff8..62f69b488b0 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -52,6 +52,26 @@ static int diff_two(const char *file1, const char *label1,
 	return ret;
 }
 
+static int cmd_rerere_clear(int argc,
+			    const char **argv,
+			    const char *prefix,
+			    struct repository *repo)
+{
+	struct option options[] = {
+		OPT_END(),
+	};
+	struct string_list merge_rr = STRING_LIST_INIT_DUP;
+
+	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	if (argc)
+		usage(_("too many arguments"));
+
+	rerere_clear(repo, &merge_rr);
+
+	string_list_clear(&merge_rr, 1);
+	return 0;
+}
+
 static int cmd_rerere_forget(int argc,
 			     const char **argv,
 			     const char *prefix,
@@ -107,9 +127,8 @@ int cmd_rerere(int argc,
 
 	if (!strcmp(argv[0], "forget"))
 		return cmd_rerere_forget(argc, argv, prefix, repo);
-
-	if (!strcmp(argv[0], "clear")) {
-		rerere_clear(repo, &merge_rr);
+	else if (!strcmp(argv[0], "clear")) {
+		return cmd_rerere_clear(argc, argv, prefix, repo);
 	} else if (!strcmp(argv[0], "gc"))
 		rerere_gc(repo, &merge_rr,
 			  skip_locked ? RERERE_NOWAIT : 0);

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 07/12] builtin/rerere: turn "status" into a proper subcommand
  2026-10-29  9:57 ` [PATCH v2 " Patrick Steinhardt
                     ` (5 preceding siblings ...)
  2026-10-29  9:57   ` [PATCH v2 06/12] builtin/rerere: turn "clear" " Patrick Steinhardt
@ 2026-10-29  9:57   ` Patrick Steinhardt
  2026-10-29  9:57   ` [PATCH v2 08/12] builtin/rerere: turn "remaining" " Patrick Steinhardt
                     ` (4 subsequent siblings)
  11 siblings, 0 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-29  9:57 UTC (permalink / raw)
  To: git; +Cc: Mark C. Chu-Carroll, Junio C Hamano

As explained in a preceding commit, a subsequent commit will introduce
proper subcommands as supported by our parse-options subsystem. Refactor
the "status" subcommand to use parse-options as a preparatory step.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rerere.c | 40 +++++++++++++++++++++++++++++-----------
 1 file changed, 29 insertions(+), 11 deletions(-)

diff --git a/builtin/rerere.c b/builtin/rerere.c
index 62f69b488b0..2bcd270a1f1 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -94,6 +94,32 @@ static int cmd_rerere_forget(int argc,
 	return ret;
 }
 
+static int cmd_rerere_status(int argc,
+			     const char **argv,
+			     const char *prefix,
+			     struct repository *repo)
+{
+	struct option options[] = {
+		OPT_END(),
+	};
+	struct string_list merge_rr = STRING_LIST_INIT_DUP;
+	struct rerere_cache cache;
+
+	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	if (argc)
+		usage(_("too many arguments"));
+
+	if (rerere_cache_setup(&cache, repo, &merge_rr,
+			       RERERE_READONLY) < 0)
+		return 0;
+	for (size_t i = 0; i < merge_rr.nr; i++)
+		printf("%s\n", merge_rr.items[i].string);
+
+	string_list_clear(&merge_rr, 1);
+	rerere_cache_release(&cache);
+	return 0;
+}
+
 int cmd_rerere(int argc,
 	       const char **argv,
 	       const char *prefix,
@@ -132,17 +158,9 @@ int cmd_rerere(int argc,
 	} else if (!strcmp(argv[0], "gc"))
 		rerere_gc(repo, &merge_rr,
 			  skip_locked ? RERERE_NOWAIT : 0);
-	else if (!strcmp(argv[0], "status")) {
-		struct rerere_cache cache;
-
-		if (rerere_cache_setup(&cache, repo, &merge_rr,
-				       RERERE_READONLY) < 0)
-			return 0;
-		for (size_t i = 0; i < merge_rr.nr; i++)
-			printf("%s\n", merge_rr.items[i].string);
-
-		rerere_cache_release(&cache);
-	} else if (!strcmp(argv[0], "remaining")) {
+	else if (!strcmp(argv[0], "status"))
+		return cmd_rerere_status(argc, argv, prefix, repo);
+	else if (!strcmp(argv[0], "remaining")) {
 		rerere_remaining(repo, &merge_rr);
 		for (size_t i = 0; i < merge_rr.nr; i++) {
 			if (merge_rr.items[i].util != RERERE_RESOLVED)

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 08/12] builtin/rerere: turn "remaining" into a proper subcommand
  2026-10-29  9:57 ` [PATCH v2 " Patrick Steinhardt
                     ` (6 preceding siblings ...)
  2026-10-29  9:57   ` [PATCH v2 07/12] builtin/rerere: turn "status" " Patrick Steinhardt
@ 2026-10-29  9:57   ` Patrick Steinhardt
  2026-10-29  9:57   ` [PATCH v2 09/12] builtin/rerere: turn "diff" " Patrick Steinhardt
                     ` (3 subsequent siblings)
  11 siblings, 0 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-29  9:57 UTC (permalink / raw)
  To: git; +Cc: Mark C. Chu-Carroll, Junio C Hamano

As explained in a preceding commit, a subsequent commit will introduce
proper subcommands as supported by our parse-options subsystem. Refactor
the "remaining" subcommand to use parse-options as a preparatory step.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rerere.c | 42 +++++++++++++++++++++++++++++++-----------
 1 file changed, 31 insertions(+), 11 deletions(-)

diff --git a/builtin/rerere.c b/builtin/rerere.c
index 2bcd270a1f1..3f82bcbeb10 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -94,6 +94,34 @@ static int cmd_rerere_forget(int argc,
 	return ret;
 }
 
+static int cmd_rerere_remaining(int argc,
+				const char **argv,
+				const char *prefix,
+				struct repository *repo)
+{
+	struct option options[] = {
+		OPT_END(),
+	};
+	struct string_list merge_rr = STRING_LIST_INIT_DUP;
+
+	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	if (argc)
+		usage(_("too many arguments"));
+
+	rerere_remaining(repo, &merge_rr);
+	for (size_t i = 0; i < merge_rr.nr; i++) {
+		if (merge_rr.items[i].util != RERERE_RESOLVED)
+			printf("%s\n", merge_rr.items[i].string);
+		else
+			/* prepare for later call to
+			 * string_list_clear() */
+			merge_rr.items[i].util = NULL;
+	}
+
+	string_list_clear(&merge_rr, 1);
+	return 0;
+}
+
 static int cmd_rerere_status(int argc,
 			     const char **argv,
 			     const char *prefix,
@@ -160,17 +188,9 @@ int cmd_rerere(int argc,
 			  skip_locked ? RERERE_NOWAIT : 0);
 	else if (!strcmp(argv[0], "status"))
 		return cmd_rerere_status(argc, argv, prefix, repo);
-	else if (!strcmp(argv[0], "remaining")) {
-		rerere_remaining(repo, &merge_rr);
-		for (size_t i = 0; i < merge_rr.nr; i++) {
-			if (merge_rr.items[i].util != RERERE_RESOLVED)
-				printf("%s\n", merge_rr.items[i].string);
-			else
-				/* prepare for later call to
-				 * string_list_clear() */
-				merge_rr.items[i].util = NULL;
-		}
-	} else if (!strcmp(argv[0], "diff")) {
+	else if (!strcmp(argv[0], "remaining"))
+		return cmd_rerere_remaining(argc, argv, prefix, repo);
+	else if (!strcmp(argv[0], "diff")) {
 		struct strbuf buf = STRBUF_INIT;
 		struct rerere_cache cache;
 

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 09/12] builtin/rerere: turn "diff" into a proper subcommand
  2026-10-29  9:57 ` [PATCH v2 " Patrick Steinhardt
                     ` (7 preceding siblings ...)
  2026-10-29  9:57   ` [PATCH v2 08/12] builtin/rerere: turn "remaining" " Patrick Steinhardt
@ 2026-10-29  9:57   ` Patrick Steinhardt
  2026-10-29  9:57   ` [PATCH v2 10/12] builtin/rerere: turn "gc" " Patrick Steinhardt
                     ` (2 subsequent siblings)
  11 siblings, 0 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-29  9:57 UTC (permalink / raw)
  To: git; +Cc: Mark C. Chu-Carroll, Junio C Hamano

As explained in a preceding commit, a subsequent commit will introduce
proper subcommands as supported by our parse-options subsystem. Refactor
the "diff" subcommand to use parse-options as a preparatory step.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rerere.c | 56 +++++++++++++++++++++++++++++++++++++-------------------
 1 file changed, 37 insertions(+), 19 deletions(-)

diff --git a/builtin/rerere.c b/builtin/rerere.c
index 3f82bcbeb10..40bae5cb9cc 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -72,6 +72,40 @@ static int cmd_rerere_clear(int argc,
 	return 0;
 }
 
+static int cmd_rerere_diff(int argc,
+			   const char **argv,
+			   const char *prefix,
+			   struct repository *repo)
+{
+	struct option options[] = {
+		OPT_END(),
+	};
+	struct string_list merge_rr = STRING_LIST_INIT_DUP;
+	struct strbuf buf = STRBUF_INIT;
+	struct rerere_cache cache;
+
+	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	if (argc)
+		usage(_("too many arguments"));
+
+	if (rerere_cache_setup(&cache, repo, &merge_rr,
+			       RERERE_READONLY) < 0)
+		return 0;
+
+	for (size_t i = 0; i < merge_rr.nr; i++) {
+		const char *path = merge_rr.items[i].string;
+		const struct rerere_id *id = merge_rr.items[i].util;
+		if (diff_two(rerere_path(repo, &buf, id, "preimage"), path, path, path))
+			die(_("unable to generate diff for '%s'"),
+			    rerere_path(repo, &buf, id, NULL));
+	}
+
+	string_list_clear(&merge_rr, 1);
+	rerere_cache_release(&cache);
+	strbuf_release(&buf);
+	return 0;
+}
+
 static int cmd_rerere_forget(int argc,
 			     const char **argv,
 			     const char *prefix,
@@ -190,25 +224,9 @@ int cmd_rerere(int argc,
 		return cmd_rerere_status(argc, argv, prefix, repo);
 	else if (!strcmp(argv[0], "remaining"))
 		return cmd_rerere_remaining(argc, argv, prefix, repo);
-	else if (!strcmp(argv[0], "diff")) {
-		struct strbuf buf = STRBUF_INIT;
-		struct rerere_cache cache;
-
-		if (rerere_cache_setup(&cache, repo, &merge_rr,
-				       RERERE_READONLY) < 0)
-			return 0;
-		for (size_t i = 0; i < merge_rr.nr; i++) {
-			const char *path = merge_rr.items[i].string;
-			const struct rerere_id *id = merge_rr.items[i].util;
-			if (diff_two(rerere_path(repo, &buf, id, "preimage"),
-				     path, path, path))
-				die(_("unable to generate diff for '%s'"),
-				    rerere_path(repo, &buf, id, NULL));
-		}
-
-		rerere_cache_release(&cache);
-		strbuf_release(&buf);
-	} else
+	else if (!strcmp(argv[0], "diff"))
+		return cmd_rerere_diff(argc, argv, prefix, repo);
+	else
 		usage_with_options(rerere_usage, options);
 
 	string_list_clear(&merge_rr, 1);

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 10/12] builtin/rerere: turn "gc" into a proper subcommand
  2026-10-29  9:57 ` [PATCH v2 " Patrick Steinhardt
                     ` (8 preceding siblings ...)
  2026-10-29  9:57   ` [PATCH v2 09/12] builtin/rerere: turn "diff" " Patrick Steinhardt
@ 2026-10-29  9:57   ` Patrick Steinhardt
  2026-10-29  9:57   ` [PATCH v2 11/12] builtin/rerere: use `OPT_SUBCOMMAND()` Patrick Steinhardt
  2026-10-29  9:57   ` [PATCH v2 12/12] builtin/rerere: introduce proper usage strings for subcommands Patrick Steinhardt
  11 siblings, 0 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-29  9:57 UTC (permalink / raw)
  To: git; +Cc: Mark C. Chu-Carroll, Junio C Hamano

As explained in a preceding commit, a subsequent commit will introduce
proper subcommands as supported by our parse-options subsystem. Refactor
the "gc" subcommand to use parse-options as a preparatory step.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rerere.c  | 43 ++++++++++++++++++++++++++++++-------------
 t/t4200-rerere.sh |  5 -----
 2 files changed, 30 insertions(+), 18 deletions(-)

diff --git a/builtin/rerere.c b/builtin/rerere.c
index 40bae5cb9cc..3162b074ec6 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -128,6 +128,30 @@ static int cmd_rerere_forget(int argc,
 	return ret;
 }
 
+static int cmd_rerere_gc(int argc,
+			 const char **argv,
+			 const char *prefix,
+			 struct repository *repo)
+{
+	int skip_locked = 0;
+	struct option options[] = {
+		OPT_HIDDEN_BOOL(0, "skip-locked", &skip_locked,
+				N_("skip gc while another process holds the lock")),
+		OPT_END(),
+	};
+	struct string_list merge_rr = STRING_LIST_INIT_DUP;
+
+	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	if (argc)
+		usage(_("too many arguments"));
+
+	rerere_gc(repo, &merge_rr,
+		  skip_locked ? RERERE_NOWAIT : 0);
+
+	string_list_clear(&merge_rr, 1);
+	return 0;
+}
+
 static int cmd_rerere_remaining(int argc,
 				const char **argv,
 				const char *prefix,
@@ -187,21 +211,16 @@ int cmd_rerere(int argc,
 	       const char *prefix,
 	       struct repository *repo)
 {
-	struct string_list merge_rr = STRING_LIST_INIT_DUP;
-	int autoupdate = -1, skip_locked = 0, flags = 0;
+	int autoupdate = -1, flags = 0;
 
 	struct option options[] = {
 		OPT_SET_INT(0, "rerere-autoupdate", &autoupdate,
 			N_("register clean resolutions in index"), 1),
-		OPT_HIDDEN_BOOL(0, "skip-locked", &skip_locked,
-			N_("skip gc while another process holds the lock")),
 		OPT_END(),
 	};
 
-	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
-
-	if (skip_locked && (argc < 1 || strcmp(argv[0], "gc")))
-		die(_("the option '%s' requires '%s'"), "--skip-locked", "gc");
+	argc = parse_options(argc, argv, prefix, options, rerere_usage,
+			     PARSE_OPT_KEEP_UNKNOWN_OPT);
 
 	repo_config(repo, git_xmerge_config, NULL);
 
@@ -215,11 +234,10 @@ int cmd_rerere(int argc,
 
 	if (!strcmp(argv[0], "forget"))
 		return cmd_rerere_forget(argc, argv, prefix, repo);
-	else if (!strcmp(argv[0], "clear")) {
+	else if (!strcmp(argv[0], "clear"))
 		return cmd_rerere_clear(argc, argv, prefix, repo);
-	} else if (!strcmp(argv[0], "gc"))
-		rerere_gc(repo, &merge_rr,
-			  skip_locked ? RERERE_NOWAIT : 0);
+	else if (!strcmp(argv[0], "gc"))
+		return cmd_rerere_gc(argc, argv, prefix, repo);
 	else if (!strcmp(argv[0], "status"))
 		return cmd_rerere_status(argc, argv, prefix, repo);
 	else if (!strcmp(argv[0], "remaining"))
@@ -229,6 +247,5 @@ int cmd_rerere(int argc,
 	else
 		usage_with_options(rerere_usage, options);
 
-	string_list_clear(&merge_rr, 1);
 	return 0;
 }
diff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh
index ab412ecc6e8..0d4cbf5114a 100755
--- a/t/t4200-rerere.sh
+++ b/t/t4200-rerere.sh
@@ -263,11 +263,6 @@ test_expect_success 'gc --skip-locked prunes while MERGE_RR is not locked' '
 	test_path_is_missing $rr2/preimage
 '
 
-test_expect_success '--skip-locked is only accepted by gc' '
-	test_must_fail git rerere --skip-locked clear 2>err &&
-	test_grep "option .--skip-locked. requires .gc." err
-'
-
 test_expect_success 'a held lock is waited out within rerere.lockTimeout' '
 	git reset --hard &&
 	rm -rf $rr &&

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 11/12] builtin/rerere: use `OPT_SUBCOMMAND()`
  2026-10-29  9:57 ` [PATCH v2 " Patrick Steinhardt
                     ` (9 preceding siblings ...)
  2026-10-29  9:57   ` [PATCH v2 10/12] builtin/rerere: turn "gc" " Patrick Steinhardt
@ 2026-10-29  9:57   ` Patrick Steinhardt
  2026-10-29  9:57   ` [PATCH v2 12/12] builtin/rerere: introduce proper usage strings for subcommands Patrick Steinhardt
  11 siblings, 0 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-29  9:57 UTC (permalink / raw)
  To: git; +Cc: Mark C. Chu-Carroll, Junio C Hamano

In git-rerere(1) we have our own custom logic to parse subcommands. In
preceding commits we have refactored all of those subcommands so that
they are ready to be used as `OPT_SUBCOMMAND()`s. Switch over to use
these so that we get rid of our custom subcommand parsing.

Unfortunately this means that we have a backwards-incompatible change
because we no longer accept the "--[no-]rerere-autoupdate" flag for all
subcommands. But this was arguably broken and misleading as we ignore
this flag for any other subcommand aynway. So we cheat a bit and simply
call it a bug that those flags were accepted in the first place.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/rerere.c | 84 +++++++++++++++++++++++++++++++++++++-------------------
 1 file changed, 56 insertions(+), 28 deletions(-)

diff --git a/builtin/rerere.c b/builtin/rerere.c
index 3162b074ec6..97c791e420b 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -5,6 +5,7 @@
 #include "rerere.h"
 #include "strbuf.h"
 #include "string-list.h"
+#include "strvec.h"
 #include "xdiff/xdiff.h"
 #include "xdiff-interface.h"
 #include "pathspec.h"
@@ -152,6 +153,31 @@ static int cmd_rerere_gc(int argc,
 	return 0;
 }
 
+static int cmd_rerere_perform(int argc,
+			      const char **argv,
+			      const char *prefix,
+			      struct repository *repo)
+{
+	int autoupdate = -1;
+	struct option options[] = {
+		OPT_SET_INT(0, "rerere-autoupdate", &autoupdate,
+			N_("register clean resolutions in index"), 1),
+		OPT_END(),
+	};
+	enum rerere_flags flags = 0;
+
+	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	if (argc)
+		usage(_("too many arguments"));
+
+	if (autoupdate == 1)
+		flags = RERERE_AUTOUPDATE;
+	if (autoupdate == 0)
+		flags = RERERE_NOAUTOUPDATE;
+
+	return rerere(repo, flags);
+}
+
 static int cmd_rerere_remaining(int argc,
 				const char **argv,
 				const char *prefix,
@@ -211,41 +237,43 @@ int cmd_rerere(int argc,
 	       const char *prefix,
 	       struct repository *repo)
 {
-	int autoupdate = -1, flags = 0;
-
+	parse_opt_subcommand_fn *fn = NULL;
 	struct option options[] = {
-		OPT_SET_INT(0, "rerere-autoupdate", &autoupdate,
-			N_("register clean resolutions in index"), 1),
+		OPT_SUBCOMMAND("clear", &fn, cmd_rerere_clear),
+		OPT_SUBCOMMAND("diff", &fn, cmd_rerere_diff),
+		OPT_SUBCOMMAND("forget", &fn, cmd_rerere_forget),
+		OPT_SUBCOMMAND("gc", &fn, cmd_rerere_gc),
+		OPT_SUBCOMMAND("remaining", &fn, cmd_rerere_remaining),
+		OPT_SUBCOMMAND("status", &fn, cmd_rerere_status),
 		OPT_END(),
 	};
 
 	argc = parse_options(argc, argv, prefix, options, rerere_usage,
-			     PARSE_OPT_KEEP_UNKNOWN_OPT);
-
+			     PARSE_OPT_KEEP_UNKNOWN_OPT | PARSE_OPT_SUBCOMMAND_OPTIONAL);
 	repo_config(repo, git_xmerge_config, NULL);
 
-	if (autoupdate == 1)
-		flags = RERERE_AUTOUPDATE;
-	if (autoupdate == 0)
-		flags = RERERE_NOAUTOUPDATE;
+	if (!fn) {
+		struct strvec args = STRVEC_INIT;
+		const char **args_copy;
+		int ret;
 
-	if (argc < 1)
-		return rerere(repo, flags);
-
-	if (!strcmp(argv[0], "forget"))
-		return cmd_rerere_forget(argc, argv, prefix, repo);
-	else if (!strcmp(argv[0], "clear"))
-		return cmd_rerere_clear(argc, argv, prefix, repo);
-	else if (!strcmp(argv[0], "gc"))
-		return cmd_rerere_gc(argc, argv, prefix, repo);
-	else if (!strcmp(argv[0], "status"))
-		return cmd_rerere_status(argc, argv, prefix, repo);
-	else if (!strcmp(argv[0], "remaining"))
-		return cmd_rerere_remaining(argc, argv, prefix, repo);
-	else if (!strcmp(argv[0], "diff"))
-		return cmd_rerere_diff(argc, argv, prefix, repo);
-	else
-		usage_with_options(rerere_usage, options);
+		strvec_push(&args, "perform");
+		strvec_pushv(&args, argv);
 
-	return 0;
+		/*
+		 * We need to copy the array as it will be modified by
+		 * `parse_options()`, and if so we leak the strings
+		 * referenced by `args`.
+		 */
+		CALLOC_ARRAY(args_copy, args.nr);
+		COPY_ARRAY(args_copy, args.v, args.nr);
+
+		ret = cmd_rerere_perform(args.nr, args_copy, prefix, repo);
+
+		strvec_clear(&args);
+		free(args_copy);
+		return ret;
+	}
+
+	return fn(argc, argv, prefix, repo);
 }

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

* [PATCH v2 12/12] builtin/rerere: introduce proper usage strings for subcommands
  2026-10-29  9:57 ` [PATCH v2 " Patrick Steinhardt
                     ` (10 preceding siblings ...)
  2026-10-29  9:57   ` [PATCH v2 11/12] builtin/rerere: use `OPT_SUBCOMMAND()` Patrick Steinhardt
@ 2026-10-29  9:57   ` Patrick Steinhardt
  11 siblings, 0 replies; 35+ messages in thread
From: Patrick Steinhardt @ 2026-10-29  9:57 UTC (permalink / raw)
  To: git; +Cc: Mark C. Chu-Carroll, Junio C Hamano

Adapt git-rerere(1) to have proper usage strings for each of the
subcommands.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 Documentation/git-rerere.adoc |  8 +++++-
 builtin/rerere.c              | 61 ++++++++++++++++++++++++++++++++++++-------
 2 files changed, 58 insertions(+), 11 deletions(-)

diff --git a/Documentation/git-rerere.adoc b/Documentation/git-rerere.adoc
index 4e6ab9a27c9..e04e9ef3ef8 100644
--- a/Documentation/git-rerere.adoc
+++ b/Documentation/git-rerere.adoc
@@ -8,7 +8,13 @@ git-rerere - Reuse recorded resolution of conflicted merges
 SYNOPSIS
 --------
 [verse]
-'git rerere' [clear | forget <pathspec>... | diff | status | remaining | gc]
+'git rerere' [--[no-]rerere-autoupdate]
+'git rerere' clear
+'git rerere' diff
+'git rerere' forget <pathspec>...
+'git rerere' gc
+'git rerere' remaining
+'git rerere' status
 
 DESCRIPTION
 -----------
diff --git a/builtin/rerere.c b/builtin/rerere.c
index 97c791e420b..051e50838ce 100644
--- a/builtin/rerere.c
+++ b/builtin/rerere.c
@@ -10,10 +10,13 @@
 #include "xdiff-interface.h"
 #include "pathspec.h"
 
-static const char * const rerere_usage[] = {
-	N_("git rerere [clear | forget <pathspec>... | diff | status | remaining | gc]"),
-	NULL,
-};
+#define RERERE_USAGE N_("git rerere [--[no-]rerere-autoupdate]")
+#define RERERE_CLEAR_USAGE N_("git rerere clear")
+#define RERERE_DIFF_USAGE N_("git rerere diff")
+#define RERERE_FORGET_USAGE N_("git rerere forget <pathspec>...")
+#define RERERE_GC_USAGE N_("git rerere gc")
+#define RERERE_REMAINING_USAGE N_("git rerere remaining")
+#define RERERE_STATUS_USAGE N_("git rerere status")
 
 static int outf(void *dummy UNUSED, mmfile_t *ptr, int nbuf)
 {
@@ -61,9 +64,13 @@ static int cmd_rerere_clear(int argc,
 	struct option options[] = {
 		OPT_END(),
 	};
+	const char * const rerere_clear_usage[] = {
+		RERERE_CLEAR_USAGE,
+		NULL,
+	};
 	struct string_list merge_rr = STRING_LIST_INIT_DUP;
 
-	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	argc = parse_options(argc, argv, prefix, options, rerere_clear_usage, 0);
 	if (argc)
 		usage(_("too many arguments"));
 
@@ -81,11 +88,15 @@ static int cmd_rerere_diff(int argc,
 	struct option options[] = {
 		OPT_END(),
 	};
+	const char * const rerere_diff_usage[] = {
+		RERERE_DIFF_USAGE,
+		NULL,
+	};
 	struct string_list merge_rr = STRING_LIST_INIT_DUP;
 	struct strbuf buf = STRBUF_INIT;
 	struct rerere_cache cache;
 
-	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	argc = parse_options(argc, argv, prefix, options, rerere_diff_usage, 0);
 	if (argc)
 		usage(_("too many arguments"));
 
@@ -115,10 +126,14 @@ static int cmd_rerere_forget(int argc,
 	struct option options[] = {
 		OPT_END(),
 	};
+	const char * const rerere_forget_usage[] = {
+		RERERE_FORGET_USAGE,
+		NULL,
+	};
 	struct pathspec pathspec;
 	int ret;
 
-	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	argc = parse_options(argc, argv, prefix, options, rerere_forget_usage, 0);
 	if (!argc)
 		warning(_("'git rerere forget' without paths is deprecated"));
 	parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD, prefix, argv);
@@ -140,9 +155,13 @@ static int cmd_rerere_gc(int argc,
 				N_("skip gc while another process holds the lock")),
 		OPT_END(),
 	};
+	const char * const rerere_gc_usage[] = {
+		RERERE_GC_USAGE,
+		NULL,
+	};
 	struct string_list merge_rr = STRING_LIST_INIT_DUP;
 
-	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	argc = parse_options(argc, argv, prefix, options, rerere_gc_usage, 0);
 	if (argc)
 		usage(_("too many arguments"));
 
@@ -164,6 +183,10 @@ static int cmd_rerere_perform(int argc,
 			N_("register clean resolutions in index"), 1),
 		OPT_END(),
 	};
+	const char * const rerere_usage[] = {
+		RERERE_USAGE,
+		NULL,
+	};
 	enum rerere_flags flags = 0;
 
 	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
@@ -186,9 +209,13 @@ static int cmd_rerere_remaining(int argc,
 	struct option options[] = {
 		OPT_END(),
 	};
+	const char * const rerere_remaining_usage[] = {
+		RERERE_REMAINING_USAGE,
+		NULL,
+	};
 	struct string_list merge_rr = STRING_LIST_INIT_DUP;
 
-	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	argc = parse_options(argc, argv, prefix, options, rerere_remaining_usage, 0);
 	if (argc)
 		usage(_("too many arguments"));
 
@@ -214,10 +241,14 @@ static int cmd_rerere_status(int argc,
 	struct option options[] = {
 		OPT_END(),
 	};
+	const char * const rerere_status_usage[] = {
+		RERERE_STATUS_USAGE,
+		NULL,
+	};
 	struct string_list merge_rr = STRING_LIST_INIT_DUP;
 	struct rerere_cache cache;
 
-	argc = parse_options(argc, argv, prefix, options, rerere_usage, 0);
+	argc = parse_options(argc, argv, prefix, options, rerere_status_usage, 0);
 	if (argc)
 		usage(_("too many arguments"));
 
@@ -247,6 +278,16 @@ int cmd_rerere(int argc,
 		OPT_SUBCOMMAND("status", &fn, cmd_rerere_status),
 		OPT_END(),
 	};
+	const char * const rerere_usage[] = {
+		RERERE_USAGE,
+		RERERE_CLEAR_USAGE,
+		RERERE_DIFF_USAGE,
+		RERERE_FORGET_USAGE,
+		RERERE_GC_USAGE,
+		RERERE_REMAINING_USAGE,
+		RERERE_STATUS_USAGE,
+		NULL,
+	};
 
 	argc = parse_options(argc, argv, prefix, options, rerere_usage,
 			     PARSE_OPT_KEEP_UNKNOWN_OPT | PARSE_OPT_SUBCOMMAND_OPTIONAL);

-- 
2.56.0.341.g393f52fd96.dirty


^ permalink raw reply related	[flat|nested] 35+ messages in thread

end of thread, other threads:[~2026-10-29  9:57 UTC | newest]

Thread overview: 35+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-27 10:46 [PATCH 00/12] builtin/rerere: convert to use subcommands Patrick Steinhardt
2026-10-27 10:46 ` [PATCH 01/12] rerere: rename `repo_rerere()` Patrick Steinhardt
2026-10-27 19:48   ` Junio C Hamano
2026-10-28  8:33     ` Patrick Steinhardt
2026-10-28 16:25       ` Junio C Hamano
2026-10-27 10:46 ` [PATCH 02/12] rerere: convert flags into an enum Patrick Steinhardt
2026-10-27 14:47   ` Mark C. Chu-Carroll
2026-10-27 19:40     ` Junio C Hamano
2026-10-28  8:33       ` Patrick Steinhardt
2026-10-28 14:11         ` Mark C. Chu-Carroll
2026-10-28 16:25         ` Junio C Hamano
2026-10-27 10:46 ` [PATCH 03/12] builtin/rerere: drop `the_repository` Patrick Steinhardt
2026-10-27 10:46 ` [PATCH 04/12] builtin/rerere: stop passing flags to subcommands that ignore them Patrick Steinhardt
2026-10-27 10:46 ` [PATCH 05/12] builtin/rerere: turn "forget" into a proper subcommand Patrick Steinhardt
2026-10-27 10:46 ` [PATCH 06/12] builtin/rerere: turn "clear" " Patrick Steinhardt
2026-10-27 10:46 ` [PATCH 07/12] builtin/rerere: turn "status" " Patrick Steinhardt
2026-10-27 10:46 ` [PATCH 08/12] builtin/rerere: turn "remaining" " Patrick Steinhardt
2026-10-27 10:46 ` [PATCH 09/12] builtin/rerere: turn "diff" " Patrick Steinhardt
2026-10-27 10:46 ` [PATCH 10/12] builtin/rerere: turn "gc" " Patrick Steinhardt
2026-10-27 10:46 ` [PATCH 11/12] builtin/rerere: use `OPT_SUBCOMMAND()` Patrick Steinhardt
2026-10-27 10:46 ` [PATCH 12/12] builtin/rerere: introduce proper usage strings for subcommands Patrick Steinhardt
2026-10-27 19:52 ` [PATCH 00/12] builtin/rerere: convert to use subcommands Junio C Hamano
2026-10-29  9:57 ` [PATCH v2 " Patrick Steinhardt
2026-10-29  9:57   ` [PATCH v2 01/12] rerere: rename `repo_rerere()` Patrick Steinhardt
2026-10-29  9:57   ` [PATCH v2 02/12] rerere: convert flags into an enum Patrick Steinhardt
2026-10-29  9:57   ` [PATCH v2 03/12] builtin/rerere: drop `the_repository` Patrick Steinhardt
2026-10-29  9:57   ` [PATCH v2 04/12] builtin/rerere: stop passing flags to subcommands that ignore them Patrick Steinhardt
2026-10-29  9:57   ` [PATCH v2 05/12] builtin/rerere: turn "forget" into a proper subcommand Patrick Steinhardt
2026-10-29  9:57   ` [PATCH v2 06/12] builtin/rerere: turn "clear" " Patrick Steinhardt
2026-10-29  9:57   ` [PATCH v2 07/12] builtin/rerere: turn "status" " Patrick Steinhardt
2026-10-29  9:57   ` [PATCH v2 08/12] builtin/rerere: turn "remaining" " Patrick Steinhardt
2026-10-29  9:57   ` [PATCH v2 09/12] builtin/rerere: turn "diff" " Patrick Steinhardt
2026-10-29  9:57   ` [PATCH v2 10/12] builtin/rerere: turn "gc" " Patrick Steinhardt
2026-10-29  9:57   ` [PATCH v2 11/12] builtin/rerere: use `OPT_SUBCOMMAND()` Patrick Steinhardt
2026-10-29  9:57   ` [PATCH v2 12/12] builtin/rerere: introduce proper usage strings for subcommands Patrick Steinhardt

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox