From d421afa0c66ec6bbd15602d534f77546c516f2a5 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 10 Dec 2018 11:04:58 -0800 Subject: rebase: introduce --reschedule-failed-exec A common use case for the `--exec` option is to verify that each commit in a topic branch compiles cleanly, via `git rebase -x make `. However, when an `exec` in such a rebase fails, it is not re-scheduled, which in this instance is not particularly helpful. Let's offer a flag to reschedule failed `exec` commands. Based on an idea by Paul Morelle. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Documentation/git-rebase.txt | 5 +++++ builtin/rebase--interactive.c | 2 ++ builtin/rebase.c | 16 +++++++++++++++- git-legacy-rebase.sh | 16 +++++++++++++++- git-rebase--common.sh | 1 + sequencer.c | 13 ++++++++++--- sequencer.h | 1 + t/t3418-rebase-continue.sh | 6 ++++++ 8 files changed, 55 insertions(+), 5 deletions(-) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index dff17b3178..174be5b1f0 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -501,6 +501,11 @@ See also INCOMPATIBLE OPTIONS below. with care: the final stash application after a successful rebase might result in non-trivial conflicts. +--reschedule-failed-exec:: +--no-reschedule-failed-exec:: + Automatically reschedule `exec` commands that failed. This only makes + sense in interactive mode (or when an `--exec` option was provided). + INCOMPATIBLE OPTIONS -------------------- diff --git a/builtin/rebase--interactive.c b/builtin/rebase--interactive.c index a2ab68ed06..a9ab009fdb 100644 --- a/builtin/rebase--interactive.c +++ b/builtin/rebase--interactive.c @@ -192,6 +192,8 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) OPT_STRING(0, "onto-name", &onto_name, N_("onto-name"), N_("onto name")), OPT_STRING(0, "cmd", &cmd, N_("cmd"), N_("the command to run")), OPT_RERERE_AUTOUPDATE(&opts.allow_rerere_auto), + OPT_BOOL(0, "reschedule-failed-exec", &opts.reschedule_failed_exec, + N_("automatically re-schedule any `exec` that fails")), OPT_END() }; diff --git a/builtin/rebase.c b/builtin/rebase.c index b5c99ec10c..cc33730743 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -104,6 +104,7 @@ struct rebase_options { int rebase_merges, rebase_cousins; char *strategy, *strategy_opts; struct strbuf git_format_patch_opt; + int reschedule_failed_exec; }; static int is_interactive(struct rebase_options *opts) @@ -415,6 +416,8 @@ static int run_specific_rebase(struct rebase_options *opts) argv_array_push(&child.args, opts->gpg_sign_opt); if (opts->signoff) argv_array_push(&child.args, "--signoff"); + if (opts->reschedule_failed_exec) + argv_array_push(&child.args, "--reschedule-failed-exec"); status = run_command(&child); goto finished_rebase; @@ -920,6 +923,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) "strategy")), OPT_BOOL(0, "root", &options.root, N_("rebase all reachable commits up to the root(s)")), + OPT_BOOL(0, "reschedule-failed-exec", + &options.reschedule_failed_exec, + N_("automatically re-schedule any `exec` that fails")), OPT_END(), }; int i; @@ -1216,6 +1222,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) break; } + if (options.reschedule_failed_exec && !is_interactive(&options)) + die(_("--reschedule-failed-exec requires an interactive rebase")); + if (options.git_am_opts.argc) { /* all am options except -q are compatible only with --am */ for (i = options.git_am_opts.argc - 1; i >= 0; i--) @@ -1241,7 +1250,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.flags |= REBASE_FORCE; } - if (options.type == REBASE_PRESERVE_MERGES) + if (options.type == REBASE_PRESERVE_MERGES) { /* * Note: incompatibility with --signoff handled in signoff block above * Note: incompatibility with --interactive is just a strong warning; @@ -1251,6 +1260,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("error: cannot combine '--preserve-merges' with " "'--rebase-merges'")); + if (options.reschedule_failed_exec) + die(_("error: cannot combine '--preserve-merges' with " + "'--reschedule-failed-exec'")); + } + if (options.rebase_merges) { if (strategy_options.nr) die(_("error: cannot combine '--rebase-merges' with " diff --git a/git-legacy-rebase.sh b/git-legacy-rebase.sh index b4c7dbfa57..699fc4da7e 100755 --- a/git-legacy-rebase.sh +++ b/git-legacy-rebase.sh @@ -48,6 +48,7 @@ skip! skip current patch and continue edit-todo! edit the todo list during an interactive rebase quit! abort but keep HEAD where it is show-current-patch! show the patch file being applied or merged +reschedule-failed-exec automatically reschedule failed exec commands " . git-sh-setup set_reflog_action rebase @@ -92,6 +93,7 @@ autosquash= keep_empty= allow_empty_message=--allow-empty-message signoff= +reschedule_failed_exec= test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t case "$(git config --bool commit.gpgsign)" in true) gpg_sign_opt=-S ;; @@ -126,6 +128,8 @@ read_basic_state () { signoff="$(cat "$state_dir"/signoff)" force_rebase=t } + test -f "$state_dir"/reschedule-failed-exec && + reschedule_failed_exec=t } finish_rebase () { @@ -163,7 +167,8 @@ run_interactive () { "$allow_empty_message" "$autosquash" "$verbose" \ "$force_rebase" "$onto_name" "$head_name" "$strategy" \ "$strategy_opts" "$cmd" "$switch_to" \ - "$allow_rerere_autoupdate" "$gpg_sign_opt" "$signoff" + "$allow_rerere_autoupdate" "$gpg_sign_opt" "$signoff" \ + "$reschedule_failed_exec" } run_specific_rebase () { @@ -378,6 +383,12 @@ do --gpg-sign=*) gpg_sign_opt="-S${1#--gpg-sign=}" ;; + --reschedule-failed-exec) + reschedule_failed_exec=--reschedule-failed-exec + ;; + --no-reschedule-failed-exec) + reschedule_failed_exec= + ;; --) shift break @@ -534,6 +545,9 @@ then # git-rebase.txt caveats with "unless you know what you are doing" test -n "$rebase_merges" && die "$(gettext "error: cannot combine '--preserve-merges' with '--rebase-merges'")" + + test -n "$reschedule_failed_exec" && + die "$(gettext "error: cannot combine '--preserve-merges' with '--reschedule-failed-exec'")" fi if test -n "$rebase_merges" diff --git a/git-rebase--common.sh b/git-rebase--common.sh index 7e39d22871..a8a44608e0 100644 --- a/git-rebase--common.sh +++ b/git-rebase--common.sh @@ -19,6 +19,7 @@ write_basic_state () { "$state_dir"/allow_rerere_autoupdate test -n "$gpg_sign_opt" && echo "$gpg_sign_opt" > "$state_dir"/gpg_sign_opt test -n "$signoff" && echo "$signoff" >"$state_dir"/signoff + test -n "$reschedule_failed_exec" && : > "$state_dir"/reschedule-failed-exec } apply_autostash () { diff --git a/sequencer.c b/sequencer.c index e1a4dd15f1..69bee63e11 100644 --- a/sequencer.c +++ b/sequencer.c @@ -158,6 +158,7 @@ static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy") static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts") static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate") static GIT_PATH_FUNC(rebase_path_quiet, "rebase-merge/quiet") +static GIT_PATH_FUNC(rebase_path_reschedule_failed_exec, "rebase-merge/reschedule-failed-exec") static int git_sequencer_config(const char *k, const char *v, void *cb) { @@ -2362,6 +2363,9 @@ static int read_populate_opts(struct replay_opts *opts) opts->signoff = 1; } + if (file_exists(rebase_path_reschedule_failed_exec())) + opts->reschedule_failed_exec = 1; + read_strategy_opts(opts, &buf); strbuf_release(&buf); @@ -2443,6 +2447,8 @@ int write_basic_state(struct replay_opts *opts, const char *head_name, write_file(rebase_path_gpg_sign_opt(), "-S%s\n", opts->gpg_sign); if (opts->signoff) write_file(rebase_path_signoff(), "--signoff\n"); + if (opts->reschedule_failed_exec) + write_file(rebase_path_reschedule_failed_exec(), "%s", ""); return 0; } @@ -3586,9 +3592,10 @@ static int pick_commits(struct todo_list *todo_list, struct replay_opts *opts) *end_of_arg = saved; /* Reread the todo file if it has changed. */ - if (res) - ; /* fall through */ - else if (stat(get_todo_path(opts), &st)) + if (res) { + if (opts->reschedule_failed_exec) + reschedule = 1; + } else if (stat(get_todo_path(opts), &st)) res = error_errno(_("could not stat '%s'"), get_todo_path(opts)); else if (match_stat_data(&todo_list->stat, &st)) { diff --git a/sequencer.h b/sequencer.h index 5071a73563..1f865dae26 100644 --- a/sequencer.h +++ b/sequencer.h @@ -39,6 +39,7 @@ struct replay_opts { int allow_empty_message; int keep_redundant_commits; int verbose; + int reschedule_failed_exec; int mainline; diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh index 0210b2ac6f..54b26a9284 100755 --- a/t/t3418-rebase-continue.sh +++ b/t/t3418-rebase-continue.sh @@ -254,4 +254,10 @@ test_expect_success 'the todo command "break" works' ' test_path_is_file execed ' +test_expect_success '--reschedule-failed-exec' ' + test_when_finished "git rebase --abort" && + test_must_fail git rebase -x false --reschedule-failed-exec HEAD^ && + grep "^exec false" .git/rebase-merge/git-rebase-todo +' + test_done -- cgit v1.2.3 From 969de3ff0e01282cc061892f47b5f04b22de76af Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 10 Dec 2018 11:04:59 -0800 Subject: rebase: add a config option to default to --reschedule-failed-exec It would be cumbersome to type out that option all the time, so let's offer the convenience of a config setting: rebase.rescheduleFailedExec. Besides, this opens the door to changing the default in a future version of Git: it does make some sense to reschedule failed `exec` commands by default (and if we could go back in time when the `exec` command was invented, we probably would change that default right from the start). Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Documentation/config/rebase.txt | 5 +++++ builtin/rebase.c | 5 +++++ git-legacy-rebase.sh | 2 ++ t/t3418-rebase-continue.sh | 7 ++++++- 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Documentation/config/rebase.txt b/Documentation/config/rebase.txt index f079bf6b7e..331d250e04 100644 --- a/Documentation/config/rebase.txt +++ b/Documentation/config/rebase.txt @@ -64,3 +64,8 @@ instead of: ------------------------------------------- + Defaults to false. + +rebase.rescheduleFailedExec:: + Automatically reschedule `exec` commands that failed. This only makes + sense in interactive mode (or when an `--exec` option was provided). + This is the same as specifying the `--reschedule-failed-exec` option. diff --git a/builtin/rebase.c b/builtin/rebase.c index cc33730743..4839e52555 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -677,6 +677,11 @@ static int rebase_config(const char *var, const char *value, void *data) return 0; } + if (!strcmp(var, "rebase.reschedulefailedexec")) { + opts->reschedule_failed_exec = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, data); } diff --git a/git-legacy-rebase.sh b/git-legacy-rebase.sh index 699fc4da7e..37db5a7ca4 100755 --- a/git-legacy-rebase.sh +++ b/git-legacy-rebase.sh @@ -99,6 +99,8 @@ case "$(git config --bool commit.gpgsign)" in true) gpg_sign_opt=-S ;; *) gpg_sign_opt= ;; esac +test "$(git config --bool rebase.reschedulefailedexec)" = "true" && +reschedule_failed_exec=--reschedule-failed-exec . git-rebase--common read_basic_state () { diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh index 54b26a9284..bdaa511bb0 100755 --- a/t/t3418-rebase-continue.sh +++ b/t/t3418-rebase-continue.sh @@ -257,7 +257,12 @@ test_expect_success 'the todo command "break" works' ' test_expect_success '--reschedule-failed-exec' ' test_when_finished "git rebase --abort" && test_must_fail git rebase -x false --reschedule-failed-exec HEAD^ && - grep "^exec false" .git/rebase-merge/git-rebase-todo + grep "^exec false" .git/rebase-merge/git-rebase-todo && + git rebase --abort && + test_must_fail git -c rebase.rescheduleFailedExec=true \ + rebase -x false HEAD^ 2>err && + grep "^exec false" .git/rebase-merge/git-rebase-todo && + test_i18ngrep "has been rescheduled" err ' test_done -- cgit v1.2.3 From 81ef8ee75d5f348d3c71ff633d13d302124e1a5e Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 10 Dec 2018 11:05:01 -0800 Subject: rebase: introduce a shortcut for --reschedule-failed-exec It is a bit cumbersome to write out the `--reschedule-failed-exec` option before `-x ` all the time; let's introduce a convenient option to do both at the same time: `-y `. Signed-off-by: Johannes Schindelin Signed-off-by: Junio C Hamano --- Documentation/git-rebase.txt | 6 ++++++ builtin/rebase.c | 21 +++++++++++++++++++++ git-legacy-rebase.sh | 6 ++++++ t/t3418-rebase-continue.sh | 3 +++ 4 files changed, 36 insertions(+) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 174be5b1f0..98f60b2be0 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -462,6 +462,12 @@ without an explicit `--interactive`. + See also INCOMPATIBLE OPTIONS below. +-y :: + This is the same as passing `--reschedule-failed-exec` before + `-x `, i.e. it appends the specified `exec` command and + turns on the mode where failed `exec` commands are automatically + rescheduled. + --root:: Rebase all commits reachable from , instead of limiting them with an . This allows you to rebase diff --git a/builtin/rebase.c b/builtin/rebase.c index 4839e52555..1963076f60 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -754,6 +754,23 @@ static int parse_opt_interactive(const struct option *opt, const char *arg, return 0; } +struct opt_y { + struct string_list *list; + struct rebase_options *options; +}; + +static int parse_opt_y(const struct option *opt, const char *arg, int unset) +{ + struct opt_y *o = opt->value; + + if (unset || !arg) + return -1; + + o->options->reschedule_failed_exec = 1; + string_list_append(o->list, arg); + return 0; +} + static void NORETURN error_on_missing_default_upstream(void) { struct branch *current_branch = branch_get(NULL); @@ -834,6 +851,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) struct string_list strategy_options = STRING_LIST_INIT_NODUP; struct object_id squash_onto; char *squash_onto_name = NULL; + struct opt_y opt_y = { .list = &exec, .options = &options }; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -911,6 +929,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_STRING_LIST('x', "exec", &exec, N_("exec"), N_("add exec lines after each commit of the " "editable list")), + { OPTION_CALLBACK, 'y', NULL, &opt_y, N_(""), + N_("same as --reschedule-failed-exec -x "), + PARSE_OPT_NONEG, parse_opt_y }, OPT_BOOL(0, "allow-empty-message", &options.allow_empty_message, N_("allow rebasing commits with empty messages")), diff --git a/git-legacy-rebase.sh b/git-legacy-rebase.sh index 37db5a7ca4..3bb0682db5 100755 --- a/git-legacy-rebase.sh +++ b/git-legacy-rebase.sh @@ -26,6 +26,7 @@ f,force-rebase! cherry-pick all commits, even if unchanged m,merge! use merging strategies to rebase i,interactive! let the user edit the list of commits to rebase x,exec=! add exec lines after each commit of the editable list +y=! same as --reschedule-failed-exec -x k,keep-empty preserve empty commits during rebase allow-empty-message allow rebasing commits with empty messages stat! display a diffstat of what changed upstream @@ -262,6 +263,11 @@ do cmd="${cmd}exec ${1#--exec=}${LF}" test -z "$interactive_rebase" && interactive_rebase=implied ;; + -y*) + reschedule_failed_exec=--reschedule-failed-exec + cmd="${cmd}exec ${1#-y}${LF}" + test -z "$interactive_rebase" && interactive_rebase=implied + ;; --interactive) interactive_rebase=explicit ;; diff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh index bdaa511bb0..25aaacacfc 100755 --- a/t/t3418-rebase-continue.sh +++ b/t/t3418-rebase-continue.sh @@ -262,6 +262,9 @@ test_expect_success '--reschedule-failed-exec' ' test_must_fail git -c rebase.rescheduleFailedExec=true \ rebase -x false HEAD^ 2>err && grep "^exec false" .git/rebase-merge/git-rebase-todo && + test_i18ngrep "has been rescheduled" err && + git rebase --abort && + test_must_fail git rebase -y false HEAD^ 2>err && test_i18ngrep "has been rescheduled" err ' -- cgit v1.2.3