diff --git a/config.c b/config.c index d9019e7e6c34b0..e0bb29b53d3ca3 100644 --- a/config.c +++ b/config.c @@ -450,18 +450,24 @@ static int git_config_include(const char *var, const char *value, return ret; } +void git_config_append_parameter(struct strbuf *env, const char *key, + const char *value) +{ + if (env->len) + strbuf_addch(env, ' '); + sq_quote_buf(env, key); + strbuf_addch(env, '='); + if (value) + sq_quote_buf(env, value); +} + static void git_config_push_split_parameter(const char *key, const char *value) { struct strbuf env = STRBUF_INIT; const char *old = getenv(CONFIG_DATA_ENVIRONMENT); - if (old && *old) { + if (old && *old) strbuf_addstr(&env, old); - strbuf_addch(&env, ' '); - } - sq_quote_buf(&env, key); - strbuf_addch(&env, '='); - if (value) - sq_quote_buf(&env, value); + git_config_append_parameter(&env, key, value); setenv(CONFIG_DATA_ENVIRONMENT, env.buf, 1); strbuf_release(&env); } diff --git a/config.h b/config.h index b66dd08007c97a..fcf48f62455886 100644 --- a/config.h +++ b/config.h @@ -22,6 +22,7 @@ */ struct object_id; +struct strbuf; /* git_config_parse_key() returns these negated: */ #define CONFIG_INVALID_KEY 1 @@ -186,6 +187,15 @@ int git_config_from_blob_oid(config_fn_t fn, const char *name, enum config_scope scope); void git_config_push_parameter(const char *text); void git_config_push_env(const char *spec); + +/* + * Append `key=value` to the GIT_CONFIG_PARAMETERS value in `env`, quoted + * the way git_config_from_parameters() reads it, so that a child can be + * given configuration on top of what this process was given. A NULL + * `value` appends a boolean entry. + */ +void git_config_append_parameter(struct strbuf *env, const char *key, + const char *value); int git_config_from_parameters(config_fn_t fn, void *data); /* diff --git a/sequencer.c b/sequencer.c index 65afd100d98e61..5df07750a7fb07 100644 --- a/sequencer.c +++ b/sequencer.c @@ -234,6 +234,11 @@ 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) @@ -242,6 +247,7 @@ 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; } @@ -407,6 +413,7 @@ 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) @@ -1107,6 +1114,27 @@ static int run_command_silent_on_success(struct child_process *cmd) return rc; } +/* + * 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 sequencer for locks and files it still holds. + */ +static void disable_auto_maintenance(struct replay_opts *opts, + struct child_process *cmd) +{ + struct strbuf *params = &opts->ctx->config_parameters; + + 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); +} + /* * If we are cherry-pick, and if the merge did not result in * hand-editing, we will hit this commit and inherit the original @@ -1148,6 +1176,7 @@ 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="); + disable_auto_maintenance(opts, &cmd); strvec_push(&cmd.args, "commit"); @@ -3924,16 +3953,18 @@ 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(opts, &cmd); status = run_command(&cmd); /* force re-reading of the cache */ @@ -4342,6 +4373,7 @@ 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(opts, &cmd); cmd.git_cmd = 1; strvec_push(&cmd.args, "merge"); @@ -5158,7 +5190,7 @@ 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) { @@ -5313,6 +5345,12 @@ static int pick_commits(struct repository *r, 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 @@ -5329,6 +5367,7 @@ 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"); /* @@ -5577,10 +5616,14 @@ int sequencer_continue(struct repository *r, struct replay_opts *opts) res = -1; goto release_todo_list; } - } 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 */ @@ -5698,6 +5741,8 @@ 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; } diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh index cb5c3a1cb5bc6f..cf6d20ce797b0b 100755 --- a/t/t3418-rebase-continue.sh +++ b/t/t3418-rebase-continue.sh @@ -395,4 +395,30 @@ test_orig_head () { test_orig_head --apply test_orig_head --merge +test_expect_success 'rebase runs auto maintenance at its end' ' + git checkout -b one-exec main^ && + test_commit F4 && + test_must_fail git rebase -x false main && + GIT_TRACE2_EVENT="$(pwd)/finish.txt" git rebase --continue && + test_subcommand_flex git maintenance run --auto F2 && + git add F2 && + test_must_fail env GIT_TRACE2_EVENT="$(pwd)/mid.txt" \ + git rebase --continue && + test_subcommand_flex git commit F2 && + git add F2 && + GIT_TRACE2_EVENT="$(pwd)/end.txt" git rebase --continue && + test_subcommand_flex git maintenance run --auto maintenance && + test_line_count = 1 maintenance +' + test_done diff --git a/t/t3510-cherry-pick-sequence.sh b/t/t3510-cherry-pick-sequence.sh index 5777dff4964381..57a77d91bd4d5d 100755 --- a/t/t3510-cherry-pick-sequence.sh +++ b/t/t3510-cherry-pick-sequence.sh @@ -721,4 +721,31 @@ 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 maintenance && + 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 foo && + git add foo && + GIT_TRACE2_EVENT="$(pwd)/end.txt" git cherry-pick --continue && + test_subcommand_flex git commit maintenance && + test_line_count = 1 maintenance +' + test_done