Merge branch 'en/rebase-incompatible-opts'

"git rebase" often ignored incompatible options instead of
complaining, which has been corrected.

* en/rebase-incompatible-opts:
  rebase: provide better error message for apply options vs. merge config
  rebase: put rebase_options initialization in single place
  rebase: fix formatting of rebase --reapply-cherry-picks option in docs
  rebase: clarify the OPT_CMDMODE incompatibilities
  rebase: add coverage of other incompatible options
  rebase: fix incompatiblity checks for --[no-]reapply-cherry-picks
  rebase: fix docs about incompatibilities with --root
  rebase: remove --allow-empty-message from incompatible opts
  rebase: flag --apply and --merge as incompatible
  rebase: mark --update-refs as requiring the merge backend
maint
Junio C Hamano 2023-02-03 16:08:21 -08:00
commit 2c6e5b32aa
3 changed files with 163 additions and 64 deletions

View File

@ -208,6 +208,39 @@ Alternatively, you can undo the 'git rebase' with


git rebase --abort git rebase --abort


MODE OPTIONS
------------

The options in this section cannot be used with any other option,
including not with each other:

--continue::
Restart the rebasing process after having resolved a merge conflict.

--skip::
Restart the rebasing process by skipping the current patch.

--abort::
Abort the rebase operation and reset HEAD to the original
branch. If `<branch>` was provided when the rebase operation was
started, then `HEAD` will be reset to `<branch>`. Otherwise `HEAD`
will be reset to where it was when the rebase operation was
started.

--quit::
Abort the rebase operation but `HEAD` is not reset back to the
original branch. The index and working tree are also left
unchanged as a result. If a temporary stash entry was created
using `--autostash`, it will be saved to the stash list.

--edit-todo::
Edit the todo list during an interactive rebase.

--show-current-patch::
Show the current patch in an interactive rebase or when rebase
is stopped because of conflicts. This is the equivalent of
`git show REBASE_HEAD`.

OPTIONS OPTIONS
------- -------
--onto <newbase>:: --onto <newbase>::
@ -249,22 +282,6 @@ See also INCOMPATIBLE OPTIONS below.
<branch>:: <branch>::
Working branch; defaults to `HEAD`. Working branch; defaults to `HEAD`.


--continue::
Restart the rebasing process after having resolved a merge conflict.

--abort::
Abort the rebase operation and reset HEAD to the original
branch. If `<branch>` was provided when the rebase operation was
started, then `HEAD` will be reset to `<branch>`. Otherwise `HEAD`
will be reset to where it was when the rebase operation was
started.

--quit::
Abort the rebase operation but `HEAD` is not reset back to the
original branch. The index and working tree are also left
unchanged as a result. If a temporary stash entry was created
using `--autostash`, it will be saved to the stash list.

--apply:: --apply::
Use applying strategies to rebase (calling `git-am` Use applying strategies to rebase (calling `git-am`
internally). This option may become a no-op in the future internally). This option may become a no-op in the future
@ -321,7 +338,6 @@ See also INCOMPATIBLE OPTIONS below.
upstream changes, the behavior towards them is controlled by upstream changes, the behavior towards them is controlled by
the `--empty` flag.) the `--empty` flag.)
+ +

In the absence of `--keep-base` (or if `--no-reapply-cherry-picks` is In the absence of `--keep-base` (or if `--no-reapply-cherry-picks` is
given), these commits will be automatically dropped. Because this given), these commits will be automatically dropped. Because this
necessitates reading all upstream commits, this can be expensive in necessitates reading all upstream commits, this can be expensive in
@ -330,7 +346,6 @@ read. When using the 'merge' backend, warnings will be issued for each
dropped commit (unless `--quiet` is given). Advice will also be issued dropped commit (unless `--quiet` is given). Advice will also be issued
unless `advice.skippedCherryPicks` is set to false (see unless `advice.skippedCherryPicks` is set to false (see
linkgit:git-config[1]). linkgit:git-config[1]).

+ +
`--reapply-cherry-picks` allows rebase to forgo reading all upstream `--reapply-cherry-picks` allows rebase to forgo reading all upstream
commits, potentially improving performance. commits, potentially improving performance.
@ -345,17 +360,6 @@ See also INCOMPATIBLE OPTIONS below.
+ +
See also INCOMPATIBLE OPTIONS below. See also INCOMPATIBLE OPTIONS below.


--skip::
Restart the rebasing process by skipping the current patch.

--edit-todo::
Edit the todo list during an interactive rebase.

--show-current-patch::
Show the current patch in an interactive rebase or when rebase
is stopped because of conflicts. This is the equivalent of
`git show REBASE_HEAD`.

-m:: -m::
--merge:: --merge::
Using merging strategies to rebase (default). Using merging strategies to rebase (default).
@ -574,10 +578,7 @@ See also INCOMPATIBLE OPTIONS below.
--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
the root commit(s) on a branch. When used with `--onto`, it the root commit(s) on a branch.
will skip changes already contained in `<newbase>` (instead of
`<upstream>`) whereas without `--onto` it will operate on every
change.
+ +
See also INCOMPATIBLE OPTIONS below. See also INCOMPATIBLE OPTIONS below.


@ -630,6 +631,8 @@ start would be overridden by the presence of
+ +
If the configuration variable `rebase.updateRefs` is set, then this option If the configuration variable `rebase.updateRefs` is set, then this option
can be used to override and disable this setting. can be used to override and disable this setting.
+
See also INCOMPATIBLE OPTIONS below.


INCOMPATIBLE OPTIONS INCOMPATIBLE OPTIONS
-------------------- --------------------
@ -645,17 +648,15 @@ are incompatible with the following options:
* --merge * --merge
* --strategy * --strategy
* --strategy-option * --strategy-option
* --allow-empty-message * --autosquash
* --[no-]autosquash
* --rebase-merges * --rebase-merges
* --interactive * --interactive
* --exec * --exec
* --no-keep-empty * --no-keep-empty
* --empty= * --empty=
* --reapply-cherry-picks * --[no-]reapply-cherry-picks when used without --keep-base
* --edit-todo
* --update-refs * --update-refs
* --root when used in combination with --onto * --root when used without --onto


In addition, the following pairs of options are incompatible: In addition, the following pairs of options are incompatible:



View File

@ -122,6 +122,8 @@ struct rebase_options {
int reapply_cherry_picks; int reapply_cherry_picks;
int fork_point; int fork_point;
int update_refs; int update_refs;
int config_autosquash;
int config_update_refs;
}; };


#define REBASE_OPTIONS_INIT { \ #define REBASE_OPTIONS_INIT { \
@ -134,6 +136,12 @@ struct rebase_options {
.exec = STRING_LIST_INIT_NODUP, \ .exec = STRING_LIST_INIT_NODUP, \
.git_format_patch_opt = STRBUF_INIT, \ .git_format_patch_opt = STRBUF_INIT, \
.fork_point = -1, \ .fork_point = -1, \
.reapply_cherry_picks = -1, \
.allow_empty_message = 1, \
.autosquash = -1, \
.config_autosquash = -1, \
.update_refs = -1, \
.config_update_refs = -1, \
} }


static struct replay_opts get_replay_opts(const struct rebase_options *opts) static struct replay_opts get_replay_opts(const struct rebase_options *opts)
@ -776,7 +784,7 @@ static int rebase_config(const char *var, const char *value, void *data)
} }


if (!strcmp(var, "rebase.autosquash")) { if (!strcmp(var, "rebase.autosquash")) {
opts->autosquash = git_config_bool(var, value); opts->config_autosquash = git_config_bool(var, value);
return 0; return 0;
} }


@ -793,7 +801,7 @@ static int rebase_config(const char *var, const char *value, void *data)
} }


if (!strcmp(var, "rebase.updaterefs")) { if (!strcmp(var, "rebase.updaterefs")) {
opts->update_refs = git_config_bool(var, value); opts->config_update_refs = git_config_bool(var, value);
return 0; return 0;
} }


@ -907,6 +915,9 @@ static int parse_opt_am(const struct option *opt, const char *arg, int unset)
BUG_ON_OPT_NEG(unset); BUG_ON_OPT_NEG(unset);
BUG_ON_OPT_ARG(arg); BUG_ON_OPT_ARG(arg);


if (opts->type != REBASE_UNSPECIFIED && opts->type != REBASE_APPLY)
die(_("apply options and merge options cannot be used together"));

opts->type = REBASE_APPLY; opts->type = REBASE_APPLY;


return 0; return 0;
@ -920,7 +931,9 @@ static int parse_opt_merge(const struct option *opt, const char *arg, int unset)
BUG_ON_OPT_NEG(unset); BUG_ON_OPT_NEG(unset);
BUG_ON_OPT_ARG(arg); BUG_ON_OPT_ARG(arg);


if (!is_merge(opts)) if (opts->type != REBASE_UNSPECIFIED && opts->type != REBASE_MERGE)
die(_("apply options and merge options cannot be used together"));

opts->type = REBASE_MERGE; opts->type = REBASE_MERGE;


return 0; return 0;
@ -935,6 +948,9 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,
BUG_ON_OPT_NEG(unset); BUG_ON_OPT_NEG(unset);
BUG_ON_OPT_ARG(arg); BUG_ON_OPT_ARG(arg);


if (opts->type != REBASE_UNSPECIFIED && opts->type != REBASE_MERGE)
die(_("apply options and merge options cannot be used together"));

opts->type = REBASE_MERGE; opts->type = REBASE_MERGE;
opts->flags |= REBASE_INTERACTIVE_EXPLICIT; opts->flags |= REBASE_INTERACTIVE_EXPLICIT;


@ -1150,8 +1166,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
prepare_repo_settings(the_repository); prepare_repo_settings(the_repository);
the_repository->settings.command_requires_full_index = 0; the_repository->settings.command_requires_full_index = 0;


options.reapply_cherry_picks = -1;
options.allow_empty_message = 1;
git_config(rebase_config, &options); git_config(rebase_config, &options);
/* options.gpg_sign_opt will be either "-S" or NULL */ /* options.gpg_sign_opt will be either "-S" or NULL */
gpg_sign = options.gpg_sign_opt ? "" : NULL; gpg_sign = options.gpg_sign_opt ? "" : NULL;
@ -1216,13 +1230,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (options.fork_point < 0) if (options.fork_point < 0)
options.fork_point = 0; options.fork_point = 0;
} }
/*
* --keep-base defaults to --reapply-cherry-picks to avoid losing
* commits when using this option.
*/
if (options.reapply_cherry_picks < 0)
options.reapply_cherry_picks = keep_base;

if (options.root && options.fork_point > 0) if (options.root && options.fork_point > 0)
die(_("options '%s' and '%s' cannot be used together"), "--root", "--fork-point"); die(_("options '%s' and '%s' cannot be used together"), "--root", "--fork-point");


@ -1365,7 +1372,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if ((options.flags & REBASE_INTERACTIVE_EXPLICIT) || if ((options.flags & REBASE_INTERACTIVE_EXPLICIT) ||
(options.action != ACTION_NONE) || (options.action != ACTION_NONE) ||
(options.exec.nr > 0) || (options.exec.nr > 0) ||
options.autosquash) { (options.autosquash == -1 && options.config_autosquash == 1) ||
options.autosquash == 1) {
allow_preemptive_ff = 0; allow_preemptive_ff = 0;
} }
if (options.committer_date_is_author_date || options.ignore_date) if (options.committer_date_is_author_date || options.ignore_date)
@ -1398,12 +1406,27 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (options.empty != EMPTY_UNSPECIFIED) if (options.empty != EMPTY_UNSPECIFIED)
imply_merge(&options, "--empty"); imply_merge(&options, "--empty");


if (options.reapply_cherry_picks < 0)
/* /*
* --keep-base implements --reapply-cherry-picks by altering upstream so * We default to --no-reapply-cherry-picks unless
* it works with both backends. * --keep-base is given; when --keep-base is given, we want
* to default to --reapply-cherry-picks.
*/ */
if (options.reapply_cherry_picks && !keep_base) options.reapply_cherry_picks = keep_base;
imply_merge(&options, "--reapply-cherry-picks"); else if (!keep_base)
/*
* The apply backend always searches for and drops cherry
* picks. This is often not wanted with --keep-base, so
* --keep-base allows --reapply-cherry-picks to be
* simulated by altering the upstream such that
* cherry-picks cannot be detected and thus all commits are
* reapplied. Thus, --[no-]reapply-cherry-picks is
* supported when --keep-base is specified, but not when
* --keep-base is left out.
*/
imply_merge(&options, options.reapply_cherry_picks ?
"--reapply-cherry-picks" :
"--no-reapply-cherry-picks");


if (gpg_sign) if (gpg_sign)
options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign); options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign);
@ -1483,15 +1506,29 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
if (strcmp(options.git_am_opts.v[i], "-q")) if (strcmp(options.git_am_opts.v[i], "-q"))
break; break;


if (i >= 0) { if (i >= 0 || options.type == REBASE_APPLY) {
if (is_merge(&options)) if (is_merge(&options))
die(_("apply options and merge options " die(_("apply options and merge options "
"cannot be used together")); "cannot be used together"));
else if (options.autosquash == -1 && options.config_autosquash == 1)
die(_("apply options are incompatible with rebase.autosquash. Consider adding --no-autosquash"));
else if (options.update_refs == -1 && options.config_update_refs == 1)
die(_("apply options are incompatible with rebase.updateRefs. Consider adding --no-update-refs"));
else else
options.type = REBASE_APPLY; options.type = REBASE_APPLY;
} }
} }


if (options.update_refs == 1)
imply_merge(&options, "--update-refs");
options.update_refs = (options.update_refs >= 0) ? options.update_refs :
((options.config_update_refs >= 0) ? options.config_update_refs : 0);

if (options.autosquash == 1)
imply_merge(&options, "--autosquash");
options.autosquash = (options.autosquash >= 0) ? options.autosquash :
((options.config_autosquash >= 0) ? options.config_autosquash : 0);

if (options.type == REBASE_UNSPECIFIED) { if (options.type == REBASE_UNSPECIFIED) {
if (!strcmp(options.default_backend, "merge")) if (!strcmp(options.default_backend, "merge"))
imply_merge(&options, "--merge"); imply_merge(&options, "--merge");

View File

@ -25,11 +25,11 @@ test_expect_success 'setup' '
' '


# #
# Rebase has lots of useful options like --whitepsace=fix, which are # Rebase has a couple options which are specific to the apply backend,
# actually all built in terms of flags to git-am. Since neither # and several options which are specific to the merge backend. Flags
# --merge nor --interactive (nor any options that imply those two) use # from the different sets cannot work together, and we do not want to
# git-am, using them together will result in flags like --whitespace=fix # just ignore one of the sets of flags. Make sure rebase warns the
# being ignored. Make sure rebase warns the user and aborts instead. # user and aborts instead.
# #


test_rebase_am_only () { test_rebase_am_only () {
@ -50,6 +50,11 @@ test_rebase_am_only () {
test_must_fail git rebase $opt --strategy-option=ours A test_must_fail git rebase $opt --strategy-option=ours A
" "


test_expect_success "$opt incompatible with --autosquash" "
git checkout B^0 &&
test_must_fail git rebase $opt --autosquash A
"

test_expect_success "$opt incompatible with --interactive" " test_expect_success "$opt incompatible with --interactive" "
git checkout B^0 && git checkout B^0 &&
test_must_fail git rebase $opt --interactive A test_must_fail git rebase $opt --interactive A
@ -60,9 +65,65 @@ test_rebase_am_only () {
test_must_fail git rebase $opt --exec 'true' A test_must_fail git rebase $opt --exec 'true' A
" "


test_expect_success "$opt incompatible with --keep-empty" "
git checkout B^0 &&
test_must_fail git rebase $opt --keep-empty A
"

test_expect_success "$opt incompatible with --empty=..." "
git checkout B^0 &&
test_must_fail git rebase $opt --empty=ask A
"

test_expect_success "$opt incompatible with --no-reapply-cherry-picks" "
git checkout B^0 &&
test_must_fail git rebase $opt --no-reapply-cherry-picks A
"

test_expect_success "$opt incompatible with --reapply-cherry-picks" "
git checkout B^0 &&
test_must_fail git rebase $opt --reapply-cherry-picks A
"

test_expect_success "$opt incompatible with --update-refs" "
git checkout B^0 &&
test_must_fail git rebase $opt --update-refs A
"

test_expect_success "$opt incompatible with --root without --onto" "
git checkout B^0 &&
test_must_fail git rebase $opt --root A
"

test_expect_success "$opt incompatible with rebase.autosquash" "
git checkout B^0 &&
test_must_fail git -c rebase.autosquash=true rebase $opt A 2>err &&
grep -e --no-autosquash err
"

test_expect_success "$opt incompatible with rebase.updateRefs" "
git checkout B^0 &&
test_must_fail git -c rebase.updateRefs=true rebase $opt A 2>err &&
grep -e --no-update-refs err
"

test_expect_success "$opt okay with overridden rebase.autosquash" "
test_when_finished \"git reset --hard B^0\" &&
git checkout B^0 &&
git -c rebase.autosquash=true rebase --no-autosquash $opt A
"

test_expect_success "$opt okay with overridden rebase.updateRefs" "
test_when_finished \"git reset --hard B^0\" &&
git checkout B^0 &&
git -c rebase.updateRefs=true rebase --no-update-refs $opt A
"
} }


# Check options which imply --apply
test_rebase_am_only --whitespace=fix test_rebase_am_only --whitespace=fix
test_rebase_am_only -C4 test_rebase_am_only -C4
# Also check an explicit --apply
test_rebase_am_only --apply


test_done test_done