Thread (12 messages) flat view 12 messages, 4 authors, 1d ago
WARM1d

[PATCH v2 0/3] sequencer: leave auto maintenance to the end of a sequence

From: Thomas Bachem via GitGitGadget <hidden>
Date: 2026-09-04 15:51:28

Changes since v1:

 * Cherry-pick and revert end their sequences with auto maintenance too, and
   keep it out of the "git commit" they spawn, so the three commands now
   behave the same (Phillip).
 * The GIT_CONFIG_PARAMETERS value comes from a helper split out of
   git_config_push_split_parameter() in config.c (new patch 1), built once
   per run and kept in replay_ctx (Phillip).
 * Commit messages rewritten, much shorter (Junio, Phillip).

Based on master. Independent of the rerere lock fix in [1].

[1] [ref]

Thomas Bachem (3):
  config: add git_config_append_parameter()
  sequencer: run auto maintenance once a sequence is done
  sequencer: keep auto maintenance out of the commands a sequence spawns

 config.c                        | 20 ++++++++----
 config.h                        | 10 ++++++
 sequencer.c                     | 57 +++++++++++++++++++++++++++++----
 t/t3418-rebase-continue.sh      | 26 +++++++++++++++
 t/t3510-cherry-pick-sequence.sh | 27 ++++++++++++++++
 5 files changed, 127 insertions(+), 13 deletions(-)


base-commit: 3cb9185f65410273787f74333cc027d2ea5daada
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-2217%2Fthomasbachem%2Frebase-auto-maintenance-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2217/thomasbachem/rebase-auto-maintenance-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/2217

Range-diff vs v1:

 -:  ---------- > 1:  ef8087e80d config: add git_config_append_parameter()
 1:  3415a4dcbf ! 2:  baab8d4876 sequencer: run auto maintenance once a rebase is done
     @@ Metadata
      Author: Thomas Bachem [off-list ref]
      
       ## Commit message ##
     -    sequencer: run auto maintenance once a rebase is done
     +    sequencer: run auto maintenance once a sequence is done
      
     -    The apply backend runs "git maintenance run --auto" from
     -    finish_rebase() once it has applied its patches, and so does "git am"
     -    on its own. The merge backend reaches finish_rebase() only on the
     -    paths both backends share in builtin/rebase.c: an abort, a branch
     -    that is already up to date, and a fast-forward. A rebase that
     -    replays commits never runs maintenance at its end. It creates most
     -    of its commits in process, and only the "git commit" it spawns for a
     -    resolved, reworded or squashed pick, the "git merge" a "rebase -r"
     -    spawns for an octopus merge or with a strategy, and whatever an exec
     -    command runs kick maintenance off, in the middle of the rebase. Run
     -    it where the sequencer finishes a rebase, after the autostash is
     -    applied, as finish_rebase() does, so that both backends end a rebase
     -    the same way, and so that the next commit can keep it out of the
     -    commands a rebase spawns. builtin/rebase.c could run it instead once
     -    run_sequencer_rebase() returns, but the sequencer is where the rebase
     -    finishes, and the autostash and the state cleanup that surround the
     -    run in finish_rebase() are there as well. prepare_auto_maintenance()
     -    closes the object database before the spawn, so the sequencer holds
     -    nothing a repack would need to replace.
     +    The apply backend of "git rebase" runs "git maintenance run --auto"
     +    from finish_rebase() once it has applied its patches. The merge
     +    backend, "git cherry-pick" and "git revert" do not run it when they
     +    finish. They create their commits in process, and only the "git
     +    commit" they spawn for an edited message or a resolved conflict, the
     +    "git merge" a "rebase -r" spawns and an exec command start it, in the
     +    middle of the sequence.
     +
     +    Run it where the sequencer finishes, so that every sequence ends the
     +    way the apply backend does, and so that the next commit can keep it
     +    out of the commands a sequence spawns.
      
          Assisted-by: Claude Fable 5.1
          Signed-off-by: Thomas Bachem [off-list ref]
      
       ## sequencer.c ##
      @@ sequencer.c: cleanup_head_ref:
     - 			run_hooks_opt(r, "post-rewrite", &hook_opt);
     + 			return -1;
     + 	}
     + 
     ++	/*
     ++	 * We ignore errors in 'git maintenance run --auto', since the
     ++	 * user should see them.
     ++	 */
     ++	run_auto_maintenance(r, opts->quiet);
     ++
     + 	/*
     + 	 * Sequence of picks finished successfully; cleanup by
     + 	 * removing the .git/sequencer directory
     +@@ sequencer.c: int sequencer_continue(struct repository *r, struct replay_opts *opts)
     + 			res = -1;
     + 			goto release_todo_list;
       		}
     - 		apply_autostash(rebase_path_autostash());
     -+		/*
     -+		 * We ignore errors in 'git maintenance run --auto', since the
     -+		 * user should see them.
     -+		 */
     -+		run_auto_maintenance(r, opts->quiet);
     +-	} else if (!file_exists(get_todo_path(opts)))
     +-		return continue_single_pick(r, opts);
     +-	else if ((res = read_populate_todo(r, &todo_list, opts)))
     ++	} else if (!file_exists(get_todo_path(opts))) {
     ++		res = continue_single_pick(r, opts);
     ++		if (!res)
     ++			run_auto_maintenance(r, opts->quiet);
     ++		return res;
     ++	} else if ((res = read_populate_todo(r, &todo_list, opts))) {
     + 		goto release_todo_list;
     ++	}
     + 
     + 	if (!is_rebase_i(opts)) {
     + 		/* Verify that the conflict has been resolved */
     +@@ sequencer.c: int sequencer_pick_revisions(struct repository *r,
     + 			BUG("unexpected extra commit from walk");
     + 
     + 		res = single_pick(r, cmit, opts);
     ++		if (!res)
     ++			run_auto_maintenance(r, opts->quiet);
     + 		goto out;
     + 	}
       
     - 		if (!opts->quiet) {
     - 			if (!opts->verbose)
      
       ## t/t3418-rebase-continue.sh ##
      @@ t/t3418-rebase-continue.sh: test_orig_head () {
     @@ t/t3418-rebase-continue.sh: test_orig_head () {
      +'
      +
       test_done
     +
     + ## t/t3510-cherry-pick-sequence.sh ##
     +@@ t/t3510-cherry-pick-sequence.sh: test_expect_success 'commit descriptions in insn sheet are optional' '
     + 	test_line_count = 4 commits
     + '
     + 
     ++test_expect_success 'cherry-pick runs auto maintenance once it is done' '
     ++	pristine_detach base &&
     ++	GIT_TRACE2_EVENT="$(pwd)/single.txt" git cherry-pick picked &&
     ++	test_subcommand_flex git maintenance run --auto <single.txt &&
     ++	GIT_TRACE2_EVENT="$(pwd)/sequence.txt" \
     ++		git cherry-pick anotherpick yetanotherpick &&
     ++	grep "\"child_start\".*\"maintenance\"" sequence.txt >maintenance &&
     ++	test_line_count = 1 maintenance
     ++'
     ++
     + test_done
 2:  06d2f0f484 ! 3:  9a6fc0427a sequencer: keep auto maintenance out of the commands a rebase spawns
     @@ Metadata
      Author: Thomas Bachem [off-list ref]
      
       ## Commit message ##
     -    sequencer: keep auto maintenance out of the commands a rebase spawns
     +    sequencer: keep auto maintenance out of the commands a sequence spawns
      
     -    The commands a rebase with the merge backend spawns, the "git commit"
     -    for a resolved, reworded or squashed pick, the "git merge" of a
     -    "rebase -r" for an octopus merge or with a strategy, and whatever an
     -    exec command runs, each kick off "git maintenance run --auto --detach",
     -    a background process the rebase then races for the repository: the
     -    "rerere gc" spawned by the commit of one "git rebase --continue" holds
     -    MERGE_RR.lock while the next pick wants it, and a repack wants to
     -    delete packs the sequencer still had open, which 65cda10d5b
     +    The "git commit" and "git merge" the sequencer spawns, and the git
     +    commands an exec runs, each start "git maintenance run --auto
     +    --detach", which then works in the background against the sequence
     +    itself. A "rerere gc" started by the commit of one "git rebase
     +    --continue" holds MERGE_RR.lock when the next pick needs it, and a
     +    repack deletes packs the sequencer still has open, which 65cda10d5b
          (sequencer: release the ODB before spawning git commit, 2026-08-12)
     -    had to fix for Windows.
     +    had to work around.
      
     -    Nothing a rebase creates is old enough to be pruned by the time it
     -    ends, and repacking what it created can wait until then, so
     -    maintenance in the middle of a rebase has nothing to do that a run at
     -    its end cannot, and a rebase to get in the way of. Pass
     -    maintenance.auto=false and gc.auto=0 to the commands a rebase spawns,
     -    through GIT_CONFIG_PARAMETERS so that the shell of an exec command
     -    passes them on too, appended to whatever -c the user gave, since the
     -    last entry wins. What the user runs while the rebase is stopped, say
     -    "git commit --amend" at an edit, is not the rebase's to control and
     -    still runs it. "git commit" and "git merge" could skip it themselves
     -    while a rebase is in progress, which would cover that too, but that
     -    spreads the rebase's business over every command that runs
     -    maintenance and defers theirs for as long as a rebase is left lying
     -    around, so keep the decision with the rebase, in what it spawns. Both
     -    backends run maintenance once the rebase is done, the merge backend
     -    since the previous commit, so nothing is lost.
     -
     -    Cherry-pick and revert are left alone: they never ran maintenance at
     -    the end of a sequence, and the "git commit" they spawn for a
     -    --continue or an edited message is the only place they run it at all.
     +    The loose objects a sequence creates wait for the run at its end that
     +    the previous commit added. Whether a sequence can be long enough to
     +    suffer from them before that remains to be seen. Pass
     +    maintenance.auto=false and gc.auto=0 to the spawned commands through
     +    GIT_CONFIG_PARAMETERS, which the shell of an exec command hands on to
     +    whatever it runs, appended after the user's own -c settings so that
     +    ours win, and built once per run. A command the user runs while the
     +    sequence is stopped, like "git commit --amend" at an edit, is not the
     +    sequencer's to control and still runs maintenance.
      
          Assisted-by: Claude Fable 5.1
          Signed-off-by: Thomas Bachem [off-list ref]
      
       ## sequencer.c ##
     +@@ sequencer.c: struct replay_ctx {
     + 	 * Whether message contains a commit message.
     + 	 */
     + 	unsigned have_message :1;
     ++	/*
     ++	 * The GIT_CONFIG_PARAMETERS value that keeps auto maintenance out
     ++	 * of the commands we spawn, built on first use.
     ++	 */
     ++	struct strbuf config_parameters;
     + };
     + 
     + struct replay_ctx* replay_ctx_new(void)
     +@@ sequencer.c: struct replay_ctx* replay_ctx_new(void)
     + 
     + 	strbuf_init(&ctx->current_fixups, 0);
     + 	strbuf_init(&ctx->message, 0);
     ++	strbuf_init(&ctx->config_parameters, 0);
     + 
     + 	return ctx;
     + }
     +@@ sequencer.c: static void replay_ctx_release(struct replay_ctx *ctx)
     + {
     + 	strbuf_release(&ctx->current_fixups);
     + 	strbuf_release(&ctx->message);
     ++	strbuf_release(&ctx->config_parameters);
     + }
     + 
     + void replay_opts_release(struct replay_opts *opts)
      @@ sequencer.c: static int run_command_silent_on_success(struct child_process *cmd)
       	return rc;
       }
       
      +/*
     -+ * A rebase runs auto maintenance once it is done, not from every command
     ++ * A sequence runs auto maintenance once it is done, not from every command
      + * it spawns along the way: their background "rerere gc" or repack would
     -+ * race the rebase for locks and files it still holds.
     ++ * race the sequencer for locks and files it still holds.
      + */
     -+static void disable_auto_maintenance(struct child_process *cmd)
     ++static void disable_auto_maintenance(struct replay_opts *opts,
     ++				     struct child_process *cmd)
      +{
     -+	struct strbuf value = STRBUF_INIT;
     -+	const char *old = getenv(CONFIG_DATA_ENVIRONMENT);
     ++	struct strbuf *params = &opts->ctx->config_parameters;
      +
     -+	if (old && *old)
     -+		strbuf_addf(&value, "%s ", old);
     -+	sq_quote_buf(&value, "maintenance.auto");
     -+	strbuf_addch(&value, '=');
     -+	sq_quote_buf(&value, "false");
     -+	strbuf_addch(&value, ' ');
     -+	sq_quote_buf(&value, "gc.auto");
     -+	strbuf_addch(&value, '=');
     -+	sq_quote_buf(&value, "0");
     -+	strvec_pushf(&cmd->env, "%s=%s", CONFIG_DATA_ENVIRONMENT, value.buf);
     -+	strbuf_release(&value);
     ++	if (!params->len) {
     ++		const char *old = getenv(CONFIG_DATA_ENVIRONMENT);
     ++
     ++		if (old && *old)
     ++			strbuf_addstr(params, old);
     ++		git_config_append_parameter(params, "maintenance.auto", "false");
     ++		git_config_append_parameter(params, "gc.auto", "0");
     ++	}
     ++	strvec_pushf(&cmd->env, "%s=%s", CONFIG_DATA_ENVIRONMENT, params->buf);
      +}
      +
       /*
     @@ sequencer.c: static int run_git_commit(const char *defmsg,
       			     author_date_from_env(&cmd.env));
       	if (opts->ignore_date)
       		strvec_push(&cmd.env, "GIT_AUTHOR_DATE=");
     -+	if (is_rebase_i(opts))
     -+		disable_auto_maintenance(&cmd);
     ++	disable_auto_maintenance(opts, &cmd);
       
       	strvec_push(&cmd.args, "commit");
       
     -@@ sequencer.c: static int do_exec(struct repository *r, const char *command_line, int quiet)
     +@@ sequencer.c: static int error_failed_squash(struct repository *r,
     + 	return error_with_patch(r, commit, subject, subject_len, opts, 1, 1);
     + }
     + 
     +-static int do_exec(struct repository *r, const char *command_line, int quiet)
     ++static int do_exec(struct repository *r, const char *command_line,
     ++		   struct replay_opts *opts)
     + {
     + 	struct child_process cmd = CHILD_PROCESS_INIT;
     + 	int dirty, status;
     + 
     +-	if (!quiet)
     ++	if (!opts->quiet)
     + 		fprintf(stderr, _("Executing: %s\n"), command_line);
       	cmd.use_shell = 1;
       	strvec_push(&cmd.args, command_line);
       	strvec_push(&cmd.env, "GIT_CHERRY_PICK_HELP");
     -+	disable_auto_maintenance(&cmd);
     ++	disable_auto_maintenance(opts, &cmd);
       	status = run_command(&cmd);
       
       	/* force re-reading of the cache */
     @@ sequencer.c: static int do_merge(struct repository *r,
       				     author_date_from_env(&cmd.env));
       		if (opts->ignore_date)
       			strvec_push(&cmd.env, "GIT_AUTHOR_DATE=");
     -+		disable_auto_maintenance(&cmd);
     ++		disable_auto_maintenance(opts, &cmd);
       
       		cmd.git_cmd = 1;
       		strvec_push(&cmd.args, "merge");
     +@@ sequencer.c: static int pick_commits(struct repository *r,
     + 			if (!opts->verbose)
     + 				term_clear_line();
     + 			*end_of_arg = '\0';
     +-			res = do_exec(r, arg, opts->quiet);
     ++			res = do_exec(r, arg, opts);
     + 			*end_of_arg = saved;
     + 
     + 			if (res) {
     +@@ sequencer.c: static int continue_single_pick(struct repository *r, struct replay_opts *opts)
     + 		return error(_("no cherry-pick or revert in progress"));
     + 
     + 	cmd.git_cmd = 1;
     ++	disable_auto_maintenance(opts, &cmd);
     + 	strvec_push(&cmd.args, "commit");
     + 
     + 	/*
      
       ## t/t3418-rebase-continue.sh ##
      @@ t/t3418-rebase-continue.sh: test_expect_success 'rebase runs auto maintenance at its end' '
     @@ t/t3418-rebase-continue.sh: test_expect_success 'rebase runs auto maintenance at
      +'
      +
       test_done
     +
     + ## t/t3510-cherry-pick-sequence.sh ##
     +@@ t/t3510-cherry-pick-sequence.sh: test_expect_success 'cherry-pick runs auto maintenance once it is done' '
     + 	test_line_count = 1 maintenance
     + '
     + 
     ++test_expect_success 'cherry-pick spawns no auto maintenance before it is done' '
     ++	pristine_detach initial &&
     ++	test_must_fail git cherry-pick base..anotherpick &&
     ++	echo resolved >foo &&
     ++	git add foo &&
     ++	test_must_fail env GIT_TRACE2_EVENT="$(pwd)/mid.txt" \
     ++		git cherry-pick --continue &&
     ++	test_subcommand_flex git commit <mid.txt &&
     ++	test_subcommand_flex ! git maintenance run --auto <mid.txt &&
     ++	echo d >foo &&
     ++	git add foo &&
     ++	GIT_TRACE2_EVENT="$(pwd)/end.txt" git cherry-pick --continue &&
     ++	test_subcommand_flex git commit <end.txt &&
     ++	grep "\"child_start\".*\"maintenance\"" end.txt >maintenance &&
     ++	test_line_count = 1 maintenance
     ++'
     ++
     + test_done

-- 
gitgitgadget
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help