Merge branch 'js/rebase-i-redo-exec'

"git rebase -i" learned to re-execute a command given with 'exec'
to run after it failed the last time.

* js/rebase-i-redo-exec:
  rebase: introduce a shortcut for --reschedule-failed-exec
  rebase: add a config option to default to --reschedule-failed-exec
  rebase: introduce --reschedule-failed-exec
This commit is contained in:
Junio C Hamano 2019-01-29 12:47:53 -08:00
commit d9d9ab0876
9 changed files with 108 additions and 5 deletions

View File

@ -64,3 +64,8 @@ instead of:
------------------------------------------- -------------------------------------------
+ +
Defaults to false. 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.

View File

@ -462,6 +462,12 @@ without an explicit `--interactive`.
+ +
See also INCOMPATIBLE OPTIONS below. See also INCOMPATIBLE OPTIONS below.
-y <cmd>::
This is the same as passing `--reschedule-failed-exec` before
`-x <cmd>`, i.e. it appends the specified `exec` command and
turns on the mode where failed `exec` commands are automatically
rescheduled.
--root:: --root::
Rebase all commits reachable from <branch>, instead of Rebase all commits reachable from <branch>, instead of
limiting them with an <upstream>. This allows you to rebase limiting them with an <upstream>. This allows you to rebase
@ -501,6 +507,11 @@ See also INCOMPATIBLE OPTIONS below.
with care: the final stash application after a successful with care: the final stash application after a successful
rebase might result in non-trivial conflicts. 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 INCOMPATIBLE OPTIONS
-------------------- --------------------

View File

@ -193,6 +193,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, "onto-name", &onto_name, N_("onto-name"), N_("onto name")),
OPT_STRING(0, "cmd", &cmd, N_("cmd"), N_("the command to run")), OPT_STRING(0, "cmd", &cmd, N_("cmd"), N_("the command to run")),
OPT_RERERE_AUTOUPDATE(&opts.allow_rerere_auto), 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() OPT_END()
}; };

View File

@ -104,6 +104,7 @@ struct rebase_options {
int rebase_merges, rebase_cousins; int rebase_merges, rebase_cousins;
char *strategy, *strategy_opts; char *strategy, *strategy_opts;
struct strbuf git_format_patch_opt; struct strbuf git_format_patch_opt;
int reschedule_failed_exec;
}; };
static int is_interactive(struct rebase_options *opts) 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); argv_array_push(&child.args, opts->gpg_sign_opt);
if (opts->signoff) if (opts->signoff)
argv_array_push(&child.args, "--signoff"); argv_array_push(&child.args, "--signoff");
if (opts->reschedule_failed_exec)
argv_array_push(&child.args, "--reschedule-failed-exec");
status = run_command(&child); status = run_command(&child);
goto finished_rebase; goto finished_rebase;
@ -674,6 +677,11 @@ static int rebase_config(const char *var, const char *value, void *data)
return 0; 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); return git_default_config(var, value, data);
} }
@ -746,6 +754,23 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,
return 0; 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) static void NORETURN error_on_missing_default_upstream(void)
{ {
struct branch *current_branch = branch_get(NULL); struct branch *current_branch = branch_get(NULL);
@ -826,6 +851,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
struct string_list strategy_options = STRING_LIST_INIT_NODUP; struct string_list strategy_options = STRING_LIST_INIT_NODUP;
struct object_id squash_onto; struct object_id squash_onto;
char *squash_onto_name = NULL; char *squash_onto_name = NULL;
struct opt_y opt_y = { .list = &exec, .options = &options };
struct option builtin_rebase_options[] = { struct option builtin_rebase_options[] = {
OPT_STRING(0, "onto", &options.onto_name, OPT_STRING(0, "onto", &options.onto_name,
N_("revision"), N_("revision"),
@ -903,6 +929,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
OPT_STRING_LIST('x', "exec", &exec, N_("exec"), OPT_STRING_LIST('x', "exec", &exec, N_("exec"),
N_("add exec lines after each commit of the " N_("add exec lines after each commit of the "
"editable list")), "editable list")),
{ OPTION_CALLBACK, 'y', NULL, &opt_y, N_("<cmd>"),
N_("same as --reschedule-failed-exec -x <cmd>"),
PARSE_OPT_NONEG, parse_opt_y },
OPT_BOOL(0, "allow-empty-message", OPT_BOOL(0, "allow-empty-message",
&options.allow_empty_message, &options.allow_empty_message,
N_("allow rebasing commits with empty messages")), N_("allow rebasing commits with empty messages")),
@ -920,6 +949,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
"strategy")), "strategy")),
OPT_BOOL(0, "root", &options.root, OPT_BOOL(0, "root", &options.root,
N_("rebase all reachable commits up to the root(s)")), 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(), OPT_END(),
}; };
int i; int i;
@ -1216,6 +1248,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
break; break;
} }
if (options.reschedule_failed_exec && !is_interactive(&options))
die(_("--reschedule-failed-exec requires an interactive rebase"));
if (options.git_am_opts.argc) { if (options.git_am_opts.argc) {
/* all am options except -q are compatible only with --am */ /* all am options except -q are compatible only with --am */
for (i = options.git_am_opts.argc - 1; i >= 0; i--) for (i = options.git_am_opts.argc - 1; i >= 0; i--)
@ -1241,7 +1276,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
options.flags |= REBASE_FORCE; 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 --signoff handled in signoff block above
* Note: incompatibility with --interactive is just a strong warning; * Note: incompatibility with --interactive is just a strong warning;
@ -1251,6 +1286,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
die(_("error: cannot combine '--preserve-merges' with " die(_("error: cannot combine '--preserve-merges' with "
"'--rebase-merges'")); "'--rebase-merges'"));
if (options.reschedule_failed_exec)
die(_("error: cannot combine '--preserve-merges' with "
"'--reschedule-failed-exec'"));
}
if (options.rebase_merges) { if (options.rebase_merges) {
if (strategy_options.nr) if (strategy_options.nr)
die(_("error: cannot combine '--rebase-merges' with " die(_("error: cannot combine '--rebase-merges' with "

View File

@ -26,6 +26,7 @@ f,force-rebase! cherry-pick all commits, even if unchanged
m,merge! use merging strategies to rebase m,merge! use merging strategies to rebase
i,interactive! let the user edit the list of commits 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 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 k,keep-empty preserve empty commits during rebase
allow-empty-message allow rebasing commits with empty messages allow-empty-message allow rebasing commits with empty messages
stat! display a diffstat of what changed upstream stat! display a diffstat of what changed upstream
@ -48,6 +49,7 @@ skip! skip current patch and continue
edit-todo! edit the todo list during an interactive rebase edit-todo! edit the todo list during an interactive rebase
quit! abort but keep HEAD where it is quit! abort but keep HEAD where it is
show-current-patch! show the patch file being applied or merged show-current-patch! show the patch file being applied or merged
reschedule-failed-exec automatically reschedule failed exec commands
" "
. git-sh-setup . git-sh-setup
set_reflog_action rebase set_reflog_action rebase
@ -92,11 +94,14 @@ autosquash=
keep_empty= keep_empty=
allow_empty_message=--allow-empty-message allow_empty_message=--allow-empty-message
signoff= signoff=
reschedule_failed_exec=
test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t test "$(git config --bool rebase.autosquash)" = "true" && autosquash=t
case "$(git config --bool commit.gpgsign)" in case "$(git config --bool commit.gpgsign)" in
true) gpg_sign_opt=-S ;; true) gpg_sign_opt=-S ;;
*) gpg_sign_opt= ;; *) gpg_sign_opt= ;;
esac esac
test "$(git config --bool rebase.reschedulefailedexec)" = "true" &&
reschedule_failed_exec=--reschedule-failed-exec
. git-rebase--common . git-rebase--common
read_basic_state () { read_basic_state () {
@ -126,6 +131,8 @@ read_basic_state () {
signoff="$(cat "$state_dir"/signoff)" signoff="$(cat "$state_dir"/signoff)"
force_rebase=t force_rebase=t
} }
test -f "$state_dir"/reschedule-failed-exec &&
reschedule_failed_exec=t
} }
finish_rebase () { finish_rebase () {
@ -163,7 +170,8 @@ run_interactive () {
"$allow_empty_message" "$autosquash" "$verbose" \ "$allow_empty_message" "$autosquash" "$verbose" \
"$force_rebase" "$onto_name" "$head_name" "$strategy" \ "$force_rebase" "$onto_name" "$head_name" "$strategy" \
"$strategy_opts" "$cmd" "$switch_to" \ "$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 () { run_specific_rebase () {
@ -255,6 +263,11 @@ do
cmd="${cmd}exec ${1#--exec=}${LF}" cmd="${cmd}exec ${1#--exec=}${LF}"
test -z "$interactive_rebase" && interactive_rebase=implied 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)
interactive_rebase=explicit interactive_rebase=explicit
;; ;;
@ -378,6 +391,12 @@ do
--gpg-sign=*) --gpg-sign=*)
gpg_sign_opt="-S${1#--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 shift
break break
@ -534,6 +553,9 @@ then
# git-rebase.txt caveats with "unless you know what you are doing" # git-rebase.txt caveats with "unless you know what you are doing"
test -n "$rebase_merges" && test -n "$rebase_merges" &&
die "$(gettext "error: cannot combine '--preserve-merges' with '--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 fi
if test -n "$rebase_merges" if test -n "$rebase_merges"

View File

@ -19,6 +19,7 @@ write_basic_state () {
"$state_dir"/allow_rerere_autoupdate "$state_dir"/allow_rerere_autoupdate
test -n "$gpg_sign_opt" && echo "$gpg_sign_opt" > "$state_dir"/gpg_sign_opt test -n "$gpg_sign_opt" && echo "$gpg_sign_opt" > "$state_dir"/gpg_sign_opt
test -n "$signoff" && echo "$signoff" >"$state_dir"/signoff test -n "$signoff" && echo "$signoff" >"$state_dir"/signoff
test -n "$reschedule_failed_exec" && : > "$state_dir"/reschedule-failed-exec
} }
apply_autostash () { apply_autostash () {

View File

@ -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_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_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
static GIT_PATH_FUNC(rebase_path_quiet, "rebase-merge/quiet") 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) static int git_sequencer_config(const char *k, const char *v, void *cb)
{ {
@ -2394,6 +2395,9 @@ static int read_populate_opts(struct replay_opts *opts)
opts->signoff = 1; opts->signoff = 1;
} }
if (file_exists(rebase_path_reschedule_failed_exec()))
opts->reschedule_failed_exec = 1;
read_strategy_opts(opts, &buf); read_strategy_opts(opts, &buf);
strbuf_release(&buf); strbuf_release(&buf);
@ -2475,6 +2479,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); write_file(rebase_path_gpg_sign_opt(), "-S%s\n", opts->gpg_sign);
if (opts->signoff) if (opts->signoff)
write_file(rebase_path_signoff(), "--signoff\n"); write_file(rebase_path_signoff(), "--signoff\n");
if (opts->reschedule_failed_exec)
write_file(rebase_path_reschedule_failed_exec(), "%s", "");
return 0; return 0;
} }
@ -3632,9 +3638,10 @@ static int pick_commits(struct repository *r,
*end_of_arg = saved; *end_of_arg = saved;
/* Reread the todo file if it has changed. */ /* Reread the todo file if it has changed. */
if (res) if (res) {
; /* fall through */ if (opts->reschedule_failed_exec)
else if (stat(get_todo_path(opts), &st)) reschedule = 1;
} else if (stat(get_todo_path(opts), &st))
res = error_errno(_("could not stat '%s'"), res = error_errno(_("could not stat '%s'"),
get_todo_path(opts)); get_todo_path(opts));
else if (match_stat_data(&todo_list->stat, &st)) { else if (match_stat_data(&todo_list->stat, &st)) {

View File

@ -40,6 +40,7 @@ struct replay_opts {
int allow_empty_message; int allow_empty_message;
int keep_redundant_commits; int keep_redundant_commits;
int verbose; int verbose;
int reschedule_failed_exec;
int mainline; int mainline;

View File

@ -254,4 +254,18 @@ test_expect_success 'the todo command "break" works' '
test_path_is_file execed 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 &&
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 &&
git rebase --abort &&
test_must_fail git rebase -y false HEAD^ 2>err &&
test_i18ngrep "has been rescheduled" err
'
test_done test_done