rebase: make the default backend configurable
Re: [PATCH v4 04/19] rebase (interactive-backend): fix handling of commits that become empty
In reply to Elijah Newren via GitGitGadgetHi Elijah
Quoted 75 lines · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
As established in the previous commit and commit
b00bf1c9a8dd(git-rebase: make --allow-empty-message the default, 2018-06-27), the behavior for rebase with different backends in various edge or corner cases is often more happenstance than design. This commit addresses another such corner case: commits which "become empty".A careful reader may note that there are two types of commits which would become empty due to a rebase:
* [clean cherry-pick] Commits which are clean cherry-picks of upstream commits, as determined by
git log --cherry-mark .... Re-applying these commits would result in an empty set of changes and a duplicative commit message; i.e. these are commits that have "already been applied" upstream.* [become empty] Commits which are not empty to start, are not clean cherry-picks of upstream commits, but which still become empty after being rebased. This happens e.g. when a commit has changes which are a strict subset of the changes in an upstream commit, or when the changes of a commit can be found spread across or among several upstream commits.
Clearly, in both cases the changes in the commit in question are found upstream already, but the commit message may not be in the latter case.
When cherry-mark can determine a commit is already upstream, then because of how cherry-mark works this means the upstream commit message was about the *exact* same set of changes. Thus, the commit messages can be assumed to be fully interchangeable (and are in fact likely to be completely identical). As such, the clean cherry-pick case represents a case when there is no information to be gained by keeping the extra commit around. All rebase types have always dropped these commits, and no one to my knowledge has ever requested that we do otherwise.
For many of the become empty cases (and likely even most), we will also be able to drop the commit without loss of information -- but this isn't quite always the case. Since these commits represent cases that were not clean cherry-picks, there is no upstream commit message explaining the same set of changes. Projects with good commit message hygiene will likely have the explanation from our commit message contained within or spread among the relevant upstream commits, but not all projects run that way. As such, the commit message of the commit being rebased may have reasoning that suggests additional changes that should be made to adapt to the new base, or it may have information that someone wants to add as a note to another commit, or perhaps someone even wants to create an empty commit with the commit message as-is.
Junio commented on the "become-empty" types of commits as follows[1]:
WRT a change that ends up being empty (as opposed to a change that is empty from the beginning), I'd think that the current behaviour is desireable one. "am" based rebase is solely to transplant an existing history and want to stop much less than "interactive" one whose purpose is to polish a series before making it publishable, and asking for confirmation ("this has become empty--do you want to drop it?") is more appropriate from the workflow point of view.
[1] https://lore.kernel.org/git/[email protected]/
I would simply add that his arguments for "am"-based rebases actually apply to all non-explicitly-interactive rebases. Also, since we are stating that different cases should have different defaults, it may be worth providing a flag to allow users to select which behavior they want for these commits.
Introduce a new command line flag for selecting the desired behavior:
--empty={drop,keep,ask}with the definitions:
drop: drop commits which become empty
keep: keep commits which become empty
ask: provide the user a chance to interact and pick what to do with
commits which become empty on a case-by-case basis
Quoted 4 lines
In line with Junio's suggestion, if the --empty flag is not specified, pick defaults as follows:
explicitly interactive: ask
otherwise: drop
Looking at the implementation there is a third option - if --exec is given without -i then the default is "keep". I'm not sure if having different defaults is convenient or confusing but don't feel that strongly about it. I've got a few minor comments below (the mains ones are saying which commit has been dropped and testing the default behavior when --empty is not given) but basically I like the new patch. Thanks for working on this, the commit message does a good job of explaining the changes.
Quoted 17 lines
Signed-off-by Elijah Newren <[email protected]>--- Documentation/git-rebase.txt | 27 ++++++++++++++++--- builtin/rebase.c | 52 ++++++++++++++++++++++++++++++++++++ sequencer.c | 48 +++++++++++++++++++++++++-------- sequencer.h | 1 + t/t3424-rebase-empty.sh | 50 +++++++++++++++++++++++++++++----- t/t3427-rebase-subtree.sh | 8 +++--- 6 files changed, 161 insertions(+), 25 deletions(-)Documentation/git-rebase.txt +0
-0@@ -258,6 +258,22 @@ See also INCOMPATIBLE OPTIONS below. original branch. The index and working tree are also left unchanged as a result.
Documentation/git-rebase.txt See also INCOMPATIBLE OPTIONS below. · line 269
Quoted 10 lines
Documentation/git-rebase.txt +9
-0+--empty={drop,keep,ask}:: + How to handle commits that are not empty to start and are not + clean cherry-picks of any upstream commit, but which become + empty after rebasing (because they contain a subset of already + upstream changes). With drop (the default), commits that + become empty are dropped. With keep, such commits are kept. + With ask (implied by --interactive), the rebase will halt when + an empty commit is applied allowing you to choose whether to + drop it, edit files more, or just commit the empty changes.
We should probably document the default for --exec without -i
Quoted 121 lines
Documentation/git-rebase.txt +14
-3+Note that commits which start empty are kept, and commits which are +clean cherry-picks (as determined by `git log --cherry-mark ...`) are +always dropped. ++ +See also INCOMPATIBLE OPTIONS below. + --keep-empty:: No-op. Rebasing commits that started empty (had no change relative to their parent) used to fail and this option would@@ -561,6 +577,7 @@ are incompatible with the following options: * --interactive * --exec * --keep-empty + * --empty= * --edit-todo * --root when used in combination with --onto@@ -569,6 +586,7 @@ In addition, the following pairs of options are incompatible: * --preserve-merges and --interactive * --preserve-merges and --signoff * --preserve-merges and --rebase-merges + * --preserve-merges and --empty= * --keep-base and --onto * --keep-base and --root@@ -585,9 +603,12 @@ commits that started empty, though these are rare in practice. It also drops commits that become empty and has no option for controlling this behavior. -The interactive backend keeps intentionally empty commits. -Unfortunately, it always halts whenever it runs across a commit that -becomes empty, even when the rebase is not explicitly interactive. +The interactive backend keeps intentionally empty commits. Similar to +the am backend, by default the interactive backend drops commits that +become empty unless -i/--interactive is specified (in which case it +stops and asks the user what to do). The interactive backend also has +an --empty={drop,keep,ask} option for changing the behavior of +handling commits that become empty. Directory rename detection ~~~~~~~~~~~~~~~~~~~~~~~~~~builtin/rebase.c +38
-0@@ -50,8 +50,16 @@ enum rebase_type {REBASE_PRESERVE_MERGES }; +enum empty_type { +EMPTY_UNSPECIFIED =-1 , +EMPTY_DROP , +EMPTY_KEEP , +EMPTY_ASK +}; +struct rebase_options {enum rebase_type type; +enum empty_type empty;const char *state_dir;struct commit *upstream;const char *upstream_name;@@ -91,6 +99,7 @@ struct rebase_options {#define REBASE_OPTIONS_INIT { \ .type = REBASE_UNSPECIFIED, \ + .empty = EMPTY_UNSPECIFIED, \ .flags = REBASE_NO_QUIET, \ .git_am_opts = ARGV_ARRAY_INIT, \ .git_format_patch_opt = STRBUF_INIT \@@ -109,6 +118,8 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts) replay.allow_rerere_auto = opts->allow_rerere_autoupdate; replay.allow_empty =1 ; replay.allow_empty_message = opts->allow_empty_message; + replay.drop_redundant_commits = (opts->empty ==EMPTY_DROP ); + replay.keep_redundant_commits = (opts->empty ==EMPTY_KEEP ); replay.verbose = opts->flags &REBASE_VERBOSE ; replay.reschedule_failed_exec = opts->reschedule_failed_exec; replay.gpg_sign =xstrdup_or_null (opts->gpg_sign_opt);@@ -444,6 +455,10 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg,BUG_ON_OPT_ARG (arg); +/* +* If we ever want to remap --keep-empty to --empty=keep, insert: +* opts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP; +*/ opts->type =REBASE_INTERACTIVE ;return 0 ; }@@ -1350,6 +1365,29 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,return 0 ; } +static enum empty_type parse_empty_value (const char *value) +{ +if (!strcasecmp (value,"drop" )) +return EMPTY_DROP ; +else if (!strcasecmp (value,"keep" )) +return EMPTY_KEEP ; +else if (!strcasecmp (value,"ask" )) +return EMPTY_ASK ; + +die (_ ("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"." ), value); +} + +static int parse_opt_empty (const struct option *opt,const char *arg,int unset) +{ +struct rebase_options *options = opt->value; +enum empty_type value =parse_empty_value (arg); + +BUG_ON_OPT_NEG (unset); + + options->empty = value; +return 0 ; +} + static void NORETURN error_on_missing_default_upstream(void) {struct branch *current_branch =branch_get (NULL );
builtin/rebase.c int cmd_rebase(int argc, const char **argv, const char *prefix) · line 1536
Quoted 5 lines
builtin/rebase.c +2
-0@@ -1494,6 +1532,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) "ignoring them"), REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),OPT_RERERE_AUTOUPDATE (&options.allow_rerere_autoupdate), + OPT_CALLBACK_F(0, "empty", &options, N_("{drop,keep,ask}"), + N_("how to handle empty commits"),
Maybe we should say "how to handle commits that become empty" to distinguish them from commits that start empty which we always keep
Quoted 108 lines
builtin/rebase.c +12
-0+ PARSE_OPT_NONEG, parse_opt_empty), {OPTION_CALLBACK ,'k' ,"keep-empty" , &options,NULL ,N_ ("(DEPRECATED) keep empty commits" ), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,@@ -1760,6 +1801,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)if (!(options.flags &REBASE_NO_QUIET ))argv_array_push (&options.git_am_opts,"-q" ); +if (options.empty !=EMPTY_UNSPECIFIED ) +imply_interactive (&options,"--empty" ); +if (gpg_sign) {free (options.gpg_sign_opt); options.gpg_sign_opt =xstrfmt ("-S%s" , gpg_sign);@@ -1843,6 +1887,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)break ; } +if (options.empty ==EMPTY_UNSPECIFIED ) { +if (options.flags &REBASE_INTERACTIVE_EXPLICIT ) + options.empty =EMPTY_ASK ; +else if (exec.nr >0 ) + options.empty =EMPTY_KEEP ; +else + options.empty =EMPTY_DROP ; + }if (reschedule_failed_exec >0 && !is_interactive (&options))die (_ ("--reschedule-failed-exec requires " "--exec or --interactive" ));sequencer.c +21
-9@@ -158,6 +158,8 @@ 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_reschedule_failed_exec, "rebase-merge/reschedule-failed-exec") +static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits") +static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits") static int git_sequencer_config(const char *k, const char *v, void *cb) {@@ -1483,7 +1485,11 @@ static int is_original_commit_empty(struct commit *commit) }/* -* Do we run "git commit" with "--allow-empty"? +* Should empty commits be allowed? Return status: +* <0: Error in is_index_unchanged(r) or is_original_commit_empty(commit) +* 0: Halt on empty commit +* 1: Allow empty commit +* 2: Drop empty commit */ static int allow_empty(struct repository *r, struct replay_opts *opts,@@ -1492,14 +1498,17 @@ static int allow_empty(struct repository *r,int index_unchanged, originally_empty;/* -* Three cases: +* Four cases: * * (1) we do not allow empty at all and error out. * -* (2) we allow ones that were initially empty, but -* forbid the ones that become empty; +* (2) we allow ones that were initially empty, and +* just drop the ones that become empty * -* (3) we allow both. +* (3) we allow ones that were initially empty, but +* halt for the ones that become empty; +* +* (4) we allow both. */ if (!opts->allow_empty)return 0 ;/* let "git commit" barf as necessary */ @@ -1516,10 +1525,12 @@ static int allow_empty(struct repository *r, originally_empty =is_original_commit_empty (commit);if (originally_empty <0 )return originally_empty; -if (!originally_empty) -return 0 ; -else +if (originally_empty)return 1 ; +else if (opts->drop_redundant_commits) +return 2 ; +else +return 0 ; } static struct {@@ -1730,7 +1741,7 @@ static int do_pick_commit(struct repository *r,char *author =NULL ;struct commit_message msg = {NULL ,NULL ,NULL ,NULL };struct strbuf msgbuf =STRBUF_INIT ; -int res, unborn =0 , reword =0 , allow; +int res, unborn =0 , reword =0 , allow, drop_commit;if (opts->no_commit) {/* @@ -1935,13 +1946,18 @@ static int do_pick_commit(struct repository *r, goto leave; } + drop_commit =0 ; allow =allow_empty (r, opts, commit);
sequencer.c static int do_pick_commit(struct repository *r, · line 1958
Quoted 10 lines
sequencer.c +4
-2if (allow <0 ) { res = allow; goto leave; - }else if (allow) + }else if (allow ==1 ) { flags |=ALLOW_EMPTY ; -if (!opts->no_commit) { + }else if (allow ==2 ) { + drop_commit =1 ; +fprintf (stderr,_ ("No changes -- Patch already applied.\n" ));
nit pick - usually messages start with a lowercase letter. Would it be helpful to explicitly state which commit is being dropped as well as why? Something like
dropping <oid> <subject> - patch contents already upstream
sequencer.c static int do_pick_commit(struct repository *r, · line 1959
Quoted 1 line
sequencer.c +1
-0+ }// else allow == 0 and there's nothing special to do
We don't use // for comments
Quoted 106 lines
sequencer.c +11
-0+if (!opts->no_commit && !drop_commit) {if (author || command ==TODO_REVERT || (flags &AMEND_MSG )) res =do_commit (r, msg_file, author, opts, flags);else @@ -2495,6 +2511,12 @@ static int read_populate_opts(struct replay_opts *opts)if (file_exists (rebase_path_reschedule_failed_exec ())) opts->reschedule_failed_exec =1 ; +if (file_exists (rebase_path_drop_redundant_commits ())) + opts->drop_redundant_commits =1 ; + +if (file_exists (rebase_path_keep_redundant_commits ())) + opts->keep_redundant_commits =1 ; +read_strategy_opts (opts, &buf);strbuf_release (&buf);@@ -2574,6 +2596,10 @@ 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->drop_redundant_commits) +write_file (rebase_path_drop_redundant_commits (),"%s" ,"" ); +if (opts->keep_redundant_commits) +write_file (rebase_path_keep_redundant_commits (),"%s" ,"" );if (opts->reschedule_failed_exec)write_file (rebase_path_reschedule_failed_exec (),"%s" ,"" );sequencer.h +1
-0@@ -39,6 +39,7 @@ struct replay_opts {int allow_rerere_auto;int allow_empty;int allow_empty_message; +int drop_redundant_commits;int keep_redundant_commits;int verbose;int quiet;t/t3424-rebase-empty.sh +37
-6@@ -34,7 +34,7 @@ test_expect_success 'setup test repository' ' git commit -m "Five letters ought to be enough for anybody" ' -test_expect_failure 'rebase (am-backend) with a variety of empty commits' ' +test_expect_failure 'rebase (am-backend)' ' test_when_finished "git rebase --abort" && git checkout -B testing localmods && # rebase (--am) should not drop commits that start empty@@ -45,11 +45,29 @@ test_expect_failure 'rebase (am-backend) with a variety of empty commits' ' test_cmp expect actual ' -test_expect_failure 'rebase --merge with a variety of empty commits' ' - test_when_finished "git rebase --abort" && +test_expect_success 'rebase --merge --empty=drop' ' git checkout -B testing localmods && - # rebase --merge should not halt on the commit that becomes empty - git rebase --merge upstream && + git rebase --merge --empty=drop upstream && + + test_write_lines D C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + +test_expect_success 'rebase --merge --empty=keep' ' + git checkout -B testing localmods && + git rebase --merge --empty=keep upstream && + + test_write_lines D C2 C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + +test_expect_success 'rebase --merge --empty=ask' ' + git checkout -B testing localmods && + test_must_fail git rebase --merge --empty=ask upstream && + + git rebase --skip && test_write_lines D C B A >expect && git log --format=%s >actual &&@@ -58,9 +76,27 @@ test_expect_failure 'rebase --merge with a variety of empty commits' ' GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR -test_expect_success 'rebase --interactive with a variety of empty commits' ' +test_expect_success 'rebase --interactive --empty=drop' ' + git checkout -B testing localmods && + git rebase --interactive --empty=drop upstream && + + test_write_lines D C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + +test_expect_success 'rebase --interactive --empty=keep' ' + git checkout -B testing localmods && + git rebase --interactive --empty=keep upstream && + + test_write_lines D C2 C B A >expect &&
t/t3424-rebase-empty.sh test_expect_failure 'rebase --merge with a variety of empty commits' ' · line 101
Quoted 10 lines
t/t3424-rebase-empty.sh +6
-1+ git log --format=%s >actual && + test_cmp expect actual +' + +test_expect_success 'rebase --interactive --empty=ask' ' git checkout -B testing localmods && - test_must_fail git rebase --interactive upstream && + test_must_fail git rebase --interactive --empty=ask upstream && git rebase --skip &&
As the default if --empty is not given is supposed to vary depending on the other options given it would be good to test that I think
Best Wishes
Phillip
Quoted 31 lines
t/t3427-rebase-subtree.sh +4
-4@@ -85,10 +85,10 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges -- verbose test "$(commit_message HEAD)" = "Empty commit" ' -test_expect_success 'Rebase -Xsubtree --onto commit' ' +test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' ' reset_rebase && git checkout -b rebase-onto to-rebase && - test_must_fail git rebase -Xsubtree=files_subtree --onto files-master master && + test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --onto files-master master && : first pick results in no changes && git rebase --skip && verbose test "$(commit_message HEAD~2)" = "master4" &&@@ -96,10 +96,10 @@ test_expect_success 'Rebase -Xsubtree --onto commit' ' verbose test "$(commit_message HEAD)" = "Empty commit" ' -test_expect_success 'Rebase -Xsubtree --rebase-merges --onto commit' ' +test_expect_success 'Rebase -Xsubtree --empty=ask --rebase-merges --onto commit' ' reset_rebase && git checkout -b rebase-merges-onto to-rebase && - test_must_fail git rebase -Xsubtree=files_subtree --rebase-merges --onto files-master --root && + test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --rebase-merges --onto files-master --root && : first pick results in no changes && git rebase --skip && verbose test "$(commit_message HEAD~2)" = "master4" &&
Re: [PATCH v4 04/19] rebase (interactive-backend): fix handling of commits that become empty
Quoted 84 lines · Phillip Wood
Hi Elijah
Quoted 75 lines · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
As established in the previous commit and commit
b00bf1c9a8dd(git-rebase: make --allow-empty-message the default, 2018-06-27), the behavior for rebase with different backends in various edge or corner cases is often more happenstance than design. This commit addresses another such corner case: commits which "become empty".A careful reader may note that there are two types of commits which would become empty due to a rebase:
* [clean cherry-pick] Commits which are clean cherry-picks of upstream commits, as determined by
git log --cherry-mark .... Re-applying these commits would result in an empty set of changes and a duplicative commit message; i.e. these are commits that have "already been applied" upstream.* [become empty] Commits which are not empty to start, are not clean cherry-picks of upstream commits, but which still become empty after being rebased. This happens e.g. when a commit has changes which are a strict subset of the changes in an upstream commit, or when the changes of a commit can be found spread across or among several upstream commits.
Clearly, in both cases the changes in the commit in question are found upstream already, but the commit message may not be in the latter case.
When cherry-mark can determine a commit is already upstream, then because of how cherry-mark works this means the upstream commit message was about the *exact* same set of changes. Thus, the commit messages can be assumed to be fully interchangeable (and are in fact likely to be completely identical). As such, the clean cherry-pick case represents a case when there is no information to be gained by keeping the extra commit around. All rebase types have always dropped these commits, and no one to my knowledge has ever requested that we do otherwise.
For many of the become empty cases (and likely even most), we will also be able to drop the commit without loss of information -- but this isn't quite always the case. Since these commits represent cases that were not clean cherry-picks, there is no upstream commit message explaining the same set of changes. Projects with good commit message hygiene will likely have the explanation from our commit message contained within or spread among the relevant upstream commits, but not all projects run that way. As such, the commit message of the commit being rebased may have reasoning that suggests additional changes that should be made to adapt to the new base, or it may have information that someone wants to add as a note to another commit, or perhaps someone even wants to create an empty commit with the commit message as-is.
Junio commented on the "become-empty" types of commits as follows[1]:
WRT a change that ends up being empty (as opposed to a change that is empty from the beginning), I'd think that the current behaviour is desireable one. "am" based rebase is solely to transplant an existing history and want to stop much less than "interactive" one whose purpose is to polish a series before making it publishable, and asking for confirmation ("this has become empty--do you want to drop it?") is more appropriate from the workflow point of view.
[1] https://lore.kernel.org/git/[email protected]/
I would simply add that his arguments for "am"-based rebases actually apply to all non-explicitly-interactive rebases. Also, since we are stating that different cases should have different defaults, it may be worth providing a flag to allow users to select which behavior they want for these commits.
Introduce a new command line flag for selecting the desired behavior:
--empty={drop,keep,ask}with the definitions:
drop: drop commits which become empty
keep: keep commits which become empty
ask: provide the user a chance to interact and pick what to do with
commits which become empty on a case-by-case basisQuoted 4 lines
In line with Junio's suggestion, if the --empty flag is not specified, pick defaults as follows:
explicitly interactive: ask
otherwise: drop
Quoted 4 lines
Looking at the implementation there is a third option - if
--execis given without-ithen the default is "keep". I'm not sure if having different defaults is convenient or confusing but don't feel that strongly about it.
Heh, in https://lore.kernel.org/git/[email protected]/
you argued that having different defaults was confusing and sounded like you felt strongly about it. Granted, there has been a lot of simplification to the implementation (and description) since then but I'm still inclined to go with the simpler and more easily explained behavior for the defaults based on what you said there.
Quoted 5 lines
I've got a few minor comments below (the mains ones
are saying which commit has been dropped and testing the default behavior when --empty is not given) but basically I like the new patch. Thanks for working on this, the commit message does a good job of explaining the changes.
:-)
Quoted 18 lines
Quoted 18 lines
Signed-off-by Elijah Newren <[email protected]>--- Documentation/git-rebase.txt | 27 ++++++++++++++++--- builtin/rebase.c | 52 ++++++++++++++++++++++++++++++++++++ sequencer.c | 48 +++++++++++++++++++++++++-------- sequencer.h | 1 + t/t3424-rebase-empty.sh | 50 +++++++++++++++++++++++++++++----- t/t3427-rebase-subtree.sh | 8 +++--- 6 files changed, 161 insertions(+), 25 deletions(-)Documentation/git-rebase.txt +0
-0@@ -258,6 +258,22 @@ See also INCOMPATIBLE OPTIONS below. original branch. The index and working tree are also left unchanged as a result.
Quoted 11 lines
Documentation/git-rebase.txt See also INCOMPATIBLE OPTIONS below. · line 269
Quoted 9 lines
Documentation/git-rebase.txt +9
-0+--empty={drop,keep,ask}:: + How to handle commits that are not empty to start and are not + clean cherry-picks of any upstream commit, but which become + empty after rebasing (because they contain a subset of already + upstream changes). With drop (the default), commits that + become empty are dropped. With keep, such commits are kept. + With ask (implied by --interactive), the rebase will halt when + an empty commit is applied allowing you to choose whether to + drop it, edit files more, or just commit the empty changes.We should probably document the default for --exec without -i
I did, but I guess it wasn't clear enough. Maybe I should add "Note that other options like --exec will use the default of drop unless -i/--interactive is specified."?
Quoted 121 lines
Quoted 121 lines
Documentation/git-rebase.txt +14
-3+Note that commits which start empty are kept, and commits which are +clean cherry-picks (as determined by `git log --cherry-mark ...`) are +always dropped. ++ +See also INCOMPATIBLE OPTIONS below. + --keep-empty:: No-op. Rebasing commits that started empty (had no change relative to their parent) used to fail and this option would@@ -561,6 +577,7 @@ are incompatible with the following options: * --interactive * --exec * --keep-empty + * --empty= * --edit-todo * --root when used in combination with --onto@@ -569,6 +586,7 @@ In addition, the following pairs of options are incompatible: * --preserve-merges and --interactive * --preserve-merges and --signoff * --preserve-merges and --rebase-merges + * --preserve-merges and --empty= * --keep-base and --onto * --keep-base and --root@@ -585,9 +603,12 @@ commits that started empty, though these are rare in practice. It also drops commits that become empty and has no option for controlling this behavior. -The interactive backend keeps intentionally empty commits. -Unfortunately, it always halts whenever it runs across a commit that -becomes empty, even when the rebase is not explicitly interactive. +The interactive backend keeps intentionally empty commits. Similar to +the am backend, by default the interactive backend drops commits that +become empty unless -i/--interactive is specified (in which case it +stops and asks the user what to do). The interactive backend also has +an --empty={drop,keep,ask} option for changing the behavior of +handling commits that become empty. Directory rename detection ~~~~~~~~~~~~~~~~~~~~~~~~~~builtin/rebase.c +38
-0@@ -50,8 +50,16 @@ enum rebase_type {REBASE_PRESERVE_MERGES }; +enum empty_type { +EMPTY_UNSPECIFIED =-1 , +EMPTY_DROP , +EMPTY_KEEP , +EMPTY_ASK +}; +struct rebase_options {enum rebase_type type; +enum empty_type empty;const char *state_dir;struct commit *upstream;const char *upstream_name;@@ -91,6 +99,7 @@ struct rebase_options {#define REBASE_OPTIONS_INIT { \ .type = REBASE_UNSPECIFIED, \ + .empty = EMPTY_UNSPECIFIED, \ .flags = REBASE_NO_QUIET, \ .git_am_opts = ARGV_ARRAY_INIT, \ .git_format_patch_opt = STRBUF_INIT \@@ -109,6 +118,8 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts) replay.allow_rerere_auto = opts->allow_rerere_autoupdate; replay.allow_empty =1 ; replay.allow_empty_message = opts->allow_empty_message; + replay.drop_redundant_commits = (opts->empty ==EMPTY_DROP ); + replay.keep_redundant_commits = (opts->empty ==EMPTY_KEEP ); replay.verbose = opts->flags &REBASE_VERBOSE ; replay.reschedule_failed_exec = opts->reschedule_failed_exec; replay.gpg_sign =xstrdup_or_null (opts->gpg_sign_opt);@@ -444,6 +455,10 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg,BUG_ON_OPT_ARG (arg); +/* +* If we ever want to remap --keep-empty to --empty=keep, insert: +* opts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP; +*/ opts->type =REBASE_INTERACTIVE ;return 0 ; }@@ -1350,6 +1365,29 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,return 0 ; } +static enum empty_type parse_empty_value (const char *value) +{ +if (!strcasecmp (value,"drop" )) +return EMPTY_DROP ; +else if (!strcasecmp (value,"keep" )) +return EMPTY_KEEP ; +else if (!strcasecmp (value,"ask" )) +return EMPTY_ASK ; + +die (_ ("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"." ), value); +} + +static int parse_opt_empty (const struct option *opt,const char *arg,int unset) +{ +struct rebase_options *options = opt->value; +enum empty_type value =parse_empty_value (arg); + +BUG_ON_OPT_NEG (unset); + + options->empty = value; +return 0 ; +} + static void NORETURN error_on_missing_default_upstream(void) {struct branch *current_branch =branch_get (NULL );@@ -1494,6 +1532,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)
Quoted 8 lines
builtin/rebase.c int cmd_rebase(int argc, const char **argv, const char *prefix) · line 1536
Quoted 5 lines
builtin/rebase.c +2
-0"ignoring them"), REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),OPT_RERERE_AUTOUPDATE (&options.allow_rerere_autoupdate), + OPT_CALLBACK_F(0, "empty", &options, N_("{drop,keep,ask}"), + N_("how to handle empty commits"),Maybe we should say "how to handle commits that become empty" to distinguish them from commits that start empty which we always keep
Ooh, good catch; will fix.
Quoted 115 lines
Quoted 115 lines
builtin/rebase.c +12
-0+ PARSE_OPT_NONEG, parse_opt_empty), {OPTION_CALLBACK ,'k' ,"keep-empty" , &options,NULL ,N_ ("(DEPRECATED) keep empty commits" ), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,@@ -1760,6 +1801,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)if (!(options.flags &REBASE_NO_QUIET ))argv_array_push (&options.git_am_opts,"-q" ); +if (options.empty !=EMPTY_UNSPECIFIED ) +imply_interactive (&options,"--empty" ); +if (gpg_sign) {free (options.gpg_sign_opt); options.gpg_sign_opt =xstrfmt ("-S%s" , gpg_sign);@@ -1843,6 +1887,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)break ; } +if (options.empty ==EMPTY_UNSPECIFIED ) { +if (options.flags &REBASE_INTERACTIVE_EXPLICIT ) + options.empty =EMPTY_ASK ; +else if (exec.nr >0 ) + options.empty =EMPTY_KEEP ; +else + options.empty =EMPTY_DROP ; + }if (reschedule_failed_exec >0 && !is_interactive (&options))die (_ ("--reschedule-failed-exec requires " "--exec or --interactive" ));sequencer.c +22
-11@@ -158,6 +158,8 @@ 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_reschedule_failed_exec, "rebase-merge/reschedule-failed-exec") +static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits") +static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits") static int git_sequencer_config(const char *k, const char *v, void *cb) {@@ -1483,7 +1485,11 @@ static int is_original_commit_empty(struct commit *commit) }/* -* Do we run "git commit" with "--allow-empty"? +* Should empty commits be allowed? Return status: +* <0: Error in is_index_unchanged(r) or is_original_commit_empty(commit) +* 0: Halt on empty commit +* 1: Allow empty commit +* 2: Drop empty commit */ static int allow_empty(struct repository *r, struct replay_opts *opts,@@ -1492,14 +1498,17 @@ static int allow_empty(struct repository *r,int index_unchanged, originally_empty;/* -* Three cases: +* Four cases: * * (1) we do not allow empty at all and error out. * -* (2) we allow ones that were initially empty, but -* forbid the ones that become empty; +* (2) we allow ones that were initially empty, and +* just drop the ones that become empty * -* (3) we allow both. +* (3) we allow ones that were initially empty, but +* halt for the ones that become empty; +* +* (4) we allow both. */ if (!opts->allow_empty)return 0 ;/* let "git commit" barf as necessary */ @@ -1516,10 +1525,12 @@ static int allow_empty(struct repository *r, originally_empty =is_original_commit_empty (commit);if (originally_empty <0 )return originally_empty; -if (!originally_empty) -return 0 ; -else +if (originally_empty)return 1 ; +else if (opts->drop_redundant_commits) +return 2 ; +else +return 0 ; } static struct {@@ -1730,7 +1741,7 @@ static int do_pick_commit(struct repository *r,char *author =NULL ;struct commit_message msg = {NULL ,NULL ,NULL ,NULL };struct strbuf msgbuf =STRBUF_INIT ; -int res, unborn =0 , reword =0 , allow; +int res, unborn =0 , reword =0 , allow, drop_commit;if (opts->no_commit) {/* @@ -1935,13 +1946,18 @@ static int do_pick_commit(struct repository *r, goto leave; } + drop_commit =0 ; allow =allow_empty (r, opts, commit);if (allow <0 ) { res = allow; goto leave; - }else if (allow) + }else if (allow ==1 ) { flags |=ALLOW_EMPTY ; -if (!opts->no_commit) {
Quoted 8 lines
sequencer.c static int do_pick_commit(struct repository *r, · line 1958
Quoted 3 lines
sequencer.c +3
-0+ }else if (allow ==2 ) { + drop_commit =1 ; +fprintf (stderr,_ ("No changes -- Patch already applied.\n" ));nit pick - usually messages start with a lowercase letter. Would it be helpful to explicitly state which commit is being dropped as well as why? Something like
dropping <oid> <subject> - patch contents already upstream
I was actually just trying to mimic the am-backend here, and copied its message verbatim for this case (see am_run() in builtin/am.c). However, your version does seem more helpful and informative. I'll look into it implementing it here.
Quoted 4 lines
sequencer.c static int do_pick_commit(struct repository *r, · line 1959
Quoted 2 lines
sequencer.c +1
-0+ }// else allow == 0 and there's nothing special to do We don't use // for comments
Oops, sorry. Will fix.
Quoted 116 lines
Quoted 116 lines
sequencer.c +11
-0+if (!opts->no_commit && !drop_commit) {if (author || command ==TODO_REVERT || (flags &AMEND_MSG )) res =do_commit (r, msg_file, author, opts, flags);else @@ -2495,6 +2511,12 @@ static int read_populate_opts(struct replay_opts *opts)if (file_exists (rebase_path_reschedule_failed_exec ())) opts->reschedule_failed_exec =1 ; +if (file_exists (rebase_path_drop_redundant_commits ())) + opts->drop_redundant_commits =1 ; + +if (file_exists (rebase_path_keep_redundant_commits ())) + opts->keep_redundant_commits =1 ; +read_strategy_opts (opts, &buf);strbuf_release (&buf);@@ -2574,6 +2596,10 @@ 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->drop_redundant_commits) +write_file (rebase_path_drop_redundant_commits (),"%s" ,"" ); +if (opts->keep_redundant_commits) +write_file (rebase_path_keep_redundant_commits (),"%s" ,"" );if (opts->reschedule_failed_exec)write_file (rebase_path_reschedule_failed_exec (),"%s" ,"" );sequencer.h +1
-0@@ -39,6 +39,7 @@ struct replay_opts {int allow_rerere_auto;int allow_empty;int allow_empty_message; +int drop_redundant_commits;int keep_redundant_commits;int verbose;int quiet;t/t3424-rebase-empty.sh +43
-7@@ -34,7 +34,7 @@ test_expect_success 'setup test repository' ' git commit -m "Five letters ought to be enough for anybody" ' -test_expect_failure 'rebase (am-backend) with a variety of empty commits' ' +test_expect_failure 'rebase (am-backend)' ' test_when_finished "git rebase --abort" && git checkout -B testing localmods && # rebase (--am) should not drop commits that start empty@@ -45,11 +45,29 @@ test_expect_failure 'rebase (am-backend) with a variety of empty commits' ' test_cmp expect actual ' -test_expect_failure 'rebase --merge with a variety of empty commits' ' - test_when_finished "git rebase --abort" && +test_expect_success 'rebase --merge --empty=drop' ' git checkout -B testing localmods && - # rebase --merge should not halt on the commit that becomes empty - git rebase --merge upstream && + git rebase --merge --empty=drop upstream && + + test_write_lines D C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + +test_expect_success 'rebase --merge --empty=keep' ' + git checkout -B testing localmods && + git rebase --merge --empty=keep upstream && + + test_write_lines D C2 C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + +test_expect_success 'rebase --merge --empty=ask' ' + git checkout -B testing localmods && + test_must_fail git rebase --merge --empty=ask upstream && + + git rebase --skip && test_write_lines D C B A >expect && git log --format=%s >actual &&@@ -58,9 +76,27 @@ test_expect_failure 'rebase --merge with a variety of empty commits' ' GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR -test_expect_success 'rebase --interactive with a variety of empty commits' ' +test_expect_success 'rebase --interactive --empty=drop' ' + git checkout -B testing localmods && + git rebase --interactive --empty=drop upstream && + + test_write_lines D C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + +test_expect_success 'rebase --interactive --empty=keep' ' + git checkout -B testing localmods && + git rebase --interactive --empty=keep upstream && + + test_write_lines D C2 C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + +test_expect_success 'rebase --interactive --empty=ask' ' git checkout -B testing localmods && - test_must_fail git rebase --interactive upstream && + test_must_fail git rebase --interactive --empty=ask upstream &&
Quoted 4 lines
t/t3424-rebase-empty.sh test_expect_failure 'rebase --merge with a variety of empty commits' ' · line 101
Quoted 1 line
t/t3424-rebase-empty.sh +0
-0git rebase --skip &&As the default if --empty is not given is supposed to vary depending on the other options given it would be good to test that I think
I'll add some tests.
Thanks for the thorough review!
Elijah
Re: [PATCH v4 04/19] rebase (interactive-backend): fix handling of commits that become empty
Hi Elijah
Quoted 90 lines · Elijah Newren
Quoted 84 lines · Phillip Wood
Hi Elijah
Quoted 75 lines · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
As established in the previous commit and commit
b00bf1c9a8dd(git-rebase: make --allow-empty-message the default, 2018-06-27), the behavior for rebase with different backends in various edge or corner cases is often more happenstance than design. This commit addresses another such corner case: commits which "become empty".A careful reader may note that there are two types of commits which would become empty due to a rebase:
* [clean cherry-pick] Commits which are clean cherry-picks of upstream commits, as determined by
git log --cherry-mark .... Re-applying these commits would result in an empty set of changes and a duplicative commit message; i.e. these are commits that have "already been applied" upstream.* [become empty] Commits which are not empty to start, are not clean cherry-picks of upstream commits, but which still become empty after being rebased. This happens e.g. when a commit has changes which are a strict subset of the changes in an upstream commit, or when the changes of a commit can be found spread across or among several upstream commits.
Clearly, in both cases the changes in the commit in question are found upstream already, but the commit message may not be in the latter case.
When cherry-mark can determine a commit is already upstream, then because of how cherry-mark works this means the upstream commit message was about the *exact* same set of changes. Thus, the commit messages can be assumed to be fully interchangeable (and are in fact likely to be completely identical). As such, the clean cherry-pick case represents a case when there is no information to be gained by keeping the extra commit around. All rebase types have always dropped these commits, and no one to my knowledge has ever requested that we do otherwise.
For many of the become empty cases (and likely even most), we will also be able to drop the commit without loss of information -- but this isn't quite always the case. Since these commits represent cases that were not clean cherry-picks, there is no upstream commit message explaining the same set of changes. Projects with good commit message hygiene will likely have the explanation from our commit message contained within or spread among the relevant upstream commits, but not all projects run that way. As such, the commit message of the commit being rebased may have reasoning that suggests additional changes that should be made to adapt to the new base, or it may have information that someone wants to add as a note to another commit, or perhaps someone even wants to create an empty commit with the commit message as-is.
Junio commented on the "become-empty" types of commits as follows[1]:
WRT a change that ends up being empty (as opposed to a change that is empty from the beginning), I'd think that the current behaviour is desireable one. "am" based rebase is solely to transplant an existing history and want to stop much less than "interactive" one whose purpose is to polish a series before making it publishable, and asking for confirmation ("this has become empty--do you want to drop it?") is more appropriate from the workflow point of view.
[1] https://lore.kernel.org/git/[email protected]/
I would simply add that his arguments for "am"-based rebases actually apply to all non-explicitly-interactive rebases. Also, since we are stating that different cases should have different defaults, it may be worth providing a flag to allow users to select which behavior they want for these commits.
Introduce a new command line flag for selecting the desired behavior:
--empty={drop,keep,ask}with the definitions:
drop: drop commits which become empty
keep: keep commits which become empty
ask: provide the user a chance to interact and pick what to do with
commits which become empty on a case-by-case basisQuoted 4 lines
In line with Junio's suggestion, if the --empty flag is not specified, pick defaults as follows:
explicitly interactive: ask
otherwise: dropQuoted 4 lines
Looking at the implementation there is a third option - if
--execis given without-ithen the default is "keep". I'm not sure if having different defaults is convenient or confusing but don't feel that strongly about it.
Quoted 6 lines
Heh, in https://lore.kernel.org/git/[email protected]/
you argued that having different defaults was confusing and sounded like you felt strongly about it. Granted, there has been a lot of simplification to the implementation (and description) since then but I'm still inclined to go with the simpler and more easily explained behavior for the defaults based on what you said there.
I would still prefer a common default but I can see the logic to the defaults you are proposing I just think it makes it hard to explain and it'll end up surprising someone who was expecting a different default. When I said I didn't feel that strongly I meant that it wasn't worth holding up this just for this.
Best Wishes
Phillip
Quoted 437 lines
Quoted 5 lines
I've got a few minor comments below (the mains ones
are saying which commit has been dropped and testing the default behavior when --empty is not given) but basically I like the new patch. Thanks for working on this, the commit message does a good job of explaining the changes.:-)
Quoted 18 lines
Quoted 18 lines
Signed-off-by Elijah Newren <[email protected]>--- Documentation/git-rebase.txt | 27 ++++++++++++++++--- builtin/rebase.c | 52 ++++++++++++++++++++++++++++++++++++ sequencer.c | 48 +++++++++++++++++++++++++-------- sequencer.h | 1 + t/t3424-rebase-empty.sh | 50 +++++++++++++++++++++++++++++----- t/t3427-rebase-subtree.sh | 8 +++--- 6 files changed, 161 insertions(+), 25 deletions(-)Documentation/git-rebase.txt +0
-0@@ -258,6 +258,22 @@ See also INCOMPATIBLE OPTIONS below. original branch. The index and working tree are also left unchanged as a result.Quoted 11 lines
Documentation/git-rebase.txt See also INCOMPATIBLE OPTIONS below. · line 269
Quoted 9 lines
Documentation/git-rebase.txt +9
-0+--empty={drop,keep,ask}:: + How to handle commits that are not empty to start and are not + clean cherry-picks of any upstream commit, but which become + empty after rebasing (because they contain a subset of already + upstream changes). With drop (the default), commits that + become empty are dropped. With keep, such commits are kept. + With ask (implied by --interactive), the rebase will halt when + an empty commit is applied allowing you to choose whether to + drop it, edit files more, or just commit the empty changes.We should probably document the default for --exec without -i
I did, but I guess it wasn't clear enough. Maybe I should add "Note that other options like --exec will use the default of drop unless -i/--interactive is specified."?
Quoted 121 lines
Quoted 121 lines
Documentation/git-rebase.txt +14
-3+Note that commits which start empty are kept, and commits which are +clean cherry-picks (as determined by `git log --cherry-mark ...`) are +always dropped. ++ +See also INCOMPATIBLE OPTIONS below. + --keep-empty:: No-op. Rebasing commits that started empty (had no change relative to their parent) used to fail and this option would@@ -561,6 +577,7 @@ are incompatible with the following options: * --interactive * --exec * --keep-empty + * --empty= * --edit-todo * --root when used in combination with --onto@@ -569,6 +586,7 @@ In addition, the following pairs of options are incompatible: * --preserve-merges and --interactive * --preserve-merges and --signoff * --preserve-merges and --rebase-merges + * --preserve-merges and --empty= * --keep-base and --onto * --keep-base and --root@@ -585,9 +603,12 @@ commits that started empty, though these are rare in practice. It also drops commits that become empty and has no option for controlling this behavior. -The interactive backend keeps intentionally empty commits. -Unfortunately, it always halts whenever it runs across a commit that -becomes empty, even when the rebase is not explicitly interactive. +The interactive backend keeps intentionally empty commits. Similar to +the am backend, by default the interactive backend drops commits that +become empty unless -i/--interactive is specified (in which case it +stops and asks the user what to do). The interactive backend also has +an --empty={drop,keep,ask} option for changing the behavior of +handling commits that become empty. Directory rename detection ~~~~~~~~~~~~~~~~~~~~~~~~~~builtin/rebase.c +38
-0@@ -50,8 +50,16 @@ enum rebase_type {REBASE_PRESERVE_MERGES }; +enum empty_type { +EMPTY_UNSPECIFIED =-1 , +EMPTY_DROP , +EMPTY_KEEP , +EMPTY_ASK +}; +struct rebase_options {enum rebase_type type; +enum empty_type empty;const char *state_dir;struct commit *upstream;const char *upstream_name;@@ -91,6 +99,7 @@ struct rebase_options {#define REBASE_OPTIONS_INIT { \ .type = REBASE_UNSPECIFIED, \ + .empty = EMPTY_UNSPECIFIED, \ .flags = REBASE_NO_QUIET, \ .git_am_opts = ARGV_ARRAY_INIT, \ .git_format_patch_opt = STRBUF_INIT \@@ -109,6 +118,8 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts) replay.allow_rerere_auto = opts->allow_rerere_autoupdate; replay.allow_empty =1 ; replay.allow_empty_message = opts->allow_empty_message; + replay.drop_redundant_commits = (opts->empty ==EMPTY_DROP ); + replay.keep_redundant_commits = (opts->empty ==EMPTY_KEEP ); replay.verbose = opts->flags &REBASE_VERBOSE ; replay.reschedule_failed_exec = opts->reschedule_failed_exec; replay.gpg_sign =xstrdup_or_null (opts->gpg_sign_opt);@@ -444,6 +455,10 @@ static int parse_opt_keep_empty(const struct option *opt, const char *arg,BUG_ON_OPT_ARG (arg); +/* +* If we ever want to remap --keep-empty to --empty=keep, insert: +* opts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP; +*/ opts->type =REBASE_INTERACTIVE ;return 0 ; }@@ -1350,6 +1365,29 @@ static int parse_opt_interactive(const struct option *opt, const char *arg,return 0 ; } +static enum empty_type parse_empty_value (const char *value) +{ +if (!strcasecmp (value,"drop" )) +return EMPTY_DROP ; +else if (!strcasecmp (value,"keep" )) +return EMPTY_KEEP ; +else if (!strcasecmp (value,"ask" )) +return EMPTY_ASK ; + +die (_ ("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"." ), value); +} + +static int parse_opt_empty (const struct option *opt,const char *arg,int unset) +{ +struct rebase_options *options = opt->value; +enum empty_type value =parse_empty_value (arg); + +BUG_ON_OPT_NEG (unset); + + options->empty = value; +return 0 ; +} + static void NORETURN error_on_missing_default_upstream(void) {struct branch *current_branch =branch_get (NULL );@@ -1494,6 +1532,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)Quoted 8 lines
builtin/rebase.c int cmd_rebase(int argc, const char **argv, const char *prefix) · line 1536
Quoted 5 lines
builtin/rebase.c +2
-0"ignoring them"), REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),OPT_RERERE_AUTOUPDATE (&options.allow_rerere_autoupdate), + OPT_CALLBACK_F(0, "empty", &options, N_("{drop,keep,ask}"), + N_("how to handle empty commits"),Maybe we should say "how to handle commits that become empty" to distinguish them from commits that start empty which we always keep
Ooh, good catch; will fix.
Quoted 115 lines
Quoted 115 lines
builtin/rebase.c +12
-0+ PARSE_OPT_NONEG, parse_opt_empty), {OPTION_CALLBACK ,'k' ,"keep-empty" , &options,NULL ,N_ ("(DEPRECATED) keep empty commits" ), PARSE_OPT_NOARG | PARSE_OPT_HIDDEN,@@ -1760,6 +1801,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)if (!(options.flags &REBASE_NO_QUIET ))argv_array_push (&options.git_am_opts,"-q" ); +if (options.empty !=EMPTY_UNSPECIFIED ) +imply_interactive (&options,"--empty" ); +if (gpg_sign) {free (options.gpg_sign_opt); options.gpg_sign_opt =xstrfmt ("-S%s" , gpg_sign);@@ -1843,6 +1887,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)break ; } +if (options.empty ==EMPTY_UNSPECIFIED ) { +if (options.flags &REBASE_INTERACTIVE_EXPLICIT ) + options.empty =EMPTY_ASK ; +else if (exec.nr >0 ) + options.empty =EMPTY_KEEP ; +else + options.empty =EMPTY_DROP ; + }if (reschedule_failed_exec >0 && !is_interactive (&options))die (_ ("--reschedule-failed-exec requires " "--exec or --interactive" ));sequencer.c +22
-11@@ -158,6 +158,8 @@ 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_reschedule_failed_exec, "rebase-merge/reschedule-failed-exec") +static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits") +static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits") static int git_sequencer_config(const char *k, const char *v, void *cb) {@@ -1483,7 +1485,11 @@ static int is_original_commit_empty(struct commit *commit) }/* -* Do we run "git commit" with "--allow-empty"? +* Should empty commits be allowed? Return status: +* <0: Error in is_index_unchanged(r) or is_original_commit_empty(commit) +* 0: Halt on empty commit +* 1: Allow empty commit +* 2: Drop empty commit */ static int allow_empty(struct repository *r, struct replay_opts *opts,@@ -1492,14 +1498,17 @@ static int allow_empty(struct repository *r,int index_unchanged, originally_empty;/* -* Three cases: +* Four cases: * * (1) we do not allow empty at all and error out. * -* (2) we allow ones that were initially empty, but -* forbid the ones that become empty; +* (2) we allow ones that were initially empty, and +* just drop the ones that become empty * -* (3) we allow both. +* (3) we allow ones that were initially empty, but +* halt for the ones that become empty; +* +* (4) we allow both. */ if (!opts->allow_empty)return 0 ;/* let "git commit" barf as necessary */ @@ -1516,10 +1525,12 @@ static int allow_empty(struct repository *r, originally_empty =is_original_commit_empty (commit);if (originally_empty <0 )return originally_empty; -if (!originally_empty) -return 0 ; -else +if (originally_empty)return 1 ; +else if (opts->drop_redundant_commits) +return 2 ; +else +return 0 ; } static struct {@@ -1730,7 +1741,7 @@ static int do_pick_commit(struct repository *r,char *author =NULL ;struct commit_message msg = {NULL ,NULL ,NULL ,NULL };struct strbuf msgbuf =STRBUF_INIT ; -int res, unborn =0 , reword =0 , allow; +int res, unborn =0 , reword =0 , allow, drop_commit;if (opts->no_commit) {/* @@ -1935,13 +1946,18 @@ static int do_pick_commit(struct repository *r, goto leave; } + drop_commit =0 ; allow =allow_empty (r, opts, commit);if (allow <0 ) { res = allow; goto leave; - }else if (allow) + }else if (allow ==1 ) { flags |=ALLOW_EMPTY ; -if (!opts->no_commit) {Quoted 8 lines
sequencer.c static int do_pick_commit(struct repository *r, · line 1958
Quoted 3 lines
sequencer.c +3
-0+ }else if (allow ==2 ) { + drop_commit =1 ; +fprintf (stderr,_ ("No changes -- Patch already applied.\n" ));nit pick - usually messages start with a lowercase letter. Would it be helpful to explicitly state which commit is being dropped as well as why? Something like
dropping <oid> <subject> - patch contents already upstreamI was actually just trying to mimic the am-backend here, and copied its message verbatim for this case (see
am_run()inbuiltin/am.c). However, your version does seem more helpful and informative. I'll look into it implementing it here.Quoted 4 lines
sequencer.c static int do_pick_commit(struct repository *r, · line 1959
Quoted 2 lines
sequencer.c +1
-0+ }// else allow == 0 and there's nothing special to do We don't use // for comments
Oops, sorry. Will fix.
Quoted 116 lines
Quoted 116 lines
sequencer.c +11
-0+if (!opts->no_commit && !drop_commit) {if (author || command ==TODO_REVERT || (flags &AMEND_MSG )) res =do_commit (r, msg_file, author, opts, flags);else @@ -2495,6 +2511,12 @@ static int read_populate_opts(struct replay_opts *opts)if (file_exists (rebase_path_reschedule_failed_exec ())) opts->reschedule_failed_exec =1 ; +if (file_exists (rebase_path_drop_redundant_commits ())) + opts->drop_redundant_commits =1 ; + +if (file_exists (rebase_path_keep_redundant_commits ())) + opts->keep_redundant_commits =1 ; +read_strategy_opts (opts, &buf);strbuf_release (&buf);@@ -2574,6 +2596,10 @@ 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->drop_redundant_commits) +write_file (rebase_path_drop_redundant_commits (),"%s" ,"" ); +if (opts->keep_redundant_commits) +write_file (rebase_path_keep_redundant_commits (),"%s" ,"" );if (opts->reschedule_failed_exec)write_file (rebase_path_reschedule_failed_exec (),"%s" ,"" );sequencer.h +1
-0@@ -39,6 +39,7 @@ struct replay_opts {int allow_rerere_auto;int allow_empty;int allow_empty_message; +int drop_redundant_commits;int keep_redundant_commits;int verbose;int quiet;t/t3424-rebase-empty.sh +43
-7@@ -34,7 +34,7 @@ test_expect_success 'setup test repository' ' git commit -m "Five letters ought to be enough for anybody" ' -test_expect_failure 'rebase (am-backend) with a variety of empty commits' ' +test_expect_failure 'rebase (am-backend)' ' test_when_finished "git rebase --abort" && git checkout -B testing localmods && # rebase (--am) should not drop commits that start empty@@ -45,11 +45,29 @@ test_expect_failure 'rebase (am-backend) with a variety of empty commits' ' test_cmp expect actual ' -test_expect_failure 'rebase --merge with a variety of empty commits' ' - test_when_finished "git rebase --abort" && +test_expect_success 'rebase --merge --empty=drop' ' git checkout -B testing localmods && - # rebase --merge should not halt on the commit that becomes empty - git rebase --merge upstream && + git rebase --merge --empty=drop upstream && + + test_write_lines D C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + +test_expect_success 'rebase --merge --empty=keep' ' + git checkout -B testing localmods && + git rebase --merge --empty=keep upstream && + + test_write_lines D C2 C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + +test_expect_success 'rebase --merge --empty=ask' ' + git checkout -B testing localmods && + test_must_fail git rebase --merge --empty=ask upstream && + + git rebase --skip && test_write_lines D C B A >expect && git log --format=%s >actual &&@@ -58,9 +76,27 @@ test_expect_failure 'rebase --merge with a variety of empty commits' ' GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR -test_expect_success 'rebase --interactive with a variety of empty commits' ' +test_expect_success 'rebase --interactive --empty=drop' ' + git checkout -B testing localmods && + git rebase --interactive --empty=drop upstream && + + test_write_lines D C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + +test_expect_success 'rebase --interactive --empty=keep' ' + git checkout -B testing localmods && + git rebase --interactive --empty=keep upstream && + + test_write_lines D C2 C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + +test_expect_success 'rebase --interactive --empty=ask' ' git checkout -B testing localmods && - test_must_fail git rebase --interactive upstream && + test_must_fail git rebase --interactive --empty=ask upstream &&Quoted 4 lines
t/t3424-rebase-empty.sh test_expect_failure 'rebase --merge with a variety of empty commits' ' · line 101
Quoted 1 line
t/t3424-rebase-empty.sh +0
-0git rebase --skip &&As the default if --empty is not given is supposed to vary depending on the other options given it would be good to test that I think
I'll add some tests.
Thanks for the thorough review!
Elijah
PATCH v4 5/19 t3406: simplify an already simple test
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
When the merge backend was re-implemented on top of the interactive backend, the output of rebase --merge changed a little. This change allowed this test to be simplified, though it wasn't noticed until now. Simplify the testcase a little.
--- t/t3406-rebase-message.sh | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-)
t/t3406-rebase-message.sh +2 -5
@@ -18,11 +18,8 @@ test_expect_success 'setup' ' ' test_expect_success 'rebase -m' ' - git rebase -m master >report && - >expect && - sed -n -e "/^Already applied: /p" \ - -e "/^Committed: /p" report >actual && - test_cmp expect actual + git rebase -m master >actual && + test_must_be_empty actual ' test_expect_success 'rebase against master twice' '
Signature · 1 line
gitgitgadget
PATCH v4 6/19 rebase, sequencer: remove the broken GIT_QUIET handling
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
The GIT_QUIET environment variable was used to signal the non-am backends that the rebase should perform quietly. The preserve-merges backend does not make use of the quiet flag anywhere (other than to write out its state whenever it writes state), and this mechanism was broken in the conversion from shell to C. Since this environment variable was specifically designed for scripts and the only backend that would still use it is no longer a script, just gut this code.
A subsequent commit will fix --quiet for the interactive/merge backend in a different way.
--- builtin/rebase.c | 6 ++---- sequencer.c | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-)
builtin/rebase.c +2 -4
@@ -699,8 +699,8 @@ static int rebase_write_basic_state(struct rebase_options *opts) opts->onto ? oid_to_hex(&opts->onto->object.oid) : "");write_file (state_dir_path ("orig-head" , opts),"%s" ,oid_to_hex (&opts->orig_head)); -write_file (state_dir_path ("quiet" , opts),"%s" , - opts->flags &REBASE_NO_QUIET ?"" :"t" ); +if (!(opts->flags &REBASE_NO_QUIET )) +write_file (state_dir_path ("quiet" , opts),"%s" ,"" );if (opts->flags &REBASE_VERBOSE )write_file (state_dir_path ("verbose" , opts),"%s" ,"" );if (opts->strategy)
@@ -1153,8 +1153,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)add_var (&script_snippet,"revisions" , opts->revisions);add_var (&script_snippet,"restrict_revision" , opts->restrict_revision ?oid_to_hex (&opts->restrict_revision->object.oid) :NULL ); -add_var (&script_snippet,"GIT_QUIET" , - opts->flags &REBASE_NO_QUIET ?"" :"t" );sq_quote_argv_pretty (&buf, opts->git_am_opts.argv);add_var (&script_snippet,"git_am_opt" , buf.buf);strbuf_release (&buf);
sequencer.c +2 -4
@@ -2568,8 +2568,6 @@ static void write_strategy_opts(struct replay_opts *opts)int write_basic_state (struct replay_opts *opts,const char *head_name,struct commit *onto,const char *orig_head) { -const char *quiet =getenv ("GIT_QUIET" ); -if (head_name)write_file (rebase_path_head_name (),"%s\n" , head_name);if (onto)
@@ -2578,8 +2576,8 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,if (orig_head)write_file (rebase_path_orig_head (),"%s\n" , orig_head); -if (quiet) -write_file (rebase_path_quiet (),"%s\n" , quiet); +if (opts->quiet) +write_file (rebase_path_quiet (),"%s" ,"" );if (opts->verbose)write_file (rebase_path_verbose (),"%s" ,"" );if (opts->strategy)
Signature · 1 line
gitgitgadget
PATCH v4 7/19 rebase: make sure to pass along the quiet flag to the sequencer
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
--- builtin/rebase.c | 3 ++- t/t3400-rebase.sh | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-)
builtin/rebase.c +2 -1
@@ -120,6 +120,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts) replay.allow_empty_message = opts->allow_empty_message; replay.drop_redundant_commits = (opts->empty ==EMPTY_DROP ); replay.keep_redundant_commits = (opts->empty ==EMPTY_KEEP ); + replay.quiet = !(opts->flags &REBASE_NO_QUIET ); replay.verbose = opts->flags &REBASE_VERBOSE ; replay.reschedule_failed_exec = opts->reschedule_failed_exec; replay.gpg_sign =xstrdup_or_null (opts->gpg_sign_opt);
@@ -1476,7 +1477,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("allow pre-rebase hook to run")),OPT_NEGBIT ('q' ,"quiet" , &options.flags,N_ ("be quiet. implies --no-stat" ), -REBASE_NO_QUIET |REBASE_VERBOSE |REBASE_DIFFSTAT ), +REBASE_NO_QUIET |REBASE_VERBOSE |REBASE_DIFFSTAT ),OPT_BIT ('v' ,"verbose" , &options.flags,N_ ("display a diffstat of what changed upstream" ), REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
t/t3400-rebase.sh +7 -1
@@ -206,12 +206,18 @@ test_expect_success 'cherry-picked commits and fork-point work together' ' test_cmp expect D ' -test_expect_success 'rebase -q is quiet' ' +test_expect_success 'rebase --am -q is quiet' ' git checkout -b quiet topic && git rebase -q master >output.out 2>&1 && test_must_be_empty output.out ' +test_expect_success 'rebase --merge -q is quiet' ' + git checkout -B quiet topic && + git rebase --merge -q master >output.out 2>&1 && + test_must_be_empty output.out +' + test_expect_success 'Rebase a commit that sprinkles CRs in' ' ( echo "One" &&
Signature · 1 line
gitgitgadget
PATCH v4 8/19 rebase: fix handling of restrict_revision
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
restrict_revision in the original shell script was an excluded revision range. It is also treated that way by the am-backend. In the conversion from shell to C (see commit 6ab54d17be3f ("rebase -i:
implement the logic to initialize $revisions in C", 2018-08-28)), the interactive-backend accidentally treated it as a positive revision rather than a negated one.
This was missed as there were no tests in the testsuite that tested an interactive rebase with fork-point behavior.
--- builtin/rebase.c | 4 ++-- t/t3400-rebase.sh | 20 +++++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-)
builtin/rebase.c +2 -2
@@ -348,8 +348,8 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)argv_array_pushl (&make_script_args,"" , revisions,NULL );if (opts->restrict_revision) -argv_array_push (&make_script_args, -oid_to_hex (&opts->restrict_revision->object.oid)); +argv_array_pushf (&make_script_args,"^%s" , +oid_to_hex (&opts->restrict_revision->object.oid)); ret = sequencer_make_script(the_repository, &todo_list.buf, make_script_args.argc, make_script_args.argv,
t/t3400-rebase.sh +19 -1
@@ -165,11 +165,29 @@ test_expect_success 'rebase works with format.useAutoBase' ' git rebase master ' -test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' ' +test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--merge)' ' git checkout -b default-base master && git checkout -b default topic && git config branch.default.remote . && git config branch.default.merge refs/heads/default-base && + git rebase --merge && + git rev-parse --verify default-base >expect && + git rev-parse default~1 >actual && + test_cmp expect actual && + git checkout default-base && + git reset --hard HEAD^ && + git checkout default && + git rebase --merge && + git rev-parse --verify default-base >expect && + git rev-parse default~1 >actual && + test_cmp expect actual +' + +test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' ' + git checkout -B default-base master && + git checkout -B default topic && + git config branch.default.remote . && + git config branch.default.merge refs/heads/default-base && git rebase && git rev-parse --verify default-base >expect && git rev-parse default~1 >actual &&
Signature · 1 line
gitgitgadget
PATCH v4 9/19 t3432: make these tests work with either am or merge backends
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
t3432 had several stress tests for can_fast_forward(), whose intent was to ensure we were using the optimization of just fast forwarding when possible. However, these tests verified that fast forwards had happened based on the output that rebase printed to the terminal. We can instead test more directly that we actually fast-forwarded by checking the reflog, which also has the side effect of making the tests applicable for the merge/interactive backend.
This change does lose the distinction between "noop" and "noop-force", but as stated in commit c9efc216830f ("t3432: test for --no-ff's interaction with fast-forward", 2019-08-27) which introduced that
distinction: "These tests aren't supposed to endorse the status quo, just test for what we're currently doing.".
This change does not actually run these tests with the merge/interactive backend; instead this is just a preparatory commit. A subsequent commit which fixes can_fast_forward() to work with that backend will then also change t3432 to add tests of that backend as well.
--- t/t3432-rebase-fast-forward.sh | 48 ++++++++++++++++------------------ 1 file changed, 22 insertions(+), 26 deletions(-)
t/t3432-rebase-fast-forward.sh +22 -26
@@ -44,19 +44,15 @@ test_rebase_same_head_ () { test_expect_$status "git rebase$flag $* with $changes is $what with $cmp HEAD" " oldhead=\$(git rev-parse HEAD) && test_when_finished 'git reset --hard \$oldhead' && + cp .git/logs/HEAD expect && git rebase$flag $* >stdout && if test $what = work then - # Must check this case first, for 'is up to - # date, rebase forced[...]rewinding head' cases - test_i18ngrep 'rewinding head' stdout + old=\$(wc -l <expect) && + test_line_count '-gt' \$old .git/logs/HEAD elif test $what = noop then - test_i18ngrep 'is up to date' stdout && - test_i18ngrep ! 'rebase forced' stdout - elif test $what = noop-force - then - test_i18ngrep 'is up to date, rebase forced' stdout + test_cmp expect .git/logs/HEAD fi && newhead=\$(git rev-parse HEAD) && if test $cmp = same
@@ -71,14 +67,14 @@ test_rebase_same_head_ () { changes='no changes' test_rebase_same_head success noop same success work same -test_rebase_same_head success noop same success noop-force same master -test_rebase_same_head success noop same success noop-force diff --onto B B -test_rebase_same_head success noop same success noop-force diff --onto B... B -test_rebase_same_head success noop same success noop-force same --onto master... master -test_rebase_same_head success noop same success noop-force same --keep-base master -test_rebase_same_head success noop same success noop-force same --keep-base -test_rebase_same_head success noop same success noop-force same --no-fork-point -test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point +test_rebase_same_head success noop same success work same master +test_rebase_same_head success noop same success work diff --onto B B +test_rebase_same_head success noop same success work diff --onto B... B +test_rebase_same_head success noop same success work same --onto master... master +test_rebase_same_head success noop same success work same --keep-base master +test_rebase_same_head success noop same success work same --keep-base +test_rebase_same_head success noop same success work same --no-fork-point +test_rebase_same_head success noop same success work same --keep-base --no-fork-point test_rebase_same_head success noop same success work same --fork-point master test_rebase_same_head success noop same success work diff --fork-point --onto B B test_rebase_same_head success noop same success work diff --fork-point --onto B... B
@@ -91,14 +87,14 @@ test_expect_success 'add work same to side' ' changes='our changes' test_rebase_same_head success noop same success work same -test_rebase_same_head success noop same success noop-force same master -test_rebase_same_head success noop same success noop-force diff --onto B B -test_rebase_same_head success noop same success noop-force diff --onto B... B -test_rebase_same_head success noop same success noop-force same --onto master... master -test_rebase_same_head success noop same success noop-force same --keep-base master -test_rebase_same_head success noop same success noop-force same --keep-base -test_rebase_same_head success noop same success noop-force same --no-fork-point -test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point +test_rebase_same_head success noop same success work same master +test_rebase_same_head success noop same success work diff --onto B B +test_rebase_same_head success noop same success work diff --onto B... B +test_rebase_same_head success noop same success work same --onto master... master +test_rebase_same_head success noop same success work same --keep-base master +test_rebase_same_head success noop same success work same --keep-base +test_rebase_same_head success noop same success work same --no-fork-point +test_rebase_same_head success noop same success work same --keep-base --no-fork-point test_rebase_same_head success noop same success work same --fork-point master test_rebase_same_head success noop same success work diff --fork-point --onto B B test_rebase_same_head success noop same success work diff --fork-point --onto B... B
@@ -112,8 +108,8 @@ test_expect_success 'add work same to upstream' ' ' changes='our and their changes' -test_rebase_same_head success noop same success noop-force diff --onto B B -test_rebase_same_head success noop same success noop-force diff --onto B... B +test_rebase_same_head success noop same success work diff --onto B B +test_rebase_same_head success noop same success work diff --onto B... B test_rebase_same_head success noop same success work diff --onto master... master test_rebase_same_head success noop same success work diff --keep-base master test_rebase_same_head success noop same success work diff --keep-base
Signature · 1 line
gitgitgadget
PATCH v4 10/19 rebase: allow more types of rebases to fast-forward
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
In the past, we dis-allowed rebases using the interactive backend from performing a fast-forward to short-circuit the rebase operation. This made sense for explicitly interactive rebases and some implicitly interactive rebases, but certainly became overly stringent when the merge backend was re-implemented via the interactive backend.
Just as the am-based rebase has always had to disable the fast-forward based on a variety of conditions or flags (e.g. --signoff, --whitespace, etc.), we need to do the same but now with a few more options. However, continuing to use REBASE_FORCE for tracking this is problematic because the interactive backend used it for a different purpose. (When REBASE_FORCE wasn't set, the interactive backend would not fast-forward the whole series but would fast-forward individual "pick" commits at the beginning of the todo list, and then a squash or something would cause it to start generating new commits.) So, introduce a new allow_preemptive_ff flag contained within cmd_rebase() and use it to track whether we are going to allow a pre-emptive fast-forward that short-circuits the whole rebase.
--- builtin/rebase.c | 18 ++++++++++++++---- t/t3432-rebase-fast-forward.sh | 2 ++ 2 files changed, 16 insertions(+), 4 deletions(-)
builtin/rebase.c +14 -4
@@ -1467,6 +1467,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)struct object_id squash_onto;char *squash_onto_name =NULL ;int reschedule_failed_exec =-1 ; +int allow_preemptive_ff =1 ;struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"),
@@ -1774,13 +1775,20 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) state_dir_base, cmd_live_rebase, buf.buf); } +if ((options.flags &REBASE_INTERACTIVE_EXPLICIT ) || + (action !=ACTION_NONE ) || + (exec.nr >0 ) || + options.autosquash) { + allow_preemptive_ff =0 ; + } +for (i =0 ; i < options.git_am_opts.argc; i++) {const char *option = options.git_am_opts.argv[i], *p;if (!strcmp (option,"--committer-date-is-author-date" ) || !strcmp (option,"--ignore-date" ) || !strcmp (option,"--whitespace=fix" ) || !strcmp (option,"--whitespace=strip" )) - options.flags |=REBASE_FORCE ; + allow_preemptive_ff =0 ;else if (skip_prefix (option,"-C" , &p)) {while (*p)if (!isdigit (*(p++)))
@@ -2116,12 +2124,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)/* * Check if we are already based on onto with linear history, * in which case we could fast-forward without replacing the commits -* with new commits recreated by replaying their changes. This -* optimization must not be done if this is an interactive rebase. +* with new commits recreated by replaying their changes. +* +* Note that can_fast_forward() initializes merge_base, so we have to +* call it before checking allow_preemptive_ff. */ if (can_fast_forward (options.onto, options.upstream, options.restrict_revision, &options.orig_head, &merge_base) && - !is_interactive (&options)) { + allow_preemptive_ff) {int flag;if (!(options.flags &REBASE_FORCE )) {
t/t3432-rebase-fast-forward.sh +2 -0
@@ -30,6 +30,8 @@ test_rebase_same_head () { shift && test_rebase_same_head_ $status_n $what_n $cmp_n "" "$*" && test_rebase_same_head_ $status_f $what_f $cmp_f " --no-ff" "$*" + test_rebase_same_head_ $status_n $what_n $cmp_n " --merge" "$*" && + test_rebase_same_head_ $status_f $what_f $cmp_f " --merge --no-ff" "$*" } test_rebase_same_head_ () {
Signature · 1 line
gitgitgadget
PATCH v4 11/19 git-rebase.txt: add more details about behavioral differences of backends
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
--- Documentation/git-rebase.txt | 85 +++++++++++++++++++++++++++++++++--- 1 file changed, 80 insertions(+), 5 deletions(-)
Documentation/git-rebase.txt +80 -5
@@ -414,7 +414,7 @@ with `--keep-base` in order to drop those commits from your branch. --ignore-whitespace:: --whitespace=<option>:: - These flag are passed to the 'git apply' program + These flags are passed to the 'git apply' program (see linkgit:git-apply[1]) that applies the patch. + See also INCOMPATIBLE OPTIONS below.
@@ -561,8 +561,8 @@ The following options: * --committer-date-is-author-date * --ignore-date - * --whitespace * --ignore-whitespace + * --whitespace * -C are incompatible with the following options:
@@ -613,9 +613,84 @@ handling commits that become empty. Directory rename detection ~~~~~~~~~~~~~~~~~~~~~~~~~~ -Directory rename heuristics are enabled in the merge and interactive -backends. Due to the lack of accurate tree information, directory -rename detection is disabled in the am backend. +Due to the lack of accurate tree information (arising from +constructing fake ancestors with the limited information available in +patches), directory rename detection is disabled in the am backend. +Disabled directory rename detection means that if one side of history +renames a directory and the other adds new files to the old directory, +then the new files will be left behind in the old directory without +any warning at the time of rebasing that you may want to move these +files into the new directory. + +Directory rename detection works with the merge and interactive +backends to provide you warnings in such cases. + +Context +~~~~~~~ + +The am backend works by creating a sequence of patches (by calling +`format-patch` internally), and then applying the patches in sequence +(calling `am` internally). Patches are composed of multiple hunks, +each with line numbers, a context region, and the actual changes. The +line numbers have to be taken with some fuzz, since the other side +will likely have inserted or deleted lines earlier in the file. The +context region is meant to help find how to adjust the line numbers in +order to apply the changes to the right lines. However, if multiple +areas of the code have the same surrounding lines of context, the +wrong one can be picked. There are real-world cases where this has +caused commits to be reapplied incorrectly with no conflicts reported. +Setting diff.context to a larger value may prevent such types of +problems, but increases the chance of spurious conflicts (since it +will require more lines of matching context to apply). + +The interactive backend works with a full copy of each relevant file, +insulating it from these types of problems. + +Labelling of conflicts markers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When there are content conflicts, the merge machinery tries to +annotate each side's conflict markers with the commits where the +content came from. Since the am backend drops the original +information about the rebased commits and their parents (and instead +generates new fake commits based off limited information in the +generated patches), those commits cannot be identified; instead it has +to fall back to a commit summary. Also, when merge.conflictStyle is +set to diff3, the am backend will use "constructed merge base" to +label the content from the merge base, and thus provide no information +about the merge base commit whatsoever. + +The interactive backend works with the full commits on both sides of +history and thus has no such limitations. + +Hooks +~~~~~ + +The am backend has not traditionally called the post-commit hook, +while the merge/interactive backend has. However, this was by +accident of implementation rather than by design. Both backends +should have the same behavior, though it is not clear which one is +correct. + +Miscellaneous differences +~~~~~~~~~~~~~~~~~~~~~~~~~ + +There are a few more behavioral differences that most folks would +probably consider inconsequential but which are mentioned for +completeness: + +* Reflog: The two backends will use different wording when describing + the changes made in the reflog, though both will make use of the + word "rebase". + +* Progress, informational, and error messages: The two backends + provide slightly different progress and informational messages. + Also, the am backend writes error messages (such as "Your files + would be overwritten...") to stdout, while the interactive backend + writes them to stderr. + +* State directories: The two backends keep their state in different + directories under .git/ include::merge-strategies.txt[]
Signature · 1 line
gitgitgadget
PATCH v4 12/19 rebase: move incompatibility checks between backend options a bit earlier
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
--- builtin/rebase.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-)
builtin/rebase.c +11 -11
@@ -1878,6 +1878,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)if (isatty (2 ) && options.flags &REBASE_NO_QUIET )strbuf_addstr (&options.git_format_patch_opt," --progress" ); +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--) +if (strcmp (options.git_am_opts.argv[i],"-q" )) +break ; + +if (is_interactive (&options) && i >=0 ) +die (_ ("cannot combine am options with either " +"interactive or merge options" )); + } +switch (options.type) {case REBASE_MERGE :case REBASE_INTERACTIVE :
@@ -1908,17 +1919,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)if (reschedule_failed_exec >=0 ) options.reschedule_failed_exec = reschedule_failed_exec; -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--) -if (strcmp (options.git_am_opts.argv[i],"-q" )) -break ; - -if (is_interactive (&options) && i >=0 ) -die (_ ("cannot combine am options with either " -"interactive or merge options" )); - } -if (options.signoff) {if (options.type ==REBASE_PRESERVE_MERGES ) die("cannot combine '--signoff' with "
Signature · 1 line
gitgitgadget
PATCH v4 13/19 rebase: add an --am option
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
Currently, this option doesn't do anything except error out if any options requiring the interactive-backend are also passed. However, when we make the default backend configurable later in this series, this flag will provide a way to override the config setting.
--- Documentation/git-rebase.txt | 11 ++++++++++- builtin/rebase.c | 18 +++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-)
Documentation/git-rebase.txt +10 -1
@@ -258,6 +258,13 @@ See also INCOMPATIBLE OPTIONS below. original branch. The index and working tree are also left unchanged as a result. +--am: + Use git-am internally to rebase. This option may become a + no-op in the future once the interactive backend handles + everything the am one does. ++ +See also INCOMPATIBLE OPTIONS below. + --empty={drop,keep,ask}:: How to handle commits that are not empty to start and are not clean cherry-picks of any upstream commit, but which become
@@ -376,7 +383,7 @@ See also INCOMPATIBLE OPTIONS below. Ensure at least <n> lines of surrounding context match before and after each change. When fewer lines of surrounding context exist they all must match. By default no context is - ever ignored. + ever ignored. Implies --am. + See also INCOMPATIBLE OPTIONS below.
@@ -416,6 +423,7 @@ with `--keep-base` in order to drop those commits from your branch. --whitespace=<option>:: These flags are passed to the 'git apply' program (see linkgit:git-apply[1]) that applies the patch. + Implies --am. + See also INCOMPATIBLE OPTIONS below.
@@ -559,6 +567,7 @@ INCOMPATIBLE OPTIONS The following options: + * --am * --committer-date-is-author-date * --ignore-date * --ignore-whitespace
builtin/rebase.c +17 -1
@@ -1335,6 +1335,18 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,return res &&is_linear_history (onto, head); } +static int parse_opt_am (const struct option *opt,const char *arg,int unset) +{ +struct rebase_options *opts = opt->value; + +BUG_ON_OPT_NEG (unset); +BUG_ON_OPT_ARG (arg); + + opts->type =REBASE_AM ; + +return 0 ; +} +/* -i followed by -m is still -i */ static int parse_opt_merge (const struct option *opt,const char *arg,int unset) {
@@ -1519,6 +1531,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)OPT_CMDMODE (0 ,"show-current-patch" , &action,N_ ("show the patch file being applied or merged" ),ACTION_SHOW_CURRENT_PATCH ), + { OPTION_CALLBACK, 0, "am", &options, NULL, +N_ ("use apply-mail strategies to rebase" ), +PARSE_OPT_NOARG |PARSE_OPT_NONEG , + parse_opt_am }, { OPTION_CALLBACK, 'm', "merge", &options, NULL,N_ ("use merging strategies to rebase" ), PARSE_OPT_NOARG | PARSE_OPT_NONEG,
@@ -1878,7 +1894,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)if (isatty (2 ) && options.flags &REBASE_NO_QUIET )strbuf_addstr (&options.git_format_patch_opt," --progress" ); -if (options.git_am_opts.argc) { +if (options.git_am_opts.argc || options.type ==REBASE_AM ) {/* all am options except -q are compatible only with --am */ for (i = options.git_am_opts.argc -1 ; i >=0 ; i--)if (strcmp (options.git_am_opts.argv[i],"-q" ))
Signature · 1 line
gitgitgadget
PATCH v4 14/19 git-prompt: change the prompt for interactive-based rebases
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
In the past, we had different prompts for different types of rebases:
REBASE: for am-based rebases
REBASE-m: for merge-based rebases
REBASE-i: for interactive-based rebases
It's not clear why this distinction was necessary or helpful; when the prompt was added in commit e75201963f67 ("Improve bash prompt to detect various states like an unfinished merge", 2007-09-30), it simply added these three different types. Perhaps there was a useful purpose back then, but there have been some changes:
* The merge backend was deleted after being implemented on top of the interactive backend, causing the prompt for merge-based rebases to change from REBASE-m to REBASE-i.
* The interactive backend is used for multiple different types of non-interactive rebases, so the "-i" part of the prompt doesn't really mean what it used to.
* Rebase backends have gained more abilities and have a great deal of overlap, sometimes making it hard to distinguish them.
* Behavioral differences between the backends have also been ironed out.
* We want to change the default backend from am to interactive, which means people would get "REBASE-i" by default if we didn't change the prompt, and only if they specified --am or --whitespace or -C would they get the "REBASE" prompt.
* In the future, we plan to have "--whitespace", "-C", and even "--am" run the interactive backend once it can handle everything the am-backend can.
For all these reasons, make the prompt for any type of rebase just be "REBASE".
--- contrib/completion/git-prompt.sh | 6 +----- t/t9903-bash-prompt.sh | 8 ++++---- 2 files changed, 5 insertions(+), 9 deletions(-)
contrib/completion/git-prompt.sh +1 -5
@@ -429,11 +429,7 @@ __git_ps1 () __git_eread "$g/rebase-merge/head-name" b __git_eread "$g/rebase-merge/msgnum" step __git_eread "$g/rebase-merge/end" total - if [ -f "$g/rebase-merge/interactive" ]; then - r="|REBASE-i" - else - r="|REBASE-m" - fi + r="|REBASE" else if [ -d "$g/rebase-apply" ]; then __git_eread "$g/rebase-apply/next" step
t/t9903-bash-prompt.sh +4 -4
@@ -163,7 +163,7 @@ test_expect_success 'prompt - inside bare repository' ' ' test_expect_success 'prompt - interactive rebase' ' - printf " (b1|REBASE-i 2/3)" >expected && + printf " (b1|REBASE 2/3)" >expected && write_script fake_editor.sh <<-\EOF && echo "exec echo" >"$1" echo "edit $(git log -1 --format="%h")" >>"$1"
@@ -180,7 +180,7 @@ test_expect_success 'prompt - interactive rebase' ' ' test_expect_success 'prompt - rebase merge' ' - printf " (b2|REBASE-i 1/3)" >expected && + printf " (b2|REBASE 1/3)" >expected && git checkout b2 && test_when_finished "git checkout master" && test_must_fail git rebase --merge b1 b2 &&
@@ -189,11 +189,11 @@ test_expect_success 'prompt - rebase merge' ' test_cmp expected "$actual" ' -test_expect_success 'prompt - rebase' ' +test_expect_success 'prompt - rebase am' ' printf " (b2|REBASE 1/3)" >expected && git checkout b2 && test_when_finished "git checkout master" && - test_must_fail git rebase b1 b2 && + test_must_fail git rebase --am b1 b2 && test_when_finished "git rebase --abort" && __git_ps1 >"$actual" && test_cmp expected "$actual"
Signature · 1 line
gitgitgadget
PATCH v4 15/19 rebase: drop '-i' from the reflog for interactive-based rebases
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
A large variety of rebase types are supported by the interactive machinery, not just the explicitly interactive ones. These all share the same code and write the same reflog messages, but the "-i" moniker in those messages doesn't really have much meaning. It also becomes somewhat distracting once we switch the default from the am-backend to the interactive one. Just remove the "-i" from these messages.
--- builtin/rebase.c | 2 +- sequencer.c | 12 ++++++------ t/t3404-rebase-interactive.sh | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-)
builtin/rebase.c +1 -1
@@ -1441,7 +1441,7 @@ static void set_reflog_action(struct rebase_options *options)if (env &&strcmp ("rebase" , env))return ;/* only override it if it is "rebase" */ -strbuf_addf (&buf,"rebase -i (%s)" , options->action); +strbuf_addf (&buf,"rebase (%s)" , options->action);setenv (GIT_REFLOG_ACTION_ENVIRONMENT , buf.buf,1 );strbuf_release (&buf); }
sequencer.c +6 -6
@@ -290,7 +290,7 @@ int sequencer_remove_state(struct replay_opts *opts)char *eol =strchr (p,'\n' );if (eol) *eol ='\0' ; -if (delete_ref ("(rebase -i) cleanup" , p,NULL ,0 ) <0 ) { +if (delete_ref ("(rebase) cleanup" , p,NULL ,0 ) <0 ) {warning (_ ("could not delete '%s'" ), p); ret =-1 ; }
@@ -324,7 +324,7 @@ static const char *action_name(const struct replay_opts *opts)case REPLAY_PICK :return N_ ("cherry-pick" );case REPLAY_INTERACTIVE_REBASE : -return N_ ("rebase -i" ); +return N_ ("rebase" ); }die (_ ("unknown action: %d" ), opts->action); }
@@ -628,7 +628,7 @@ static int do_recursive_merge(struct repository *r, COMMIT_LOCK | SKIP_IF_UNCHANGED))/* * TRANSLATORS: %s will be "revert", "cherry-pick" or -* "rebase -i". +* "rebase". */ return error (_ ("%s: Unable to write new index file" ),_ (action_name (opts)));
@@ -2885,8 +2885,8 @@ static int save_todo(struct todo_list *todo_list, struct replay_opts *opts)int next = todo_list->current, offset, fd;/* -* rebase -i writes "git-rebase-todo" without the currently executing -* command, appending it to "done" instead. +* interactive backend writes "git-rebase-todo" without the currently +* executing command, appending it to "done" instead. */ if (is_rebase_i (opts)) next++;
@@ -3197,7 +3197,7 @@ static int do_label(struct repository *r, const char *name, int len)return error (_ ("illegal label name: '%.*s'" ), len, name);strbuf_addf (&ref_name,"refs/rewritten/%.*s" , len, name); -strbuf_addf (&msg,"rebase -i (label) '%.*s'" , len, name); +strbuf_addf (&msg,"rebase (label) '%.*s'" , len, name); transaction =ref_store_transaction_begin (refs, &err);if (!transaction) {
t/t3404-rebase-interactive.sh +5 -5
@@ -223,7 +223,7 @@ test_expect_success 'reflog for the branch shows state before rebase' ' ' test_expect_success 'reflog for the branch shows correct finish message' ' - printf "rebase -i (finish): refs/heads/branch1 onto %s\n" \ + printf "rebase (finish): refs/heads/branch1 onto %s\n" \ "$(git rev-parse branch2)" >expected && git log -g --pretty=%gs -1 refs/heads/branch1 >actual && test_cmp expected actual
@@ -1162,10 +1162,10 @@ test_expect_success 'rebase -i produces readable reflog' ' git branch -f branch-reflog-test H && git rebase -i --onto I F branch-reflog-test && cat >expect <<-\EOF && - rebase -i (finish): returning to refs/heads/branch-reflog-test - rebase -i (pick): H - rebase -i (pick): G - rebase -i (start): checkout I + rebase (finish): returning to refs/heads/branch-reflog-test + rebase (pick): H + rebase (pick): G + rebase (start): checkout I EOF git reflog -n4 HEAD | sed "s/[^:]*: //" >actual &&
Signature · 1 line
gitgitgadget
PATCH v4 16/19 rebase tests: mark tests specific to the am-backend with --am
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
We have many rebase tests in the testsuite, and often the same test is repeated multiple times just testing different backends. For those tests that were specifically trying to test the am backend, add the --am flag.
--- t/t3400-rebase.sh | 10 +++++----- t/t3401-rebase-and-am-rename.sh | 4 ++-- t/t3404-rebase-interactive.sh | 2 +- t/t3406-rebase-message.sh | 12 ++++++------ t/t3407-rebase-abort.sh | 6 +++--- t/t3420-rebase-autostash.sh | 2 +- t/t3425-rebase-topology-merges.sh | 8 ++++---- t/t3432-rebase-fast-forward.sh | 4 ++-- t/t5407-post-rewrite-hook.sh | 12 ++++++------ t/t7512-status-help.sh | 12 ++++++------ 10 files changed, 36 insertions(+), 36 deletions(-)
t/t3400-rebase.sh +5 -5
@@ -183,19 +183,19 @@ test_expect_success 'default to common base in @{upstream}s reflog if no upstrea test_cmp expect actual ' -test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' ' +test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--am)' ' git checkout -B default-base master && git checkout -B default topic && git config branch.default.remote . && git config branch.default.merge refs/heads/default-base && - git rebase && + git rebase --am && git rev-parse --verify default-base >expect && git rev-parse default~1 >actual && test_cmp expect actual && git checkout default-base && git reset --hard HEAD^ && git checkout default && - git rebase && + git rebase --am && git rev-parse --verify default-base >expect && git rev-parse default~1 >actual && test_cmp expect actual
@@ -226,7 +226,7 @@ test_expect_success 'cherry-picked commits and fork-point work together' ' test_expect_success 'rebase --am -q is quiet' ' git checkout -b quiet topic && - git rebase -q master >output.out 2>&1 && + git rebase --am -q master >output.out 2>&1 && test_must_be_empty output.out '
@@ -325,7 +325,7 @@ test_expect_success 'rebase --am and --show-current-patch' ' echo two >>init.t && git commit -a -m two && git tag two && - test_must_fail git rebase -f --onto init HEAD^ && + test_must_fail git rebase --am -f --onto init HEAD^ && GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && grep "show.*$(git rev-parse two)" stderr )
t/t3401-rebase-and-am-rename.sh +2 -2
@@ -52,13 +52,13 @@ test_expect_success 'rebase --interactive: directory rename detected' ' ) ' -test_expect_failure 'rebase (am): directory rename detected' ' +test_expect_failure 'rebase --am: directory rename detected' ' ( cd dir-rename && git checkout B^0 && - git -c merge.directoryRenames=true rebase A && + git -c merge.directoryRenames=true rebase --am A && git ls-files -s >out && test_line_count = 5 out &&
t/t3404-rebase-interactive.sh +1 -1
@@ -1138,7 +1138,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int git checkout conflict-branch && ( set_fake_editor && - test_must_fail git rebase -f --onto HEAD~2 HEAD~ && + test_must_fail git rebase -f --am --onto HEAD~2 HEAD~ && test_must_fail git rebase --edit-todo ) && git rebase --abort
t/t3406-rebase-message.sh +6 -6
@@ -23,24 +23,24 @@ test_expect_success 'rebase -m' ' ' test_expect_success 'rebase against master twice' ' - git rebase master >out && + git rebase --am master >out && test_i18ngrep "Current branch topic is up to date" out ' test_expect_success 'rebase against master twice with --force' ' - git rebase --force-rebase master >out && + git rebase --force-rebase --am master >out && test_i18ngrep "Current branch topic is up to date, rebase forced" out ' test_expect_success 'rebase against master twice from another branch' ' git checkout topic^ && - git rebase master topic >out && + git rebase --am master topic >out && test_i18ngrep "Current branch topic is up to date" out ' test_expect_success 'rebase fast-forward to master' ' git checkout topic^ && - git rebase topic >out && + git rebase --am topic >out && test_i18ngrep "Fast-forwarded HEAD to topic" out '
@@ -89,7 +89,7 @@ test_expect_success 'GIT_REFLOG_ACTION' ' git checkout -b reflog-topic start && test_commit reflog-to-rebase && - git rebase reflog-onto && + git rebase --am reflog-onto && git log -g --format=%gs -3 >actual && cat >expect <<-\EOF && rebase finished: returning to refs/heads/reflog-topic
@@ -99,7 +99,7 @@ test_expect_success 'GIT_REFLOG_ACTION' ' test_cmp expect actual && git checkout -b reflog-prefix reflog-to-rebase && - GIT_REFLOG_ACTION=change-the-reflog git rebase reflog-onto && + GIT_REFLOG_ACTION=change-the-reflog git rebase --am reflog-onto && git log -g --format=%gs -3 >actual && cat >expect <<-\EOF && rebase finished: returning to refs/heads/reflog-prefix
t/t3407-rebase-abort.sh +3 -3
@@ -96,14 +96,14 @@ testrebase() { ' } -testrebase "" .git/rebase-apply +testrebase " --am" .git/rebase-apply testrebase " --merge" .git/rebase-merge -test_expect_success 'rebase --quit' ' +test_expect_success 'rebase --am --quit' ' cd "$work_dir" && # Clean up the state from the previous one git reset --hard pre-rebase && - test_must_fail git rebase master && + test_must_fail git rebase --am master && test_path_is_dir .git/rebase-apply && head_before=$(git rev-parse HEAD) && git rebase --quit &&
t/t3420-rebase-autostash.sh +1 -1
@@ -234,7 +234,7 @@ test_expect_success "rebase: noop rebase" ' git checkout feature-branch ' -testrebase "" .git/rebase-apply +testrebase " --am" .git/rebase-apply testrebase " --merge" .git/rebase-merge testrebase " --interactive" .git/rebase-merge
t/t3425-rebase-topology-merges.sh +4 -4
@@ -54,7 +54,7 @@ test_run_rebase () { test_linear_range 'n o' e.. " } -test_run_rebase success '' +test_run_rebase success --am test_run_rebase success -m test_run_rebase success -i
@@ -70,7 +70,7 @@ test_run_rebase () { test_linear_range "\'"$expected"\'" d.. " } -test_run_rebase success 'n o e' '' +test_run_rebase success 'n o e' --am test_run_rebase success 'n o e' -m test_run_rebase success 'n o e' -i
@@ -86,7 +86,7 @@ test_run_rebase () { test_linear_range "\'"$expected"\'" c.. " } -test_run_rebase success 'd n o e' '' +test_run_rebase success 'd n o e' --am test_run_rebase success 'd n o e' -m test_run_rebase success 'd n o e' -i
@@ -102,7 +102,7 @@ test_run_rebase () { test_linear_range "\'"$expected"\'" c.. " } -test_run_rebase success 'd n o e' '' +test_run_rebase success 'd n o e' --am test_run_rebase success 'd n o e' -m test_run_rebase success 'd n o e' -i
t/t3432-rebase-fast-forward.sh +2 -2
@@ -28,8 +28,8 @@ test_rebase_same_head () { shift && cmp_f="$1" && shift && - test_rebase_same_head_ $status_n $what_n $cmp_n "" "$*" && - test_rebase_same_head_ $status_f $what_f $cmp_f " --no-ff" "$*" + test_rebase_same_head_ $status_n $what_n $cmp_n " --am" "$*" && + test_rebase_same_head_ $status_f $what_f $cmp_f " --am --no-ff" "$*" test_rebase_same_head_ $status_n $what_n $cmp_n " --merge" "$*" && test_rebase_same_head_ $status_f $what_f $cmp_f " --merge --no-ff" "$*" }
t/t5407-post-rewrite-hook.sh +6 -6
@@ -53,10 +53,10 @@ test_expect_success 'git commit --amend --no-post-rewrite' ' test ! -f post-rewrite.data ' -test_expect_success 'git rebase' ' +test_expect_success 'git rebase --am' ' git reset --hard D && clear_hook_input && - test_must_fail git rebase --onto A B && + test_must_fail git rebase --am --onto A B && echo C > foo && git add foo && git rebase --continue &&
@@ -68,10 +68,10 @@ test_expect_success 'git rebase' ' verify_hook_input ' -test_expect_success 'git rebase --skip' ' +test_expect_success 'git rebase --am --skip' ' git reset --hard D && clear_hook_input && - test_must_fail git rebase --onto A B && + test_must_fail git rebase --am --onto A B && test_must_fail git rebase --skip && echo D > foo && git add foo &&
@@ -84,10 +84,10 @@ test_expect_success 'git rebase --skip' ' verify_hook_input ' -test_expect_success 'git rebase --skip the last one' ' +test_expect_success 'git rebase --am --skip the last one' ' git reset --hard F && clear_hook_input && - test_must_fail git rebase --onto D A && + test_must_fail git rebase --am --onto D A && git rebase --skip && echo rebase >expected.args && cat >expected.data <<-EOF &&
t/t7512-status-help.sh +6 -6
@@ -71,10 +71,10 @@ test_expect_success 'prepare for rebase conflicts' ' ' -test_expect_success 'status when rebase in progress before resolving conflicts' ' +test_expect_success 'status when rebase --am in progress before resolving conflicts' ' test_when_finished "git rebase --abort" && ONTO=$(git rev-parse --short HEAD^^) && - test_must_fail git rebase HEAD^ --onto HEAD^^ && + test_must_fail git rebase --am HEAD^ --onto HEAD^^ && cat >expected <<EOF && rebase in progress; onto $ONTO You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
@@ -94,11 +94,11 @@ EOF ' -test_expect_success 'status when rebase in progress before rebase --continue' ' +test_expect_success 'status when rebase --am in progress before rebase --continue' ' git reset --hard rebase_conflicts && test_when_finished "git rebase --abort" && ONTO=$(git rev-parse --short HEAD^^) && - test_must_fail git rebase HEAD^ --onto HEAD^^ && + test_must_fail git rebase --am HEAD^ --onto HEAD^^ && echo three >main.txt && git add main.txt && cat >expected <<EOF &&
@@ -688,7 +688,7 @@ EOF ' -test_expect_success 'status when rebase conflicts with statushints disabled' ' +test_expect_success 'status when rebase --am conflicts with statushints disabled' ' git reset --hard master && git checkout -b statushints_disabled && test_when_finished "git config --local advice.statushints true" &&
@@ -698,7 +698,7 @@ test_expect_success 'status when rebase conflicts with statushints disabled' ' test_commit three_statushints main.txt three && test_when_finished "git rebase --abort" && ONTO=$(git rev-parse --short HEAD^^) && - test_must_fail git rebase HEAD^ --onto HEAD^^ && + test_must_fail git rebase --am HEAD^ --onto HEAD^^ && cat >expected <<EOF && rebase in progress; onto $ONTO You are currently rebasing branch '\''statushints_disabled'\'' on '\''$ONTO'\''.
Signature · 1 line
gitgitgadget
PATCH v4 17/19 rebase tests: repeat some tests using the merge backend instead of am
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
In order to ensure the merge/interactive backend gets similar coverage to the am one, add some tests for cases where previously only the am backend was tested.
--- t/t5520-pull.sh | 17 +++++++++++++++-- t/t6047-diff3-conflict-markers.sh | 13 +++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-)
t/t5520-pull.sh +15 -2
@@ -277,14 +277,27 @@ test_expect_success '--rebase' ' test_cmp expect actual ' -test_expect_success '--rebase fast forward' ' +test_expect_success '--rebase (merge) fast forward' ' git reset --hard before-rebase && git checkout -b ff && echo another modification >file && git commit -m third file && git checkout to-rebase && - git pull --rebase . ff && + git -c rebase.backend=merge pull --rebase . ff && + test_cmp_rev HEAD ff && + + # The above only validates the result. Did we actually bypass rebase? + git reflog -1 >reflog.actual && + sed "s/^[0-9a-f][0-9a-f]*/OBJID/" reflog.actual >reflog.fuzzy && + echo "OBJID HEAD@{0}: pull --rebase . ff: Fast-forward" >reflog.expected && + test_cmp reflog.expected reflog.fuzzy +' + +test_expect_success '--rebase (am) fast forward' ' + git reset --hard before-rebase && + + git -c rebase.backend=am pull --rebase . ff && test_cmp_rev HEAD ff && # The above only validates the result. Did we actually bypass rebase?
t/t6047-diff3-conflict-markers.sh +11 -2
@@ -186,7 +186,7 @@ test_expect_success 'check multiple merge bases' ' ) ' -test_expect_success 'rebase describes fake ancestor base' ' +test_expect_success 'rebase --merge describes parent of commit being picked' ' test_create_repo rebase && ( cd rebase &&
@@ -194,7 +194,16 @@ test_expect_success 'rebase describes fake ancestor base' ' test_commit master file && git checkout -b side HEAD^ && test_commit side file && - test_must_fail git -c merge.conflictstyle=diff3 rebase master && + test_must_fail git -c merge.conflictstyle=diff3 rebase --merge master && + grep "||||||| parent of" file + ) +' + +test_expect_success 'rebase --am describes fake ancestor base' ' + ( + cd rebase && + git rebase --abort && + test_must_fail git -c merge.conflictstyle=diff3 rebase --am master && grep "||||||| constructed merge base" file ) '
Signature · 1 line
gitgitgadget
PATCH v4 18/19 rebase: make the backend configurable via config setting
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
--- Documentation/config/rebase.txt | 8 ++++++++ builtin/rebase.c | 31 ++++++++++++++++++++++++------- 2 files changed, 32 insertions(+), 7 deletions(-)
Documentation/config/rebase.txt +8 -0
@@ -5,6 +5,14 @@ rebase.useBuiltin:: is always used. Setting this will emit a warning, to alert any remaining users that setting this now does nothing. +rebase.backend:: + Default backend to use for rebasing. Possible choices are + 'am' or 'merge' (note that the merge backend is sometimes also + refered to as the interactive backend or the interactive + machinery elsewhere in the docs). Also, in the future, if the + merge backend gains all remaining capabilities of the am + backend, this setting may become unused. + rebase.stat:: Whether to show a diffstat of what changed upstream since the last rebase. False by default.
builtin/rebase.c +24 -7
@@ -60,6 +60,7 @@ enum empty_type {struct rebase_options {enum rebase_type type;enum empty_type empty; +const char *default_backend;const char *state_dir;struct commit *upstream;const char *upstream_name;
@@ -100,6 +101,7 @@ struct rebase_options {#define REBASE_OPTIONS_INIT { \ .type = REBASE_UNSPECIFIED, \ .empty = EMPTY_UNSPECIFIED, \ + .default_backend = "am", \ .flags = REBASE_NO_QUIET, \ .git_am_opts = ARGV_ARRAY_INIT, \ .git_format_patch_opt = STRBUF_INIT \
@@ -1272,6 +1274,10 @@ static int rebase_config(const char *var, const char *value, void *data)return 0 ; } +if (!strcmp (var,"rebase.backend" )) { +return git_config_string (&opts->default_backend, var, value); + } +return git_default_config (var, value, data); }
@@ -1900,9 +1906,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)if (strcmp (options.git_am_opts.argv[i],"-q" ))break ; -if (is_interactive (&options) && i >=0 ) -die (_ ("cannot combine am options with either " -"interactive or merge options" )); +if (i >=0 ) { +if (is_interactive (&options)) +die (_ ("cannot combine am options with either " +"interactive or merge options" )); +else + options.type =REBASE_AM ; + } + } + +if (options.type ==REBASE_UNSPECIFIED ) { +if (!strcmp (options.default_backend,"merge" )) + options.type =REBASE_MERGE ; +else if (!strcmp (options.default_backend,"am" )) + options.type =REBASE_AM ; +else +die (_ ("Unknown rebase backend: %s" ), + options.default_backend); }switch (options.type) {
@@ -1915,10 +1935,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.state_dir =apply_dir ();break ;default : -/* the default rebase backend is `--am` */ - options.type =REBASE_AM ; - options.state_dir =apply_dir (); -break ; +BUG ("options.type was just set above; should be unreachable." ); }if (options.empty ==EMPTY_UNSPECIFIED ) {
Signature · 1 line
gitgitgadget
PATCH v4 19/19 rebase: change the default backend from "am" to "merge"
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
The am-backend drops information and thus limits what we can do:
* lack of full tree information from the original commits means we cannot do directory rename detection and warn users that they might want to move some of their new files that they placed in old directories to prevent their becoming orphaned.[1]
* reduction in context from only having a few lines beyond those changed means that when context lines are non-unique we can apply patches incorrectly.[2]
* lack of access to original commits means that conflict marker annotation has less information available.
Also, the merge/interactive backend have far more abilities, appear to currently have a slight performance advantage[3] and have room for more optimizations than the am backend[4] (and work is underway to take advantage of some of those possibilities).
[1] https://lore.kernel.org/git/[email protected]/
[2] https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/
[3] https://public-inbox.org/git/CABPp-BF=ev03WgODk6TMQmuNoatg2kiEe5DR__gJ0OTVqHSnfQ@mail.gmail.com/
[4] https://lore.kernel.org/git/CABPp-BGh7yW69QwxQb13K0HM38NKmQif3A6C6UULEKYnkEJ5vA@mail.gmail.com/
--- Documentation/git-rebase.txt | 2 +- builtin/rebase.c | 4 ++-- t/t5520-pull.sh | 10 ++++++---- t/t9106-git-svn-commit-diff-clobber.sh | 3 ++- 4 files changed, 11 insertions(+), 8 deletions(-)
Documentation/git-rebase.txt +1 -1
@@ -313,7 +313,7 @@ See also INCOMPATIBLE OPTIONS below. --merge:: Use merging strategies to rebase. When the recursive (default) merge strategy is used, this allows rebase to be aware of renames on the - upstream side. + upstream side. This is the default. + Note that a rebase merge works by replaying each commit from the working branch on top of the <upstream> branch. Because of this, when a merge
builtin/rebase.c +2 -2
@@ -101,7 +101,7 @@ struct rebase_options {#define REBASE_OPTIONS_INIT { \ .type = REBASE_UNSPECIFIED, \ .empty = EMPTY_UNSPECIFIED, \ - .default_backend = "am", \ + .default_backend = "merge", \ .flags = REBASE_NO_QUIET, \ .git_am_opts = ARGV_ARRAY_INIT, \ .git_format_patch_opt = STRBUF_INIT \
@@ -1917,7 +1917,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)if (options.type ==REBASE_UNSPECIFIED ) {if (!strcmp (options.default_backend,"merge" )) - options.type =REBASE_MERGE ; +imply_interactive (&options,"--merge" );else if (!strcmp (options.default_backend,"am" )) options.type =REBASE_AM ;else
t/t5520-pull.sh +6 -4
@@ -340,7 +340,7 @@ test_expect_success '--rebase with conflicts shows advice' ' test_tick && git commit -m "Create conflict" seq.txt && test_must_fail git pull --rebase . seq 2>err >out && - test_i18ngrep "Resolve all conflicts manually" out + test_i18ngrep "Resolve all conflicts manually" err ' test_expect_success 'failed --rebase shows advice' '
@@ -354,7 +354,7 @@ test_expect_success 'failed --rebase shows advice' ' git checkout -f -b fails-to-rebase HEAD^ && test_commit v2-without-cr file "2" file2-lf && test_must_fail git pull --rebase . diverging 2>err >out && - test_i18ngrep "Resolve all conflicts manually" out + test_i18ngrep "Resolve all conflicts manually" err ' test_expect_success '--rebase fails with multiple branches' '
@@ -774,8 +774,10 @@ test_expect_success 'git pull --rebase does not reapply old patches' ' ( cd dst && test_must_fail git pull --rebase && - find .git/rebase-apply -name "000*" >patches && - test_line_count = 1 patches + cat .git/rebase-merge/done .git/rebase-merge/git-rebase-todo >work && + grep -v -e \# -e ^$ work >patches && + test_line_count = 1 patches && + rm -f work ) '
t/t9106-git-svn-commit-diff-clobber.sh +2 -1
@@ -92,7 +92,8 @@ test_expect_success 'multiple dcommit from git svn will not clobber svn' " test_expect_success 'check that rebase really failed' ' - test -d .git/rebase-apply + git status >output && + grep currently.rebasing output ' test_expect_success 'resolve, continue the rebase and dcommit' "
Signature · 1 line
gitgitgadget
Hi Elijah
Quoted 20 lines · Elijah Newren via GitGitGadget
This is a re-roll of en/rebase-backend which has been rebased on v2.25.0 and updated to remove the dependence on ra/rebase-i-more-options, and also tries to address feedback from Phillip, Junio, and Jonathan. This series does a lot of work around making the default rebase backend configurable, and switches the default from the am backend to the merge/interactive one.
Changes since v3:
* Rebased on origin/master and updated to remove the dependence on ra/rebase-i-more-options.
* Added two new patches at the start of the series.
* Split the old first patch into two, while modifying them based on Phillip's feedback (though slightly differently than discussed on the list; instead of making --keep-empty a synonym for --empty=keep, I instead kept backward compatibility by making it a no-op).
* I noted the post-commit hook in the differences between backends. Emily is investigating what changes need to happen there, so I merely documented the existing differences.
* dropped '-i' from the reflog messages; now they just refer to 'rebase'
Quoted 4 lines
Changes possibly missing from this version, for discussion:
* I did not remove the --am option as suggested by Phillip, since Junio and
Phillip were still discussing whether it is wanted/needed. > * I did not address the last two items Jonathan brought up as I
couldn't
Quoted 1 line
find enough information to reproduce or understand the problems.
I think I've got a fix for the failure with --committer-date-is-author-date that Jonathan reported which I'll post next week - the bug was not in this series, it was just exposed by it. I'll try and read through this series next week as well.
Best Wishes
Phillip
Quoted 1,038 lines
Elijah Newren (19):
git-rebase.txt: update description of --allow-empty-message
t3404: directly test the behavior of interest
rebase (interactive-backend): make --keep-empty the default rebase (interactive-backend): fix handling of commits that become
empty
t3406: simplify an already simple test
rebase, sequencer: remove the brokenGIT_QUIEThandling
rebase: make sure to pass along the quiet flag to the sequencer
rebase: fix handling ofrestrict_revision
t3432: make these tests work with either am or merge backends
rebase: allow more types of rebases to fast-forward git-rebase.txt: add more details about behavioral differences of
backends
rebase: move incompatibility checks between backend options a bit
earlier
rebase: add an --am option
git-prompt: change the prompt for interactive-based rebases
rebase: drop '-i' from the reflog for interactive-based rebases rebase tests: mark tests specific to the am-backend with --am rebase tests: repeat some tests using the merge backend instead of am
rebase: make the backend configurable via config setting
rebase: change the default backend from "am" to "merge"Documentation/config/rebase.txt | 8 ++ Documentation/git-rebase.txt | 150 +++++++++++++++++--- builtin/rebase.c | 186 +++++++++++++++++++------ contrib/completion/git-prompt.sh | 6 +- rebase-interactive.c | 7 +- rebase-interactive.h | 2 +- sequencer.c | 84 ++++++----- sequencer.h | 3 +- t/t3400-rebase.sh | 36 ++++- t/t3401-rebase-and-am-rename.sh | 4 +- t/t3404-rebase-interactive.sh | 19 +-- t/t3406-rebase-message.sh | 19 ++- t/t3407-rebase-abort.sh | 6 +- t/t3420-rebase-autostash.sh | 2 +- t/t3421-rebase-topology-linear.sh | 16 +-- t/t3424-rebase-empty.sh | 108 ++++++++++++++ t/t3425-rebase-topology-merges.sh | 8 +- t/t3427-rebase-subtree.sh | 12 +- t/t3432-rebase-fast-forward.sh | 54 ++++--- t/t5407-post-rewrite-hook.sh | 12 +- t/t5520-pull.sh | 27 +++- t/t6047-diff3-conflict-markers.sh | 13 +- t/t7512-status-help.sh | 12 +- t/t9106-git-svn-commit-diff-clobber.sh | 3 +- t/t9903-bash-prompt.sh | 8 +- 25 files changed, 595 insertions(+), 210 deletions(-)create mode 100755
t/t3424-rebase-empty.shbase-commit:
d0654dc308b0ba76dd8ed7bbb33c8d8f7aacd783
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v4
Pull-Request: https://github.com/git/git/pull/679Range-diff vs v3:
-: ---------- > 1: 3ea48d5394 git-rebase.txt: update description of --allow-empty-message -: ---------- > 2: 10fdd162a0 t3404: directly test the behavior of interest 1: 1c2b77e94d ! 3: 179f82ab83 rebase: extend the options for handling of empty commits @@ -1,49 +1,46 @@ Author: Elijah Newren <[email protected]> - rebase: extend the options for handling of empty commits + rebase (interactive-backend): make --keep-empty the default - Extend the interactive machinery with the ability to handle the full - spread of options for how to handle commits that either start or become - empty (by "become empty" I mean the changes in a commit are a subset of - changes that exist upstream, so the net effect of applying the commit is - no changes). Introduce a new command line flag for selecting the - desired behavior: - --empty={drop,keep,ask} - with the definitions: - drop: drop empty commits - keep: keep empty commits - ask: provide the user a chance to interact and pick what to do with - empty commits on a case-by-case basis + Different rebase backends have different treatment for commits which + start empty (i.e. have no changes relative to their parent), and the + --keep-empty option was added at some point to allow adjusting behavior + for the interactive backend. The handling of commits which start empty + is actually quite similar to commit b00bf1c9a8dd (git-rebase: make + --allow-empty-message the default, 2018-06-27), which pointed out that + the behavior for various backends is often more happenstance than + design. The specific change made in that commit is actually quite + relevant as well and much of the logic there directly applies here. - Note that traditionally, am-based rebases have always dropped commits - that either started or became empty, while interactive-based rebases - have defaulted to ask (and provided an option to keep commits that - started empty). This difference made sense since users of an am-based - rebase just wanted to quickly batch apply a sequence of commits, while - users editing a todo list will likely want the chance to interact and - handle unusual cases on a case-by-case basis. However, not all rebases - using the interactive machinery are explicitly interactive anymore. In - particular --merge was always meant to behave more like --am: just - rebase a batch of commits without popping up a todo list. + It makes a lot of sense in 'git commit' to error out on the creation of + empty commits, unless an override flag is provided. However, once + someone determines that there is a rare case that merits using the + manual override to create such a commit, it is somewhere between + annoying and harmful to have to take extra steps to keep such + intentional commits around. Granted, empty commits are quite rare, + which is why handling of them doesn't get considered much and folks tend + to defer to existing (accidental) behavior and assume there was a reason + for it, leading them to just add flags (--keep-empty in this case) that + allow them to override the bad defaults. Fix the interactive backend so + that --keep-empty is the default, much like we did with + --allow-empty-message. The am backend should also be fixed to have + --keep-empty semantics for commits that start empty, but that is not + included in this patch other than a testcase documenting the failure. - If the --empty flag is not specified, pick defaults as follows: - explicitly interactive: ask - --exec: keep (exec is about checking existing commits, and often - used without actually changing the base. Thus the - expectation is that the user doesn't necessarily want - anything to change; they just want to test). - otherwise: drop - - Also, this commit makes --keep-empty just imply --empty=keep, and hides - it from help so that we aren't confusing users with different ways to do - the same thing. (I could have added a --drop-empty flag, but then that - invites users to specify both --keep-empty and --drop-empty and we have - to add sanity checking around that; it seems cleaner to have a single - multi-valued option.) This actually fixes --keep-empty too; previously, - it only meant to sometimes keep empty commits, in particular commits - which started empty would be kept. But it would still error out and ask - the user what to do with commits that became empty. Now it keeps empty - commits, as instructed. + Note that there was one test in t3421 which appears to have been written + expecting --keep-empty to not be the default as correct behavior. This + test was introduced in commit 00b8be5a4d38 ("add tests for rebasing of + empty commits", 2013-06-06), which was part of a series focusing on + rebase topology and which had an interesting original cover letter at + https://lore.kernel.org/git/[email protected]/ + which noted + Your input especially appreciated on whether you agree with the + intent of the test cases. + and then went into a long example about how one of the many tests added + had several questions about whether it was correct. As such, I believe + most the tests in that series were about testing rebase topology with as + many different flags as possible and were not trying to state in general + how those flags should behave otherwise. Signed-off-by: Elijah Newren <[email protected]> @@ -51,126 +48,62 @@ --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ - original branch. The index and working tree are also left unchanged as a result. -+--empty={drop,keep,ask}:: -+ How to handle commits that become empty (because they contain a -+ subset of already upstream changes) or start empty. With drop -+ (the default), commits that start or become empty are dropped. -+ With keep (implied by --exec), such commits are kept. With ask -+ (implied by --interactive), the rebase will halt when an empty -+ commit is applied allowing you to choose whether to drop it or -+ commit it. Also with ask, if the rebase is interactive then -+ commits which start empty will be commented out in the todo -+ action list (giving you a chance to uncomment). -++ -+Note that this has no effect on commits which are already upstream (as -+can be checked via `git log --cherry-mark ...`), which are always -+dropped by rebase. -++ -+See also INCOMPATIBLE OPTIONS below. -+ --keep-empty:: - Keep the commits that do not change anything from its - parents in the result. -+ Deprecated alias for what is now known as --empty=keep. ++ No-op. Rebasing commits that started empty (had no change ++ relative to their parent) used to fail and this option would ++ override that behavior, allowing commits with empty changes to ++ be rebased. Now commits with no changes do not cause rebasing ++ to halt. + - See also INCOMPATIBLE OPTIONS below. - -@@ - * --interactive - * --exec - * --keep-empty -+ * --empty= - * --edit-todo - * --root when used in combination with --onto - -@@ - * --preserve-merges and --ignore-whitespace - * --preserve-merges and --committer-date-is-author-date - * --preserve-merges and --ignore-date -+ * --preserve-merges and --empty= - * --keep-base and --onto - * --keep-base and --root +-See also INCOMPATIBLE OPTIONS below. ++See also BEHAVIORAL DIFFERENCES and INCOMPATIBLE OPTIONS below. + --allow-empty-message:: + No-op. Rebasing commits with an empty message used to fail @@ + Empty commits + ~~~~~~~~~~~~~ - There are some subtle differences how the backends behave. - --Empty commits --~~~~~~~~~~~~~ -- -The am backend drops any "empty" commits, regardless of whether the -commit started empty (had no changes relative to its parent to -start with) or ended empty (all changes were already applied -upstream in other commits). -- ++The am backend unfortunately drops intentionally empty commits, i.e. ++commits that started empty, though these are rare in practice. It ++also drops commits that become empty and has no option for controlling ++this behavior. + -The interactive backend drops commits by default that -started empty and halts if it hits a commit that ended up empty. -The `--keep-empty` option exists for the interactive backend to allow -it to keep commits that started empty. -- ++The interactive backend keeps intentionally empty commits. ++Unfortunately, it always halts whenever it runs across a commit that ++becomes empty, even when the rebase is not explicitly interactive. + Directory rename detection ~~~~~~~~~~~~~~~~~~~~~~~~~~ - diff --git a/builtin/rebase.c b/builtin/rebase.c --- a/builtin/rebase.c +++ b/builtin/rebase.c -@@ - REBASE_PRESERVE_MERGES - }; - -+enum empty_type { -+ EMPTY_UNSPECIFIED = -1, -+ EMPTY_DROP, -+ EMPTY_KEEP, -+ EMPTY_ASK -+}; -+ - struct rebase_options { - enum rebase_type type; -+ enum empty_type empty; - const char *state_dir; - struct commit *upstream; - const char *upstream_name; @@ const char *action; int signoff; int allow_rerere_autoupdate; - int keep_empty; int autosquash; - int ignore_whitespace; char *gpg_sign_opt; -@@ - - #define REBASE_OPTIONS_INIT { \ - .type = REBASE_UNSPECIFIED, \ -+ .empty = EMPTY_UNSPECIFIED, \ - .flags = REBASE_NO_QUIET, \ - .git_am_opts = ARGV_ARRAY_INIT, \ - .git_format_patch_opt = STRBUF_INIT \ -@@ - replay.allow_rerere_auto = opts->allow_rerere_autoupdate; - replay.allow_empty = 1; - replay.allow_empty_message = opts->allow_empty_message; -+ replay.drop_redundant_commits = (opts->empty == EMPTY_DROP); -+ replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP); -+ replay.ask_on_initially_empty = (opts->empty == EMPTY_ASK && -+ !(opts->flags & REBASE_INTERACTIVE_EXPLICIT)); - replay.verbose = opts->flags & REBASE_VERBOSE; - replay.reschedule_failed_exec = opts->reschedule_failed_exec; - replay.committer_date_is_author_date = + int autostash; @@ git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands); - flags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0; -+ flags |= (opts->empty == EMPTY_DROP) ? TODO_LIST_DROP_EMPTY : 0; -+ flags |= (opts->empty == EMPTY_ASK && -+ opts->flags & REBASE_INTERACTIVE_EXPLICIT) ? -+ TODO_LIST_ASK_EMPTY : 0; flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0; flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0; flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0; @@ -183,10 +116,8 @@ +{ + struct rebase_options *opts = opt->value; + -+ BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + -+ opts->empty = EMPTY_KEEP; + opts->type = REBASE_INTERACTIVE; + return 0; +} @@ -201,62 +132,28 @@ - OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")), + { OPTION_CALLBACK, 'k', "keep-empty", &options, NULL, + N_("(DEPRECATED) keep empty commits"), -+ PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN, ++ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, + parse_opt_keep_empty }, - OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message, - N_("allow commits with empty messages")), - OPT_BOOL(0, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")), + OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message, + N_("allow commits with empty messages"), + PARSE_OPT_HIDDEN), @@ opts->allow_rerere_autoupdate ? opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ? "--rerere-autoupdate" : "--no-rerere-autoupdate" : ""); - add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); -+ add_var(&script_snippet, "empty", opts->empty == EMPTY_KEEP ? "yes" : ""); add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : ""); add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt); add_var(&script_snippet, "cmd", opts->cmd); -@@ - return 0; - } - -+static enum empty_type parse_empty_value(const char *value) -+{ -+ if (!strcasecmp(value, "drop")) -+ return EMPTY_DROP; -+ else if (!strcasecmp(value, "keep")) -+ return EMPTY_KEEP; -+ else if (!strcasecmp(value, "ask")) -+ return EMPTY_ASK; -+ -+ die(_("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"."), value); -+} -+ -+static int parse_opt_empty(const struct option *opt, const char *arg, int unset) -+{ -+ struct rebase_options *options = opt->value; -+ enum empty_type value = parse_empty_value(arg); -+ -+ BUG_ON_OPT_NEG(unset); -+ -+ options->empty = value; -+ return 0; -+} -+ - static void NORETURN error_on_missing_default_upstream(void) - { - struct branch *current_branch = branch_get(NULL); @@ "ignoring them"), REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN), OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate), - OPT_BOOL('k', "keep-empty", &options.keep_empty, - N_("preserve empty commits during rebase")), -+ OPT_CALLBACK_F(0, "empty", &options, N_("{drop,keep,ask}"), -+ N_("how to handle empty commits"), -+ PARSE_OPT_NONEG, parse_opt_empty), + { OPTION_CALLBACK, 'k', "keep-empty", &options, NULL, + N_("(DEPRECATED) keep empty commits"), -+ PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN, ++ PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, + parse_opt_keep_empty }, OPT_BOOL(0, "autosquash", &options.autosquash, N_("move commits that begin with " @@ -267,26 +164,10 @@ - if (options.keep_empty) - imply_interactive(&options, "--keep-empty"); -+ if (options.empty != EMPTY_UNSPECIFIED) -+ imply_interactive(&options, "--empty"); - +- if (gpg_sign) { free(options.gpg_sign_opt); -@@ - break; - } - -+ if (options.empty == EMPTY_UNSPECIFIED) { -+ if (options.flags & REBASE_INTERACTIVE_EXPLICIT) -+ options.empty = EMPTY_ASK; -+ else if (exec.nr > 0) -+ options.empty = EMPTY_KEEP; -+ else -+ options.empty = EMPTY_DROP; -+ } - if (reschedule_failed_exec > 0 && !is_interactive(&options)) - die(_("--reschedule-failed-exec requires " - "--exec or --interactive")); + options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign); diff --git a/rebase-interactive.c b/rebase-interactive.c --- a/rebase-interactive.c @@ -296,19 +177,22 @@ } -void append_todo_help(unsigned keep_empty, int command_count, -+void append_todo_help(unsigned no_ask_empty, int command_count, ++void append_todo_help(int command_count, const char *shortrevisions, const char *shortonto, struct strbuf *buf) { @@ + "the rebase will be aborted.\n\n"); strbuf_add_commented_lines(buf, msg, strlen(msg)); - +- - if (!keep_empty) { -+ if (!no_ask_empty) { - msg = _("Note that empty commits are commented out"); - strbuf_add_commented_lines(buf, msg, strlen(msg)); - } +- msg = _("Note that empty commits are commented out"); +- strbuf_add_commented_lines(buf, msg, strlen(msg)); +- } + } + + int edit_todo_list(struct repository *r, struct todo_list *todo_list, diff --git a/rebase-interactive.h b/rebase-interactive.h --- a/rebase-interactive.h @@ -318,7 +202,7 @@ struct todo_list; -void append_todo_help(unsigned keep_empty, int command_count, -+void append_todo_help(unsigned no_ask_empty, int command_count, ++void append_todo_help(int command_count, const char *shortrevisions, const char *shortonto, struct strbuf *buf); int edit_todo_list(struct repository *r, struct todo_list *todo_list, @@ -327,157 +211,51 @@ --- a/sequencer.c +++ b/sequencer.c @@ - 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_reschedule_failed_exec, "rebase-merge/reschedule-failed-exec") -+static GIT_PATH_FUNC(rebase_path_drop_redundant_commits, "rebase-merge/drop_redundant_commits") -+static GIT_PATH_FUNC(rebase_path_keep_redundant_commits, "rebase-merge/keep_redundant_commits") -+static GIT_PATH_FUNC(rebase_path_ask_on_initially_empty, "rebase-merge/ask_on_initially_empty") - - static int git_sequencer_config(const char *k, const char *v, void *cb) + struct replay_opts *opts, + struct commit *commit) { +- int index_unchanged, empty_commit; ++ int index_unchanged, originally_empty; + + /* + * Three cases: @@ - empty_commit = is_original_commit_empty(commit); - if (empty_commit < 0) - return empty_commit; + if (opts->keep_redundant_commits) + return 1; + +- empty_commit = is_original_commit_empty(commit); +- if (empty_commit < 0) +- return empty_commit; - if (!empty_commit) -+ if (!empty_commit || opts->ask_on_initially_empty) ++ originally_empty = is_original_commit_empty(commit); ++ if (originally_empty < 0) ++ return originally_empty; ++ if (!originally_empty) return 0; else return 1; -@@ - char *author = NULL; - struct commit_message msg = { NULL, NULL, NULL, NULL }; - struct strbuf msgbuf = STRBUF_INIT; -- int res, unborn = 0, reword = 0, allow; -+ int res, unborn = 0, reword = 0, allow, drop_commit; - - if (opts->no_commit) { - /* -@@ - goto leave; - } - -- allow = allow_empty(r, opts, commit); -- if (allow < 0) { -- res = allow; -- goto leave; -- } else if (allow) -- flags |= ALLOW_EMPTY; -- if (!opts->no_commit) { -+ drop_commit = 0; -+ if (opts->drop_redundant_commits && is_index_unchanged(r)) { -+ drop_commit = 1; -+ fprintf(stderr, _("No changes -- Patch already applied.")); -+ } else { -+ allow = allow_empty(r, opts, commit); -+ if (allow < 0) { -+ res = allow; -+ goto leave; -+ } else if (allow) { -+ flags |= ALLOW_EMPTY; -+ } -+ } -+ if (!opts->no_commit && !drop_commit) { - if (author || command == TODO_REVERT || (flags & AMEND_MSG)) - res = do_commit(r, msg_file, author, opts, flags); - else -@@ - else if (!strcmp(key, "options.allow-empty-message")) - opts->allow_empty_message = - git_config_bool_or_int(key, value, &error_flag); -+ else if (!strcmp(key, "options.drop-redundant-commits")) -+ opts->drop_redundant_commits = -+ git_config_bool_or_int(key, value, &error_flag); - else if (!strcmp(key, "options.keep-redundant-commits")) - opts->keep_redundant_commits = - git_config_bool_or_int(key, value, &error_flag); -+ else if (!strcmp(key, "options.ask_on_initially_empty")) -+ opts->ask_on_initially_empty = -+ git_config_bool_or_int(key, value, &error_flag); - else if (!strcmp(key, "options.signoff")) - opts->signoff = git_config_bool_or_int(key, value, &error_flag); - else if (!strcmp(key, "options.record-origin")) -@@ - if (file_exists(rebase_path_reschedule_failed_exec())) - opts->reschedule_failed_exec = 1; - -+ if (file_exists(rebase_path_drop_redundant_commits())) -+ opts->drop_redundant_commits = 1; -+ -+ if (file_exists(rebase_path_keep_redundant_commits())) -+ opts->keep_redundant_commits = 1; -+ -+ if (file_exists(rebase_path_ask_on_initially_empty())) -+ opts->ask_on_initially_empty = 1; -+ - read_strategy_opts(opts, &buf); - strbuf_release(&buf); - -@@ - write_file(rebase_path_cdate_is_adate(), "%s", ""); - if (opts->ignore_date) - write_file(rebase_path_ignore_date(), "%s", ""); -+ if (opts->drop_redundant_commits) -+ write_file(rebase_path_drop_redundant_commits(), "%s", ""); -+ if (opts->keep_redundant_commits) -+ write_file(rebase_path_keep_redundant_commits(), "%s", ""); -+ if (opts->ask_on_initially_empty) -+ write_file(rebase_path_ask_on_initially_empty(), "%s", ""); - if (opts->reschedule_failed_exec) - write_file(rebase_path_reschedule_failed_exec(), "%s", ""); - -@@ - if (opts->allow_empty_message) - res |= git_config_set_in_file_gently(opts_file, - "options.allow-empty-message", "true"); -+ if (opts->drop_redundant_commits) -+ res |= git_config_set_in_file_gently(opts_file, -+ "options.drop-redundant-commits", "true"); - if (opts->keep_redundant_commits) - res |= git_config_set_in_file_gently(opts_file, - "options.keep-redundant-commits", "true"); -+ if (opts->ask_on_initially_empty) -+ res |= git_config_set_in_file_gently(opts_file, -+ "options.ask_on_initially_empty", "true"); - if (opts->signoff) - res |= git_config_set_in_file_gently(opts_file, - "options.signoff", "true"); @@ struct rev_info *revs, struct strbuf *out, unsigned flags) { - int keep_empty = flags & TODO_LIST_KEEP_EMPTY; -+ int drop_empty = flags & TODO_LIST_DROP_EMPTY; -+ int ask_empty = flags & TODO_LIST_ASK_EMPTY; int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS; int root_with_onto = flags & TODO_LIST_ROOT_WITH_ONTO; struct strbuf buf = STRBUF_INIT, oneline = STRBUF_INIT; -@@ - is_empty = is_original_commit_empty(commit); - if (!is_empty && (commit->object.flags & PATCHSAME)) - continue; -+ if (is_empty && drop_empty) -+ continue; - - strbuf_reset(&oneline); - pretty_print_commit(pp, commit, &oneline); @@ if (!to_merge) { /* non-merge commit: easy case */ strbuf_reset(&buf); - if (!keep_empty && is_empty) -+ if (is_empty && ask_empty) - strbuf_addf(&buf, "%c ", comment_line_char); +- strbuf_addf(&buf, "%c ", comment_line_char); strbuf_addf(&buf, "%s %s %s", cmd_pick, oid_to_hex(&commit->object.oid), + oneline.buf); @@ struct pretty_print_context pp = {0}; struct rev_info revs; struct commit *commit; - int keep_empty = flags & TODO_LIST_KEEP_EMPTY; -+ int drop_empty = flags & TODO_LIST_DROP_EMPTY; -+ int ask_empty = flags & TODO_LIST_ASK_EMPTY; const char *insn = flags & TODO_LIST_ABBREVIATE_CMDS ? "p" : "pick"; int rebase_merges = flags & TODO_LIST_REBASE_MERGES; @@ -491,19 +269,16 @@ if (!is_empty && (commit->object.flags & PATCHSAME)) continue; - if (!keep_empty && is_empty) -+ if (is_empty && drop_empty) -+ continue; -+ if (is_empty && ask_empty) - strbuf_addf(out, "%c ", comment_line_char); +- strbuf_addf(out, "%c ", comment_line_char); strbuf_addf(out, "%s %s ", insn, oid_to_hex(&commit->object.oid)); + pretty_print_commit(&pp, commit, out); @@ todo_list_to_strbuf(r, todo_list, &buf, num, flags); if (flags & TODO_LIST_APPEND_TODO_HELP) - append_todo_help(flags & TODO_LIST_KEEP_EMPTY, count_commands(todo_list), -+ append_todo_help(!(flags & TODO_LIST_ASK_EMPTY), -+ count_commands(todo_list), ++ append_todo_help(count_commands(todo_list), shortrevisions, shortonto, &buf); res = write_message(buf.buf, buf.len, file, 0); @@ -511,16 +286,6 @@ diff --git a/sequencer.h b/sequencer.h --- a/sequencer.h +++ b/sequencer.h -@@ - int allow_rerere_auto; - int allow_empty; - int allow_empty_message; -+ int drop_redundant_commits; - int keep_redundant_commits; -+ int ask_on_initially_empty; - int verbose; - int quiet; - int reschedule_failed_exec; @@ int sequencer_skip(struct repository *repo, struct replay_opts *opts); int sequencer_remove_state(struct replay_opts *opts); @@ -530,19 +295,34 @@ #define TODO_LIST_SHORTEN_IDS (1U << 1) #define TODO_LIST_ABBREVIATE_CMDS (1U << 2) #define TODO_LIST_REBASE_MERGES (1U << 3) -@@ - * `--onto`, we do not want to re-generate the root commits. - */ - #define TODO_LIST_ROOT_WITH_ONTO (1U << 6) -+#define TODO_LIST_DROP_EMPTY (1U << 7) -+#define TODO_LIST_ASK_EMPTY (1U << 8) - - - int sequencer_make_script(struct repository *r, struct strbuf *out, int argc, diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh --- a/t/t3421-rebase-topology-linear.sh +++ b/t/t3421-rebase-topology-linear.sh +@@ + test_run_rebase () { + result=$1 + shift +- test_expect_$result "rebase $* drops empty commit" " ++ test_expect_$result "rebase $* keeps begin-empty commits" " + reset_rebase && +- git rebase $* c l && +- test_cmp_rev c HEAD~2 && +- test_linear_range 'd l' c.. ++ git rebase $* j l && ++ test_cmp_rev c HEAD~4 && ++ test_linear_range 'j d k l' c.. + " + } +-test_run_rebase success '' ++test_run_rebase failure '' + test_run_rebase success -m + test_run_rebase success -i +-test_have_prereq !REBASE_P || test_run_rebase success -p ++test_have_prereq !REBASE_P || test_run_rebase failure -p + + test_run_rebase () { + result=$1 @@ test_run_rebase success '' test_run_rebase success -m @@ -603,31 +383,22 @@ + git commit -m "Five letters ought to be enough for anybody" +' + -+test_expect_success 'rebase --merge --empty=drop' ' ++test_expect_failure 'rebase (am-backend) with a variety of empty commits' ' ++ test_when_finished "git rebase --abort" && + git checkout -B testing localmods && -+ git rebase --merge --empty=drop upstream && -+ -+ test_write_lines C B A >expect && -+ git log --format=%s >actual && -+ test_cmp expect actual -+' ++ # rebase (--am) should not drop commits that start empty ++ git rebase upstream && + -+test_expect_success 'rebase --merge --empty=keep' ' -+ git checkout -B testing localmods && -+ git rebase --merge --empty=keep upstream && -+ -+ test_write_lines D C2 C B A >expect && ++ test_write_lines D C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + -+test_expect_success 'rebase --merge --empty=ask' ' ++test_expect_failure 'rebase --merge with a variety of empty commits' ' ++ test_when_finished "git rebase --abort" && + git checkout -B testing localmods && -+ test_must_fail git rebase --merge --empty=ask upstream && -+ -+ test_must_fail git rebase --skip && -+ git commit --allow-empty && -+ git rebase --continue && ++ # rebase --merge should not halt on the commit that becomes empty ++ git rebase --merge upstream && + + test_write_lines D C B A >expect && + git log --format=%s >actual && @@ -636,25 +407,17 @@ + +GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR + -+test_expect_success 'rebase --interactive --empty=drop' ' ++test_expect_success 'rebase --interactive with a variety of empty commits' ' + git checkout -B testing localmods && -+ git rebase --interactive --empty=drop upstream && ++ test_must_fail git rebase --interactive upstream && + -+ test_write_lines C B A >expect && -+ git log --format=%s >actual && -+ test_cmp expect actual -+' -+ -+test_expect_success 'rebase --interactive --empty=keep' ' -+ git checkout -B testing localmods && -+ git rebase --interactive --empty=keep upstream && ++ git rebase --skip && + -+ test_write_lines D C2 C B A >expect && ++ test_write_lines D C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + -+ +test_done diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh @@ -665,32 +428,28 @@ ' -test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' ' -+test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' ' ++test_expect_success 'Rebase -Xsubtree --onto commit' ' reset_rebase && git checkout -b rebase-onto to-rebase && - test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master && -+ test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --onto files-master master && ++ test_must_fail git rebase -Xsubtree=files_subtree --onto files-master master && : first pick results in no changes && - git rebase --continue && -+ test_must_fail git rebase --skip && -+ : last pick was an empty commit that has no changes, but we want to keep it && -+ git commit --allow-empty && ++ git rebase --skip && verbose test "$(commit_message HEAD~2)" = "master4" && verbose test "$(commit_message HEAD~)" = "files_subtree/master5" && verbose test "$(commit_message HEAD)" = "Empty commit" ' -test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' ' -+test_expect_success 'Rebase -Xsubtree --empty=ask --rebase-merges --onto commit' ' ++test_expect_success 'Rebase -Xsubtree --rebase-merges --onto commit' ' reset_rebase && git checkout -b rebase-merges-onto to-rebase && - test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root && -+ test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --rebase-merges --onto files-master --root && ++ test_must_fail git rebase -Xsubtree=files_subtree --rebase-merges --onto files-master --root && : first pick results in no changes && - git rebase --continue && -+ test_must_fail git rebase --skip && -+ : last pick was an empty commit that has no changes, but we want to keep it && -+ git commit --allow-empty && ++ git rebase --skip && verbose test "$(commit_message HEAD~2)" = "master4" && verbose test "$(commit_message HEAD~)" = "files_subtree/master5" && verbose test "$(commit_message HEAD)" = "Empty commit" -: ---------- > 4: c9542a2abe rebase (interactive-backend): fix handling of commits that become empty 2: bd3c5ec155 = 5: 9f66229d5c t3406: simplify an already simple test 3: 49388b79fd = 6: 8d731fa39c rebase, sequencer: remove the broken GIT_QUIET handling 4: 478479358f ! 7: b6b6597eef rebase: make sure to pass along the quiet flag to the sequencer @@ -8,13 +8,13 @@ --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ + replay.allow_empty_message = opts->allow_empty_message; + replay.drop_redundant_commits = (opts->empty == EMPTY_DROP); replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP); - replay.ask_on_initially_empty = (opts->empty == EMPTY_ASK && - !(opts->flags & REBASE_INTERACTIVE_EXPLICIT)); + replay.quiet = !(opts->flags & REBASE_NO_QUIET); replay.verbose = opts->flags & REBASE_VERBOSE; replay.reschedule_failed_exec = opts->reschedule_failed_exec; - replay.committer_date_is_author_date = + replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt); @@ N_("allow pre-rebase hook to run")), OPT_NEGBIT('q', "quiet", &options.flags, 5: ee26f5a161 = 8: 0acefa988b rebase: fix handling of restrict_revision 6: 34a69def33 = 9: 8c5b5b5133 t3432: make these tests work with either am or merge backends 7: f2c92853b4 ! 10: b8c087d6fb rebase: allow more types of rebases to fast-forward @@ -35,8 +35,8 @@ OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ - options.ignore_date) - options.flags |= REBASE_FORCE; + state_dir_base, cmd_live_rebase, buf.buf); + } + if ((options.flags & REBASE_INTERACTIVE_EXPLICIT) || + (action != ACTION_NONE) || @@ -47,7 +47,9 @@ + for (i = 0; i < options.git_am_opts.argc; i++) { const char *option = options.git_am_opts.argv[i], *p; - if (!strcmp(option, "--whitespace=fix") || + if (!strcmp(option, "--committer-date-is-author-date") || + !strcmp(option, "--ignore-date") || + !strcmp(option, "--whitespace=fix") || !strcmp(option, "--whitespace=strip")) - options.flags |= REBASE_FORCE; + allow_preemptive_ff = 0;8:
b307340f7c! 11:b50a1741e0git-rebase.txt: add more details about behavioral differences of backends
@@ -8,23 +8,24 @@
---a/Documentation/git-rebase.txt
+++b/Documentation/git-rebase.txt
@@
- with--keep-basein order to drop those commits from your branch.--ignore-whitespace::
-- Behaves differently depending on which backend is selected.
--+
--'am' backend: When applying a patch, ignore changes in whitespace in --context lines if necessary.
--+
--'interactive' backend: Treat lines with only whitespace changes as --unchanged for the sake of a three-way merge.
-+ Ignore whitespace-only changes in the commits being rebased,
-+ which may avoid "unnecessary" conflicts. (Both backends
-+ currently have differing edgecase bugs with this option; see
-+ BEHAVIORAL DIFFERENCES.)
-
--whitespace=<option>::
- This flag is passed to the 'git apply' program
+- These flag are passed to the 'git apply' program
++ These flags are passed to the 'git apply' program
+ (see linkgit:git-apply[1]) that applies the patch.
+ +
+ See also INCOMPATIBLE OPTIONS below.
+@@
+
+ * --committer-date-is-author-date
+ * --ignore-date
+- * --whitespace
+ * --ignore-whitespace
++ * --whitespace
+ * -C
+
+ are incompatible with the following options:
@@
Directory rename detection
~~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -82,24 +83,14 @@
+The interactive backend works with the full commits on both sides of +history and thus has no such limitations.
+
-+--ignore-whitespace
-+~~~~~~~~~~~~~~~~~~~
++Hooks
++~~~~~
+
-+The --ignore-whitespace option is supposed to ignore whitespace-only -+changes if it allows the code to merge cleanly. Unfortunately, the -+different backends implement this differently, and both have different -+edge case bugs.
-++
-+'am' backend: When applying a patch, ignore changes in whitespace in -+context lines if necessary. (Which implies that if the whitespace -+change was not in the context lines but on a line with a real change, -+then the rebase will still fail with "unnecessary" content conflicts.)
-++
-+'interactive' backend: Treat lines with only whitespace changes as -+unchanged for the sake of a three-way merge. This means that if one -+side made no changes and the commits being rebased had whitespace-only -+changes, those whitespaces fixups will be discarded despite the fact -+that they present no content conflict.
++The am backend has not traditionally called the post-commit hook, ++while the merge/interactive backend has. However, this was by ++accident of implementation rather than by design. Both backends ++should have the same behavior, though it is not clear which one is ++correct.
+
+Miscellaneous differences
+~~~~~~~~~~~~~~~~~~~~~~~~~
@@ -123,19 +114,3 @@include::merge-strategies.txt[]
-
- diff --gita/t/t3433-rebase-options-compatibility.shb/t/t3433-rebase-options-compatibility.sh
- ---a/t/t3433-rebase-options-compatibility.sh
- +++b/t/t3433-rebase-options-compatibility.sh
-@@
- GIT_AUTHOR_DATE="1999-04-02T08:03:20+05:30"
- exportGIT_AUTHOR_DATE
-
--# This is a special case in which both am and interactive backends
--# provide the same output. It was done intentionally because
--# both the backends fall short of optimal behaviour.
-+# This is a common case in which both am and interactive backends
-+# provide the same output with --ignore-whitespace.
-test_expect_success'setup' '
- git checkout -b topic &&
-q_to_tab>file <<-\EOF &&9: 7c3f2e07f3 = 12: 58e6e4ffb3 rebase: move incompatibility checks between backend options a bit earlier 10: 1df11f0b51 ! 13: 5478c730ac rebase: add an --am option@@ -24,8 +24,8 @@
+See also INCOMPATIBLE OPTIONS below.
+
--empty={drop,keep,ask}::
- How to handle commits that become empty (because they contain a
- subset of already upstream changes) or start empty. With drop
+ How to handle commits that are not empty to start and are not
+ clean cherry-picks of any upstream commit, but which become
@@
Ensure at least <n> lines of surrounding context match before and after each change. When fewer lines of surrounding
@@ -37,7 +37,7 @@@@
--whitespace=<option>::
- This flag is passed to the 'git apply' program
+ These flags are passed to the 'git apply' program
(see linkgit:git-apply[1]) that applies the patch.
+ Implies --am.
+
@@ -48,9 +48,9 @@
The following options:+ * --am
- * --whitespace
- * -C
-
+ * --committer-date-is-author-date
+ * --ignore-date
+ * --ignore-whitespacediff --git
a/builtin/rebase.cb/builtin/rebase.c---a/builtin/rebase.c11: ff43593211 = 14: db5e29bd81 git-prompt: change the prompt for interactive-based rebases -: ---------- > 15: 413e190ac9 rebase: drop '-i' from the reflog for interactive-based rebases 12: 99388f24e5 ! 16: 170be283a8 rebase tests: mark tests specific to the am-backend with --am@@ -228,40 +228,6 @@
test_rebase_same_head_$status_f $what_f $cmp_f " --merge --no-ff" "$*"
}- diff --git
a/t/t3433-rebase-options-compatibility.shb/t/t3433-rebase-options-compatibility.sh
- ---a/t/t3433-rebase-options-compatibility.sh
- +++b/t/t3433-rebase-options-compatibility.sh
-@@
- new line 2
- line 3
- EOF
--test_must_failgit rebase main side &&
-+test_must_failgit rebase --am main side &&
- git rebase --abort &&
-- git rebase --ignore-whitespace main side &&
-+ git rebase --am --ignore-whitespace main side &&
-test_cmpexpect file
- '
-
-@@
-
-test_expect_success'--committer-date-is-author-date works with am backend' '
- git commit --amend &&
-- git rebase --committer-date-is-author-date HEAD^ &&
-+ git rebase --am --committer-date-is-author-date HEAD^ &&
- git show HEAD --pretty="format:%ai" >authortime &&
- git show HEAD --pretty="format:%ci" >committertime &&
-test_cmpauthortime committertime
-@@
- # sets to +0530.
-test_expect_success'--ignore-date works with am backend' '
- git commit --amend --date="$GIT_AUTHOR_DATE" &&
-- git rebase --ignore-date HEAD^ &&
-+ git rebase --am --ignore-date HEAD^ &&
- git show HEAD --pretty="format:%ai" >authortime &&
- grep "+0000" authortime
- '
-
diff --gita/t/t5407-post-rewrite-hook.shb/t/t5407-post-rewrite-hook.sh
---a/t/t5407-post-rewrite-hook.sh
+++b/t/t5407-post-rewrite-hook.sh13: c2ba6317bf = 17: 1e3d4066c4 rebase tests: repeat some tests using the merge backend instead of am 14: 8bec6df51a = 18: 9b4ac83d2d rebase: make the backend configurable via config setting 15: 044853fd61 = 19: 859a4a94d7 rebase: change the default backend from "am" to "merge"
Quoted 7 lines · Phillip Wood
Quoted 6 lines
Changes possibly missing from this version, for discussion:
* I did not remove the --am option as suggested by Phillip, since Junio and Phillip were still discussing whether it is wanted/needed.
* I did not address the last two items Jonathan brought up as I couldn't find enough information to reproduce or understand the problems.
Quoted 6 lines
I think I've got a fix for the failure with --committer-date-is-author-date that Jonathan reported which I'll post next week - the bug was not in this series, it was just exposed by it. I'll try and read through this series next week as well.
Best Wishes
Thanks.
As to the "--am" option, I do not care too deeply about it anymore, so if there is nothing else that we need to further polish, should we move this forward to 'next' soonish?
Quoted 15 lines · Junio C Hamano
Quoted 12 lines · Phillip Wood
Quoted 6 lines
Changes possibly missing from this version, for discussion:
* I did not remove the --am option as suggested by Phillip, since Junio and Phillip were still discussing whether it is wanted/needed.
* I did not address the last two items Jonathan brought up as I couldn't find enough information to reproduce or understand the problems.I think I've got a fix for the failure with --committer-date-is-author-date that Jonathan reported which I'll post next week - the bug was not in this series, it was just exposed by it. I'll try and read through this series next week as well.
Quoted 7 lines
Quoted 1 line
Best Wishes
Thanks.
As to the "--am" option, I do not care too deeply about it anymore, so if there is nothing else that we need to further polish, should we move this forward to 'next' soonish?
I was hoping to hear back from Phillip, as he always provides great comments. I believe I've addressed all his comments up through v3, but he hasn't had time in the last 2.5 weeks to review v4.
There was also an issue surrounding post-commit hooks that I think Jonathan and/or Emily were looking in to. I haven't heard from them in a while, but I didn't think the issue was a blocker either. If it is, I can try to help push something along.
So, the current state is that there's nothing left for me to polish that I know of. If others know of things I've missed or want to review v4 and point out changes I should make, I'm happy to make them.
v5 cover rebase: make the default backend configurable and change the default
In reply to Elijah Newren via GitGitGadgetThis is v5 of en/rebase-backend based on v2.25.0. This series does a lot of work around making the default rebase backend configurable, and switches the default from the apply backend to the merge one.
Changes since v4:
* Added a note about an unfortunately-timed Ctrl-C to the docs, as suggested by SZEDER.
* Addressed feedback from Phillip
* Added a new patch at the end renaming the two backends. PLEASE take a look at its commit message for rationale and let me know if you agree with it.
Note that in particular, the last patch at the end could have been partially squashed into other patches, but if people objected to the patch in general I wanted Junio to be able to take the rest of the series and just drop that patch.
As with v4, there are some things mentioned earlier that are not addressed:
* I did not remove the --am (now --apply) option as suggested by Phillip, since Junio and Phillip were still discussing whether it is wanted/needed. I kind of like having it, but have no strong opinion.
* I noted the post-commit hook in the differences between backends. Emily is investigating what changes need to happen there, so I merely documented the existing differences.
* I did not address the last two items Jonathan brought up as I couldn't find enough information to reproduce or understand the problems.
Elijah Newren (20):
git-rebase.txt: update description of --allow-empty-message
t3404: directly test the behavior of interest
rebase (interactive-backend): make --keep-empty the default rebase (interactive-backend): fix handling of commits that become
empty
t3406: simplify an already simple test
rebase, sequencer: remove the broken GIT_QUIET handling
rebase: make sure to pass along the quiet flag to the sequencer
rebase: fix handling of restrict_revision
t3432: make these tests work with either am or merge backends
rebase: allow more types of rebases to fast-forward git-rebase.txt: add more details about behavioral differences of
backends
rebase: move incompatibility checks between backend options a bit
earlier
rebase: add an --am option
git-prompt: change the prompt for interactive-based rebases
rebase: drop '-i' from the reflog for interactive-based rebases rebase tests: mark tests specific to the am-backend with --am rebase tests: repeat some tests using the merge backend instead of am
rebase: make the backend configurable via config setting
rebase: change the default backend from "am" to "merge"
rebase: rename the two primary rebase backends
Documentation/config/rebase.txt | 6 + Documentation/git-rebase.txt | 170 +++++++++++++--- builtin/rebase.c | 257 +++++++++++++++++-------- contrib/completion/git-prompt.sh | 6 +- rebase-interactive.c | 7 +- rebase-interactive.h | 2 +- sequencer.c | 82 +++++--- sequencer.h | 3 +- t/t3400-rebase.sh | 40 +++- t/t3401-rebase-and-am-rename.sh | 4 +- t/t3404-rebase-interactive.sh | 19 +- t/t3406-rebase-message.sh | 19 +- t/t3407-rebase-abort.sh | 6 +- t/t3420-rebase-autostash.sh | 22 +-- t/t3421-rebase-topology-linear.sh | 48 ++--- t/t3424-rebase-empty.sh | 126 ++++++++++++ t/t3425-rebase-topology-merges.sh | 8 +- t/t3427-rebase-subtree.sh | 12 +- t/t3432-rebase-fast-forward.sh | 54 +++--- t/t5407-post-rewrite-hook.sh | 16 +- t/t5520-pull.sh | 27 ++- t/t6047-diff3-conflict-markers.sh | 13 +- t/t7512-status-help.sh | 12 +- t/t9106-git-svn-commit-diff-clobber.sh | 3 +- t/t9903-bash-prompt.sh | 8 +- 25 files changed, 693 insertions(+), 277 deletions(-) create mode 100755 t/t3424-rebase-empty.sh
base-commit: d0654dc308b0ba76dd8ed7bbb33c8d8f7aacd783
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v5
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v5
Pull-Request: https://github.com/git/git/pull/679
Range-diff vs v4:
1: 3ea48d53940 = 1: 3ea48d53940 git-rebase.txt: update description of --allow-empty-message 2: 10fdd162a05 = 2: 10fdd162a05 t3404: directly test the behavior of interest 3: 179f82ab83e ! 3: 74cab9a32b3 rebase (interactive-backend): make --keep-empty the default
@@ -4,13 +4,13 @@
Different rebase backends have different treatment for commits which start empty (i.e. have no changes relative to their parent), and the
- --keep-empty option was added at some point to allow adjusting behavior
- for the interactive backend. The handling of commits which start empty
- is actually quite similar to commit b00bf1c9a8dd (git-rebase: make
- --allow-empty-message the default, 2018-06-27), which pointed out that
- the behavior for various backends is often more happenstance than
- design. The specific change made in that commit is actually quite
- relevant as well and much of the logic there directly applies here.
+ --keep-empty option was added at some point to allow adjusting behavior.
+ The handling of commits which start empty is actually quite similar to
+ commit b00bf1c9a8dd (git-rebase: make --allow-empty-message the default,
+ 2018-06-27), which pointed out that the behavior for various backends is
+ often more happenstance than design. The specific change made in that
+ commit is actually quite relevant as well and much of the logic there
+ directly applies here.
It makes a lot of sense in 'git commit' to error out on the creation of
empty commits, unless an override flag is provided. However, once
@@ -405,8 +405,6 @@
+ test_cmp expect actual
+'
+
-+GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
-+
+test_expect_success 'rebase --interactive with a variety of empty commits' '
+ git checkout -B testing localmods &&
+ test_must_fail git rebase --interactive upstream &&
4: c9542a2abe0 ! 4: 54b745c4ab5 rebase (interactive-backend): fix handling of commits that become empty
@@ -98,6 +98,8 @@
+ With ask (implied by --interactive), the rebase will halt when
+ an empty commit is applied allowing you to choose whether to
+ drop it, edit files more, or just commit the empty changes.
++ Other options, like --exec, will use the default of drop unless
++ -i/--interactive is explicitly specified.
++
+Note that commits which start empty are kept, and commits which are
+clean cherry-picks (as determined by `git log --cherry-mark ...`) are
@@ -224,7 +226,7 @@
REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN),
OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate),
+ OPT_CALLBACK_F(0, "empty", &options, N_("{drop,keep,ask}"),
-+ N_("how to handle empty commits"),
++ N_("how to handle commits that become empty"),
+ PARSE_OPT_NONEG, parse_opt_empty),
{ OPTION_CALLBACK, 'k', "keep-empty", &options, NULL,
N_("(DEPRECATED) keep empty commits"),
@@ -342,8 +344,10 @@
- if (!opts->no_commit) {
+ } else if (allow == 2) {
+ drop_commit = 1;
-+ fprintf(stderr, _("No changes -- Patch already applied.\n"));
-+ } // else allow == 0 and there's nothing special to do
++ fprintf(stderr,
++ _("dropping %s %s -- patch contents already upstream\n"),
++ oid_to_hex(&commit->object.oid), msg.subject);
++ } /* else allow == 0 and there's nothing special to do */
+ if (!opts->no_commit && !drop_commit) {
if (author || command == TODO_REVERT || (flags & AMEND_MSG))
res = do_commit(r, msg_file, author, opts, flags);
@@ -404,9 +408,7 @@
-test_expect_failure 'rebase --merge with a variety of empty commits' '
- test_when_finished "git rebase --abort" &&
+test_expect_success 'rebase --merge --empty=drop' '
- git checkout -B testing localmods &&
-- # rebase --merge should not halt on the commit that becomes empty
-- git rebase --merge upstream &&
++ git checkout -B testing localmods &&
+ git rebase --merge --empty=drop upstream &&
+
+ test_write_lines D C B A >expect &&
@@ -414,6 +416,17 @@
+ test_cmp expect actual
+'
+
++test_expect_success 'rebase --merge uses default of --empty=drop' '
+ git checkout -B testing localmods &&
+- # rebase --merge should not halt on the commit that becomes empty
+ git rebase --merge upstream &&
+
+ test_write_lines D C B A >expect &&
+@@
+ test_cmp expect actual
+ '
+
+-test_expect_success 'rebase --interactive with a variety of empty commits' '
+test_expect_success 'rebase --merge --empty=keep' '
+ git checkout -B testing localmods &&
+ git rebase --merge --empty=keep upstream &&
@@ -428,14 +441,12 @@
+ test_must_fail git rebase --merge --empty=ask upstream &&
+
+ git rebase --skip &&
-
- test_write_lines D C B A >expect &&
- git log --format=%s >actual &&
-@@
-
- GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
-
--test_expect_success 'rebase --interactive with a variety of empty commits' '
++
++ test_write_lines D C B A >expect &&
++ git log --format=%s >actual &&
++ test_cmp expect actual
++'
++
+test_expect_success 'rebase --interactive --empty=drop' '
+ git checkout -B testing localmods &&
+ git rebase --interactive --empty=drop upstream &&
@@ -455,11 +466,19 @@
+'
+
+test_expect_success 'rebase --interactive --empty=ask' '
- git checkout -B testing localmods &&
-- test_must_fail git rebase --interactive upstream &&
++ git checkout -B testing localmods &&
+ test_must_fail git rebase --interactive --empty=ask upstream &&
-
- git rebase --skip &&
++
++ git rebase --skip &&
++
++ test_write_lines D C B A >expect &&
++ git log --format=%s >actual &&
++ test_cmp expect actual
++'
++
++test_expect_success 'rebase --interactive uses default of --empty=ask' '
+ git checkout -B testing localmods &&
+ test_must_fail git rebase --interactive upstream &&
diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh
5: 9f66229d5cc = 5: f5170e71510 t3406: simplify an already simple test
6: 8d731fa39c3 = 6: 2791b818c4d rebase, sequencer: remove the broken GIT_QUIET handling
7: b6b6597eef8 = 7: 3dccb58d066 rebase: make sure to pass along the quiet flag to the sequencer
8: 0acefa988b1 = 8: 4e6f5ce8c46 rebase: fix handling of restrict_revision
9: 8c5b5b51330 = 9: bcd04ec4ded t3432: make these tests work with either am or merge backends
10: b8c087d6fb1 = 10: 45eb87b36b0 rebase: allow more types of rebases to fast-forward
11: b50a1741e0b = 11: 11e96b9a5fa git-rebase.txt: add more details about behavioral differences of backends
12: 58e6e4ffb3f = 12: 0c921919dce rebase: move incompatibility checks between backend options a bit earlier
13: 5478c730aca = 13: de1b0aaca88 rebase: add an --am option
14: db5e29bd818 = 14: e4c04d3cf8b git-prompt: change the prompt for interactive-based rebases
15: 413e190ac9a ! 15: f0f00c1e7fd rebase: drop '-i' from the reflog for interactive-based rebases @@ -54,17 +54,6 @@
*/
return error(_("%s: Unable to write new index file"),
_(action_name(opts)));
-@@
- int next = todo_list->current, offset, fd;
-
- /*
-- * rebase -i writes "git-rebase-todo" without the currently executing
-- * command, appending it to "done" instead.
-+ * interactive backend writes "git-rebase-todo" without the currently
-+ * executing command, appending it to "done" instead.
- */
- if (is_rebase_i(opts))
- next++;
@@
return error(_("illegal label name: '%.*s'"), len, name);
16: 170be283a85 ! 16: 56486d4d694 rebase tests: mark tests specific to the am-backend with --am
@@ -173,6 +173,163 @@
testrebase " --interactive" .git/rebase-merge
+ diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh
+ --- a/t/t3421-rebase-topology-linear.sh
+ +++ b/t/t3421-rebase-topology-linear.sh
+@@
+ test_linear_range 'd e' c..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+ test_run_rebase success -m
+ test_run_rebase success -i
+ test_have_prereq !REBASE_P || test_run_rebase success -p
+@@
+ test_cmp_rev e HEAD
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+ test_run_rebase success -m
+ test_run_rebase success -i
+ test_have_prereq !REBASE_P || test_run_rebase success -p
+@@
+ test_linear_range 'd e' b..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+ test_run_rebase success --fork-point
+ test_run_rebase success -m
+ test_run_rebase success -i
+@@
+ test_linear_range 'd e' branch-b..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+ test_run_rebase success --fork-point
+ test_run_rebase success -m
+ test_run_rebase success -i
+@@
+ test_cmp_rev e HEAD
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+ test_run_rebase success --fork-point
+ test_run_rebase success -m
+ test_run_rebase success -i
+@@
+ test_linear_range 'd i' h..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+ test_run_rebase success -m
+ test_run_rebase success -i
+ test_have_prereq !REBASE_P || test_run_rebase success -p
+@@
+ test_linear_range 'd' h..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+ test_run_rebase success -m
+ test_run_rebase success -i
+ test_have_prereq !REBASE_P || test_run_rebase success -p
+@@
+ test_linear_range 'd i' f..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+ test_run_rebase success -m
+ test_run_rebase success -i
+ test_have_prereq !REBASE_P || test_run_rebase success -p
+@@
+ test_linear_range 'd gp i' h..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+ test_run_rebase success -m
+ test_run_rebase success -i
+ test_have_prereq !REBASE_P || test_run_rebase success -p
+@@
+ test_linear_range 'j d k l' c..
+ "
+ }
+-test_run_rebase failure ''
++test_run_rebase failure --am
+ test_run_rebase success -m
+ test_run_rebase success -i
+ test_have_prereq !REBASE_P || test_run_rebase failure -p
+@@
+ test_linear_range 'd k l' c..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+ test_run_rebase success -m
+ test_run_rebase success -i
+ test_have_prereq !REBASE_P || test_run_rebase success -p
+@@
+ test_linear_range 'd k l' j..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+ test_run_rebase success -m
+ test_run_rebase success -i
+ test_have_prereq !REBASE_P || test_run_rebase success -p
+@@
+ test_linear_range 'x y' c..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+ test_run_rebase success -m
+ test_run_rebase success -i
+ test_have_prereq !REBASE_P || test_run_rebase success -p
+@@
+ test_linear_range 'x y' c..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+ test_run_rebase success -m
+ test_run_rebase success -i
+ test_have_prereq !REBASE_P || test_run_rebase failure -p
+@@
+ test_linear_range 'x y' m..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+ test_run_rebase success -m
+ test_run_rebase success -i
+ test_have_prereq !REBASE_P || test_run_rebase success -p
+@@
+ "
+ }
+
+-test_run_rebase success ''
++test_run_rebase success --am
+ test_run_rebase success -m
+ test_run_rebase success -i
+ test_have_prereq !REBASE_P || test_run_rebase failure -p
+@@
+ test_linear_range 'x y' m..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+ test_run_rebase success -m
+ test_run_rebase success -i
+ test_have_prereq !REBASE_P || test_run_rebase failure -p
+
diff --git a/t/t3425-rebase-topology-merges.sh b/t/t3425-rebase-topology-merges.sh --- a/t/t3425-rebase-topology-merges.sh
+++ b/t/t3425-rebase-topology-merges.sh
17: 1e3d4066c40 = 17: dc12574a8b3 rebase tests: repeat some tests using the merge backend instead of am 18: 9b4ac83d2d0 = 18: ba1f2699b39 rebase: make the backend configurable via config setting 19: 859a4a94d7f ! 19: 59faaa2f920 rebase: change the default backend from "am" to "merge"
@@ -13,6 +13,7 @@
patches incorrectly.[2]
* lack of access to original commits means that conflict marker annotation has less information available.
+ * the am backend has safety problems with an ill-timed interrupt.
Also, the merge/interactive backend have far more abilities, appear to currently have a slight performance advantage[3] and have room for more
@@ -38,6 +39,24 @@
+
Note that a rebase merge works by replaying each commit from the working branch on top of the <upstream> branch. Because of this, when a merge
+@@
+ should have the same behavior, though it is not clear which one is
+ correct.
+
++Interruptability
++~~~~~~~~~~~~~~~~
++
++The am backend has safety problems with an ill-timed interrupt; if the ++user presses Ctrl-C at the wrong time to try to abort the rebase, the ++rebase can enter a state where it cannot be aborted with a subsequent ++git rebase --abort. The interactive backend does not appear to ++suffer from the same shortcoming. (See ++https://lore.kernel.org/git/[email protected]/ for ++details.)
++
+ Miscellaneous differences
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+
diff --git a/builtin/rebase.c b/builtin/rebase.c
--- a/builtin/rebase.c
-: ----------- > 20: ad8339aebf2 rebase: rename the two primary rebase backends
Signature · 1 line
gitgitgadget
PATCH v5 1/20 git-rebase.txt: update description of --allow-empty-message
From: Elijah Newren <[email protected]>
Commit b00bf1c9a8dd ("git-rebase: make --allow-empty-message the default", 2018-06-27) made --allow-empty-message the default and thus turned --allow-empty-message into a no-op but did not update the documentation to reflect this. Update the documentation now, and hide the option from the normal -h output since it is not useful.
--- Documentation/git-rebase.txt | 7 ++++--- builtin/rebase.c | 12 +++++++----- 2 files changed, 11 insertions(+), 8 deletions(-)
Documentation/git-rebase.txt +4 -3
@@ -265,9 +265,10 @@ See also INCOMPATIBLE OPTIONS below. See also INCOMPATIBLE OPTIONS below. --allow-empty-message:: - By default, rebasing commits with an empty message will fail. - This option overrides that behavior, allowing commits with empty - messages to be rebased. + No-op. Rebasing commits with an empty message used to fail + and this option would override that behavior, allowing commits + with empty messages to be rebased. Now commits with an empty + message do not cause rebasing to halt. + See also INCOMPATIBLE OPTIONS below.
builtin/rebase.c +7 -5
@@ -453,8 +453,9 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)OPT_NEGBIT (0 ,"ff" , &opts.flags,N_ ("allow fast-forward" ),REBASE_FORCE ),OPT_BOOL (0 ,"keep-empty" , &opts.keep_empty,N_ ("keep empty commits" )), -OPT_BOOL (0 ,"allow-empty-message" , &opts.allow_empty_message, -N_ ("allow commits with empty messages" )), +OPT_BOOL_F (0 ,"allow-empty-message" , &opts.allow_empty_message, +N_ ("allow commits with empty messages" ), +PARSE_OPT_HIDDEN ),OPT_BOOL (0 ,"rebase-merges" , &opts.rebase_merges,N_ ("rebase merge commits" )),OPT_BOOL (0 ,"rebase-cousins" , &opts.rebase_cousins, N_("keep original branch points of cousins")),
@@ -1495,9 +1496,10 @@ 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" )), -OPT_BOOL (0 ,"allow-empty-message" , - &options.allow_empty_message, -N_ ("allow rebasing commits with empty messages" )), +OPT_BOOL_F (0 ,"allow-empty-message" , + &options.allow_empty_message, +N_ ("allow rebasing commits with empty messages" ), +PARSE_OPT_HIDDEN ), {OPTION_STRING, 'r', "rebase-merges", &rebase_merges,N_ ("mode" ), N_("try to rebase merges instead of skipping them"),
Signature · 1 line
gitgitgadget
PATCH v5 2/20 t3404: directly test the behavior of interest
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
t3404.3 is a simple test added by commit d078c3910689 ("t3404: todo list with commented-out commands only aborts", 2018-08-10) which was designed to test a todo list that only contained commented-out commands. There were two problems with this test: (1) its title did not reflect the purpose of the test, and (2) it tested the desired behavior through a side-effect of other functionality instead of directly testing the desired behavior discussed in the commit message.
Modify the test to directly test the desired behavior and update the test title.
--- t/t3404-rebase-interactive.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-)
t/t3404-rebase-interactive.sh +4 -3
@@ -72,15 +72,16 @@ test_expect_success 'rebase --keep-empty' ' test_line_count = 6 actual ' -test_expect_success 'rebase -i with empty HEAD' ' +test_expect_success 'rebase -i with empty todo list' ' cat >expect <<-\EOF && error: nothing to do EOF ( set_fake_editor && - test_must_fail env FAKE_LINES="1 exec_true" \ - git rebase -i HEAD^ >actual 2>&1 + test_must_fail env FAKE_LINES="#" \ + git rebase -i HEAD^ >output 2>&1 ) && + tail -n 1 output >actual && # Ignore output about changing todo list test_i18ncmp expect actual '
Signature · 1 line
gitgitgadget
PATCH v5 3/20 rebase (interactive-backend): make --keep-empty the default
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
Different rebase backends have different treatment for commits which start empty (i.e. have no changes relative to their parent), and the --keep-empty option was added at some point to allow adjusting behavior. The handling of commits which start empty is actually quite similar to commit b00bf1c9a8dd (git-rebase: make --allow-empty-message the default, 2018-06-27), which pointed out that the behavior for various backends is often more happenstance than design. The specific change made in that commit is actually quite relevant as well and much of the logic there directly applies here.
It makes a lot of sense in 'git commit' to error out on the creation of empty commits, unless an override flag is provided. However, once someone determines that there is a rare case that merits using the manual override to create such a commit, it is somewhere between annoying and harmful to have to take extra steps to keep such intentional commits around. Granted, empty commits are quite rare, which is why handling of them doesn't get considered much and folks tend to defer to existing (accidental) behavior and assume there was a reason for it, leading them to just add flags (--keep-empty in this case) that allow them to override the bad defaults. Fix the interactive backend so that --keep-empty is the default, much like we did with --allow-empty-message. The am backend should also be fixed to have --keep-empty semantics for commits that start empty, but that is not included in this patch other than a testcase documenting the failure.
Note that there was one test in t3421 which appears to have been written expecting --keep-empty to not be the default as correct behavior. This test was introduced in commit 00b8be5a4d38 ("add tests for rebasing of empty commits", 2013-06-06), which was part of a series focusing on rebase topology and which had an interesting original cover letter at
https://lore.kernel.org/git/[email protected]/
which noted
Your input especially appreciated on whether you agree with the intent of the test cases.
and then went into a long example about how one of the many tests added had several questions about whether it was correct. As such, I believe most the tests in that series were about testing rebase topology with as many different flags as possible and were not trying to state in general how those flags should behave otherwise.
--- Documentation/git-rebase.txt | 24 ++++++----- builtin/rebase.c | 28 +++++++++---- rebase-interactive.c | 7 +--- rebase-interactive.h | 2 +- sequencer.c | 20 ++++----- sequencer.h | 2 +- t/t3421-rebase-topology-linear.sh | 16 +++---- t/t3424-rebase-empty.sh | 70 +++++++++++++++++++++++++++++++ t/t3427-rebase-subtree.sh | 12 +++--- 9 files changed, 126 insertions(+), 55 deletions(-) create mode 100755 t/t3424-rebase-empty.sh
Documentation/git-rebase.txt +13 -11
@@ -259,10 +259,13 @@ See also INCOMPATIBLE OPTIONS below. unchanged as a result. --keep-empty:: - Keep the commits that do not change anything from its - parents in the result. + No-op. Rebasing commits that started empty (had no change + relative to their parent) used to fail and this option would + override that behavior, allowing commits with empty changes to + be rebased. Now commits with no changes do not cause rebasing + to halt. + -See also INCOMPATIBLE OPTIONS below. +See also BEHAVIORAL DIFFERENCES and INCOMPATIBLE OPTIONS below. --allow-empty-message:: No-op. Rebasing commits with an empty message used to fail
@@ -577,15 +580,14 @@ There are some subtle differences how the backends behave. Empty commits ~~~~~~~~~~~~~ -The am backend drops any "empty" commits, regardless of whether the -commit started empty (had no changes relative to its parent to -start with) or ended empty (all changes were already applied -upstream in other commits). +The am backend unfortunately drops intentionally empty commits, i.e. +commits that started empty, though these are rare in practice. It +also drops commits that become empty and has no option for controlling +this behavior. -The interactive backend drops commits by default that -started empty and halts if it hits a commit that ended up empty. -The `--keep-empty` option exists for the interactive backend to allow -it to keep commits that started empty. +The interactive backend keeps intentionally empty commits. +Unfortunately, it always halts whenever it runs across a commit that +becomes empty, even when the rebase is not explicitly interactive. Directory rename detection ~~~~~~~~~~~~~~~~~~~~~~~~~~
builtin/rebase.c +19 -9
@@ -77,7 +77,6 @@ struct rebase_options {const char *action;int signoff;int allow_rerere_autoupdate; -int keep_empty;int autosquash;char *gpg_sign_opt;int autostash;
@@ -375,7 +374,6 @@ static int run_rebase_interactive(struct rebase_options *opts,git_config_get_bool ("rebase.abbreviatecommands" , &abbreviate_commands); - flags |= opts->keep_empty ?TODO_LIST_KEEP_EMPTY :0 ; flags |= abbreviate_commands ?TODO_LIST_ABBREVIATE_CMDS :0 ; flags |= opts->rebase_merges ?TODO_LIST_REBASE_MERGES :0 ; flags |= opts->rebase_cousins >0 ?TODO_LIST_REBASE_COUSINS :0 ;
@@ -439,6 +437,17 @@ static int run_rebase_interactive(struct rebase_options *opts,return ret; } +static int parse_opt_keep_empty (const struct option *opt,const char *arg, +int unset) +{ +struct rebase_options *opts = opt->value; + +BUG_ON_OPT_ARG (arg); + + opts->type =REBASE_INTERACTIVE ; +return 0 ; +} +static const char *const builtin_rebase_interactive_usage[] = {N_ ("git rebase--interactive [<options>]" ),NULL
@@ -452,7 +461,10 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix)struct option options[] = {OPT_NEGBIT (0 ,"ff" , &opts.flags,N_ ("allow fast-forward" ),REBASE_FORCE ), -OPT_BOOL (0 ,"keep-empty" , &opts.keep_empty,N_ ("keep empty commits" )), + {OPTION_CALLBACK ,'k' ,"keep-empty" , &options,NULL , +N_ ("(DEPRECATED) keep empty commits" ), +PARSE_OPT_NOARG |PARSE_OPT_HIDDEN , + parse_opt_keep_empty },OPT_BOOL_F (0 ,"allow-empty-message" , &opts.allow_empty_message,N_ ("allow commits with empty messages" ),PARSE_OPT_HIDDEN ),
@@ -1145,7 +1157,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action) opts->allow_rerere_autoupdate ? opts->allow_rerere_autoupdate ==RERERE_AUTOUPDATE ? "--rerere-autoupdate" : "--no-rerere-autoupdate" : ""); -add_var (&script_snippet,"keep_empty" , opts->keep_empty ?"yes" :"" );add_var (&script_snippet,"autosquash" , opts->autosquash ?"t" :"" );add_var (&script_snippet,"gpg_sign_opt" , opts->gpg_sign_opt);add_var (&script_snippet,"cmd" , opts->cmd);
@@ -1483,8 +1494,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) "ignoring them"), REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN), OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate), -OPT_BOOL ('k' ,"keep-empty" , &options.keep_empty, -N_ ("preserve empty commits during rebase" )), + {OPTION_CALLBACK ,'k' ,"keep-empty" , &options,NULL , +N_ ("(DEPRECATED) keep empty commits" ), +PARSE_OPT_NOARG |PARSE_OPT_HIDDEN , + parse_opt_keep_empty },OPT_BOOL (0 ,"autosquash" , &options.autosquash,N_ ("move commits that begin with " "squash!/fixup! under -i")),
@@ -1747,9 +1760,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)if (!(options.flags &REBASE_NO_QUIET ))argv_array_push (&options.git_am_opts,"-q" ); -if (options.keep_empty) -imply_interactive (&options,"--keep-empty" ); -if (gpg_sign) {free (options.gpg_sign_opt); options.gpg_sign_opt =xstrfmt ("-S%s" , gpg_sign);
rebase-interactive.c +1 -6
@@ -28,7 +28,7 @@ static enum missing_commit_check_level get_missing_commit_check_level(void)return MISSING_COMMIT_CHECK_IGNORE ; } -void append_todo_help (unsigned keep_empty,int command_count, +void append_todo_help (int command_count,const char *shortrevisions,const char *shortonto,struct strbuf *buf) {
@@ -80,11 +80,6 @@ void append_todo_help(unsigned keep_empty, int command_count, "the rebase will be aborted.\n\n");strbuf_add_commented_lines (buf, msg,strlen (msg)); - -if (!keep_empty) { - msg =_ ("Note that empty commits are commented out" ); -strbuf_add_commented_lines (buf, msg,strlen (msg)); - } } int edit_todo_list(struct repository *r, struct todo_list *todo_list,
rebase-interactive.h +1 -1
@@ -5,7 +5,7 @@ struct strbuf;struct repository ;struct todo_list ; -void append_todo_help (unsigned keep_empty,int command_count, +void append_todo_help (int command_count,const char *shortrevisions,const char *shortonto,struct strbuf *buf); int edit_todo_list(struct repository *r, struct todo_list *todo_list,
sequencer.c +7 -13
@@ -1489,7 +1489,7 @@ static int allow_empty(struct repository *r,struct replay_opts *opts, struct commit *commit) { - int index_unchanged, empty_commit; + int index_unchanged, originally_empty;/* * Three cases:
@@ -1513,10 +1513,10 @@ static int allow_empty(struct repository *r,if (opts->keep_redundant_commits)return 1 ; - empty_commit =is_original_commit_empty (commit); -if (empty_commit <0 ) -return empty_commit; -if (!empty_commit) + originally_empty =is_original_commit_empty (commit); +if (originally_empty <0 ) +return originally_empty; +if (!originally_empty)return 0 ;else return 1 ;
@@ -4566,7 +4566,6 @@ static int make_script_with_merges(struct pretty_print_context *pp, struct rev_info *revs, struct strbuf *out, unsigned flags) { - int keep_empty = flags & TODO_LIST_KEEP_EMPTY; int rebase_cousins = flags & TODO_LIST_REBASE_COUSINS;int root_with_onto = flags &TODO_LIST_ROOT_WITH_ONTO ;struct strbuf buf =STRBUF_INIT , oneline =STRBUF_INIT ;
@@ -4629,8 +4628,6 @@ static int make_script_with_merges(struct pretty_print_context *pp,if (!to_merge) {/* non-merge commit: easy case */ strbuf_reset (&buf); -if (!keep_empty && is_empty) -strbuf_addf (&buf,"%c " , comment_line_char);strbuf_addf (&buf,"%s %s %s" , cmd_pick,oid_to_hex (&commit->object.oid), oneline.buf);
@@ -4797,7 +4794,6 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,struct pretty_print_context pp = {0 };struct rev_info revs;struct commit *commit; -int keep_empty = flags &TODO_LIST_KEEP_EMPTY ;const char *insn = flags &TODO_LIST_ABBREVIATE_CMDS ?"p" :"pick" ;int rebase_merges = flags &TODO_LIST_REBASE_MERGES ;
@@ -4833,12 +4829,10 @@ int sequencer_make_script(struct repository *r, struct strbuf *out, int argc,return make_script_with_merges (&pp, &revs, out, flags);while ((commit =get_revision (&revs))) { -int is_empty =is_original_commit_empty (commit); +int is_empty =is_original_commit_empty (commit);if (!is_empty && (commit->object.flags &PATCHSAME ))continue ; -if (!keep_empty && is_empty) -strbuf_addf (out,"%c " , comment_line_char);strbuf_addf (out,"%s %s " , insn,oid_to_hex (&commit->object.oid));pretty_print_commit (&pp, commit, out);
@@ -4975,7 +4969,7 @@ int todo_list_write_to_file(struct repository *r, struct todo_list *todo_list,todo_list_to_strbuf (r, todo_list, &buf, num, flags);if (flags &TODO_LIST_APPEND_TODO_HELP ) -append_todo_help (flags &TODO_LIST_KEEP_EMPTY ,count_commands (todo_list), +append_todo_help (count_commands (todo_list), shortrevisions, shortonto, &buf); res =write_message (buf.buf, buf.len, file,0 );
sequencer.h +1 -1
@@ -132,7 +132,7 @@ int sequencer_rollback(struct repository *repo, struct replay_opts *opts);int sequencer_skip (struct repository *repo,struct replay_opts *opts);int sequencer_remove_state (struct replay_opts *opts); -#define TODO_LIST_KEEP_EMPTY (1U << 0) +/* #define TODO_LIST_KEEP_EMPTY (1U << 0) */ /* No longer used */ #define TODO_LIST_SHORTEN_IDS (1U << 1)#define TODO_LIST_ABBREVIATE_CMDS (1U << 2)#define TODO_LIST_REBASE_MERGES (1U << 3)
t/t3421-rebase-topology-linear.sh +8 -8
@@ -205,17 +205,17 @@ test_expect_success 'setup of linear history for empty commit tests' ' test_run_rebase () { result=$1 shift - test_expect_$result "rebase $* drops empty commit" " + test_expect_$result "rebase $* keeps begin-empty commits" " reset_rebase && - git rebase $* c l && - test_cmp_rev c HEAD~2 && - test_linear_range 'd l' c.. + git rebase $* j l && + test_cmp_rev c HEAD~4 && + test_linear_range 'j d k l' c.. " } -test_run_rebase success '' +test_run_rebase failure '' test_run_rebase success -m test_run_rebase success -i -test_have_prereq !REBASE_P || test_run_rebase success -p +test_have_prereq !REBASE_P || test_run_rebase failure -p test_run_rebase () { result=$1
@@ -230,7 +230,7 @@ test_run_rebase () { test_run_rebase success '' test_run_rebase success -m test_run_rebase success -i -test_have_prereq !REBASE_P || test_run_rebase failure -p +test_have_prereq !REBASE_P || test_run_rebase success -p test_run_rebase () { result=$1
@@ -245,7 +245,7 @@ test_run_rebase () { test_run_rebase success '' test_run_rebase success -m test_run_rebase success -i -test_have_prereq !REBASE_P || test_run_rebase failure -p +test_have_prereq !REBASE_P || test_run_rebase success -p test_run_rebase success --rebase-merges # m
t/t3424-rebase-empty.sh new file +70 -0
70 lines not on this page · raw message
t/t3427-rebase-subtree.sh +6 -6
@@ -85,23 +85,23 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges -- verbose test "$(commit_message HEAD)" = "Empty commit" ' -test_expect_success 'Rebase -Xsubtree --keep-empty --onto commit' ' +test_expect_success 'Rebase -Xsubtree --onto commit' ' reset_rebase && git checkout -b rebase-onto to-rebase && - test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --onto files-master master && + test_must_fail git rebase -Xsubtree=files_subtree --onto files-master master && : first pick results in no changes && - git rebase --continue && + git rebase --skip && verbose test "$(commit_message HEAD~2)" = "master4" && verbose test "$(commit_message HEAD~)" = "files_subtree/master5" && verbose test "$(commit_message HEAD)" = "Empty commit" ' -test_expect_success 'Rebase -Xsubtree --keep-empty --rebase-merges --onto commit' ' +test_expect_success 'Rebase -Xsubtree --rebase-merges --onto commit' ' reset_rebase && git checkout -b rebase-merges-onto to-rebase && - test_must_fail git rebase -Xsubtree=files_subtree --keep-empty --rebase-merges --onto files-master --root && + test_must_fail git rebase -Xsubtree=files_subtree --rebase-merges --onto files-master --root && : first pick results in no changes && - git rebase --continue && + git rebase --skip && verbose test "$(commit_message HEAD~2)" = "master4" && verbose test "$(commit_message HEAD~)" = "files_subtree/master5" && verbose test "$(commit_message HEAD)" = "Empty commit"
Signature · 1 line
gitgitgadget
PATCH v5 4/20 rebase (interactive-backend): fix handling of commits that become empty
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
As established in the previous commit and commit b00bf1c9a8dd (git-rebase: make --allow-empty-message the default, 2018-06-27), the behavior for rebase with different backends in various edge or corner cases is often more happenstance than design. This commit addresses another such corner case: commits which "become empty".
A careful reader may note that there are two types of commits which would become empty due to a rebase:
* [clean cherry-pick] Commits which are clean cherry-picks of upstream commits, as determined by git log --cherry-mark .... Re-applying these commits would result in an empty set of changes and a duplicative commit message; i.e. these are commits that have "already been applied" upstream.
* [become empty] Commits which are not empty to start, are not clean cherry-picks of upstream commits, but which still become empty after being rebased. This happens e.g. when a commit has changes which are a strict subset of the changes in an upstream commit, or when the changes of a commit can be found spread across or among several upstream commits.
Clearly, in both cases the changes in the commit in question are found upstream already, but the commit message may not be in the latter case.
When cherry-mark can determine a commit is already upstream, then because of how cherry-mark works this means the upstream commit message was about the *exact* same set of changes. Thus, the commit messages can be assumed to be fully interchangeable (and are in fact likely to be completely identical). As such, the clean cherry-pick case represents a case when there is no information to be gained by keeping the extra commit around. All rebase types have always dropped these commits, and no one to my knowledge has ever requested that we do otherwise.
For many of the become empty cases (and likely even most), we will also be able to drop the commit without loss of information -- but this isn't quite always the case. Since these commits represent cases that were not clean cherry-picks, there is no upstream commit message explaining the same set of changes. Projects with good commit message hygiene will likely have the explanation from our commit message contained within or spread among the relevant upstream commits, but not all projects run that way. As such, the commit message of the commit being rebased may have reasoning that suggests additional changes that should be made to adapt to the new base, or it may have information that someone wants to add as a note to another commit, or perhaps someone even wants to create an empty commit with the commit message as-is.
Junio commented on the "become-empty" types of commits as follows[1]:
WRT a change that ends up being empty (as opposed to a change that is empty from the beginning), I'd think that the current behaviour is desireable one. "am" based rebase is solely to transplant an existing history and want to stop much less than "interactive" one whose purpose is to polish a series before making it publishable, and asking for confirmation ("this has become empty--do you want to drop it?") is more appropriate from the workflow point of view.
[1] https://lore.kernel.org/git/[email protected]/
I would simply add that his arguments for "am"-based rebases actually apply to all non-explicitly-interactive rebases. Also, since we are stating that different cases should have different defaults, it may be worth providing a flag to allow users to select which behavior they want for these commits.
Introduce a new command line flag for selecting the desired behavior:
--empty={drop,keep,ask}with the definitions:
drop: drop commits which become empty
keep: keep commits which become empty
ask: provide the user a chance to interact and pick what to do with
commits which become empty on a case-by-case basis
In line with Junio's suggestion, if the --empty flag is not specified, pick defaults as follows:
explicitly interactive: ask
otherwise: drop
--- Documentation/git-rebase.txt | 29 ++++++++++++++-- builtin/rebase.c | 52 ++++++++++++++++++++++++++++ sequencer.c | 50 +++++++++++++++++++++------ sequencer.h | 1 + t/t3424-rebase-empty.sh | 66 +++++++++++++++++++++++++++++++++--- t/t3427-rebase-subtree.sh | 8 ++--- 6 files changed, 183 insertions(+), 23 deletions(-)
Documentation/git-rebase.txt +26 -3
@@ -258,6 +258,24 @@ See also INCOMPATIBLE OPTIONS below. original branch. The index and working tree are also left unchanged as a result. +--empty={drop,keep,ask}:: + How to handle commits that are not empty to start and are not + clean cherry-picks of any upstream commit, but which become + empty after rebasing (because they contain a subset of already + upstream changes). With drop (the default), commits that + become empty are dropped. With keep, such commits are kept. + With ask (implied by --interactive), the rebase will halt when + an empty commit is applied allowing you to choose whether to + drop it, edit files more, or just commit the empty changes. + Other options, like --exec, will use the default of drop unless + -i/--interactive is explicitly specified. ++ +Note that commits which start empty are kept, and commits which are +clean cherry-picks (as determined by `git log --cherry-mark ...`) are +always dropped. ++ +See also INCOMPATIBLE OPTIONS below. + --keep-empty:: No-op. Rebasing commits that started empty (had no change relative to their parent) used to fail and this option would
@@ -561,6 +579,7 @@ are incompatible with the following options: * --interactive * --exec * --keep-empty + * --empty= * --edit-todo * --root when used in combination with --onto
@@ -569,6 +588,7 @@ In addition, the following pairs of options are incompatible: * --preserve-merges and --interactive * --preserve-merges and --signoff * --preserve-merges and --rebase-merges + * --preserve-merges and --empty= * --keep-base and --onto * --keep-base and --root
@@ -585,9 +605,12 @@ commits that started empty, though these are rare in practice. It also drops commits that become empty and has no option for controlling this behavior. -The interactive backend keeps intentionally empty commits. -Unfortunately, it always halts whenever it runs across a commit that -becomes empty, even when the rebase is not explicitly interactive. +The interactive backend keeps intentionally empty commits. Similar to +the am backend, by default the interactive backend drops commits that +become empty unless -i/--interactive is specified (in which case it +stops and asks the user what to do). The interactive backend also has +an --empty={drop,keep,ask} option for changing the behavior of +handling commits that become empty. Directory rename detection ~~~~~~~~~~~~~~~~~~~~~~~~~~
builtin/rebase.c +52 -0
102 lines not on this page · raw message
sequencer.c +39 -11
108 lines not on this page · raw message
sequencer.h +1 -0
@@ -39,6 +39,7 @@ struct replay_opts {int allow_rerere_auto;int allow_empty;int allow_empty_message; +int drop_redundant_commits;int keep_redundant_commits;int verbose;int quiet;
t/t3424-rebase-empty.sh +61 -5
85 lines not on this page · raw message
t/t3427-rebase-subtree.sh +4 -4
@@ -85,10 +85,10 @@ test_expect_failure REBASE_P 'Rebase -Xsubtree --keep-empty --preserve-merges -- verbose test "$(commit_message HEAD)" = "Empty commit" ' -test_expect_success 'Rebase -Xsubtree --onto commit' ' +test_expect_success 'Rebase -Xsubtree --empty=ask --onto commit' ' reset_rebase && git checkout -b rebase-onto to-rebase && - test_must_fail git rebase -Xsubtree=files_subtree --onto files-master master && + test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --onto files-master master && : first pick results in no changes && git rebase --skip && verbose test "$(commit_message HEAD~2)" = "master4" &&
@@ -96,10 +96,10 @@ test_expect_success 'Rebase -Xsubtree --onto commit' ' verbose test "$(commit_message HEAD)" = "Empty commit" ' -test_expect_success 'Rebase -Xsubtree --rebase-merges --onto commit' ' +test_expect_success 'Rebase -Xsubtree --empty=ask --rebase-merges --onto commit' ' reset_rebase && git checkout -b rebase-merges-onto to-rebase && - test_must_fail git rebase -Xsubtree=files_subtree --rebase-merges --onto files-master --root && + test_must_fail git rebase -Xsubtree=files_subtree --empty=ask --rebase-merges --onto files-master --root && : first pick results in no changes && git rebase --skip && verbose test "$(commit_message HEAD~2)" = "master4" &&
Signature · 1 line
gitgitgadget
PATCH v5 5/20 t3406: simplify an already simple test
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
When the merge backend was re-implemented on top of the interactive backend, the output of rebase --merge changed a little. This change allowed this test to be simplified, though it wasn't noticed until now. Simplify the testcase a little.
--- t/t3406-rebase-message.sh | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-)
t/t3406-rebase-message.sh +2 -5
@@ -18,11 +18,8 @@ test_expect_success 'setup' ' ' test_expect_success 'rebase -m' ' - git rebase -m master >report && - >expect && - sed -n -e "/^Already applied: /p" \ - -e "/^Committed: /p" report >actual && - test_cmp expect actual + git rebase -m master >actual && + test_must_be_empty actual ' test_expect_success 'rebase against master twice' '
Signature · 1 line
gitgitgadget
PATCH v5 6/20 rebase, sequencer: remove the broken GIT_QUIET handling
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
The GIT_QUIET environment variable was used to signal the non-am backends that the rebase should perform quietly. The preserve-merges backend does not make use of the quiet flag anywhere (other than to write out its state whenever it writes state), and this mechanism was broken in the conversion from shell to C. Since this environment variable was specifically designed for scripts and the only backend that would still use it is no longer a script, just gut this code.
A subsequent commit will fix --quiet for the interactive/merge backend in a different way.
--- builtin/rebase.c | 6 ++---- sequencer.c | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-)
builtin/rebase.c +2 -4
@@ -699,8 +699,8 @@ static int rebase_write_basic_state(struct rebase_options *opts) opts->onto ? oid_to_hex(&opts->onto->object.oid) : "");write_file (state_dir_path ("orig-head" , opts),"%s" ,oid_to_hex (&opts->orig_head)); -write_file (state_dir_path ("quiet" , opts),"%s" , - opts->flags &REBASE_NO_QUIET ?"" :"t" ); +if (!(opts->flags &REBASE_NO_QUIET )) +write_file (state_dir_path ("quiet" , opts),"%s" ,"" );if (opts->flags &REBASE_VERBOSE )write_file (state_dir_path ("verbose" , opts),"%s" ,"" );if (opts->strategy)
@@ -1153,8 +1153,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)add_var (&script_snippet,"revisions" , opts->revisions);add_var (&script_snippet,"restrict_revision" , opts->restrict_revision ?oid_to_hex (&opts->restrict_revision->object.oid) :NULL ); -add_var (&script_snippet,"GIT_QUIET" , - opts->flags &REBASE_NO_QUIET ?"" :"t" );sq_quote_argv_pretty (&buf, opts->git_am_opts.argv);add_var (&script_snippet,"git_am_opt" , buf.buf);strbuf_release (&buf);
sequencer.c +2 -4
@@ -2570,8 +2570,6 @@ static void write_strategy_opts(struct replay_opts *opts)int write_basic_state (struct replay_opts *opts,const char *head_name,struct commit *onto,const char *orig_head) { -const char *quiet =getenv ("GIT_QUIET" ); -if (head_name)write_file (rebase_path_head_name (),"%s\n" , head_name);if (onto)
@@ -2580,8 +2578,8 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,if (orig_head)write_file (rebase_path_orig_head (),"%s\n" , orig_head); -if (quiet) -write_file (rebase_path_quiet (),"%s\n" , quiet); +if (opts->quiet) +write_file (rebase_path_quiet (),"%s" ,"" );if (opts->verbose)write_file (rebase_path_verbose (),"%s" ,"" );if (opts->strategy)
Signature · 1 line
gitgitgadget
PATCH v5 7/20 rebase: make sure to pass along the quiet flag to the sequencer
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
--- builtin/rebase.c | 3 ++- t/t3400-rebase.sh | 8 +++++++- 2 files changed, 9 insertions(+), 2 deletions(-)
builtin/rebase.c +2 -1
@@ -120,6 +120,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts) replay.allow_empty_message = opts->allow_empty_message; replay.drop_redundant_commits = (opts->empty ==EMPTY_DROP ); replay.keep_redundant_commits = (opts->empty ==EMPTY_KEEP ); + replay.quiet = !(opts->flags &REBASE_NO_QUIET ); replay.verbose = opts->flags &REBASE_VERBOSE ; replay.reschedule_failed_exec = opts->reschedule_failed_exec; replay.gpg_sign =xstrdup_or_null (opts->gpg_sign_opt);
@@ -1476,7 +1477,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("allow pre-rebase hook to run")),OPT_NEGBIT ('q' ,"quiet" , &options.flags,N_ ("be quiet. implies --no-stat" ), -REBASE_NO_QUIET |REBASE_VERBOSE |REBASE_DIFFSTAT ), +REBASE_NO_QUIET |REBASE_VERBOSE |REBASE_DIFFSTAT ),OPT_BIT ('v' ,"verbose" , &options.flags,N_ ("display a diffstat of what changed upstream" ), REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT),
t/t3400-rebase.sh +7 -1
@@ -206,12 +206,18 @@ test_expect_success 'cherry-picked commits and fork-point work together' ' test_cmp expect D ' -test_expect_success 'rebase -q is quiet' ' +test_expect_success 'rebase --am -q is quiet' ' git checkout -b quiet topic && git rebase -q master >output.out 2>&1 && test_must_be_empty output.out ' +test_expect_success 'rebase --merge -q is quiet' ' + git checkout -B quiet topic && + git rebase --merge -q master >output.out 2>&1 && + test_must_be_empty output.out +' + test_expect_success 'Rebase a commit that sprinkles CRs in' ' ( echo "One" &&
Signature · 1 line
gitgitgadget
PATCH v5 8/20 rebase: fix handling of restrict_revision
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
restrict_revision in the original shell script was an excluded revision range. It is also treated that way by the am-backend. In the conversion from shell to C (see commit 6ab54d17be3f ("rebase -i:
implement the logic to initialize $revisions in C", 2018-08-28)), the interactive-backend accidentally treated it as a positive revision rather than a negated one.
This was missed as there were no tests in the testsuite that tested an interactive rebase with fork-point behavior.
--- builtin/rebase.c | 4 ++-- t/t3400-rebase.sh | 20 +++++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-)
builtin/rebase.c +2 -2
@@ -348,8 +348,8 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags)argv_array_pushl (&make_script_args,"" , revisions,NULL );if (opts->restrict_revision) -argv_array_push (&make_script_args, -oid_to_hex (&opts->restrict_revision->object.oid)); +argv_array_pushf (&make_script_args,"^%s" , +oid_to_hex (&opts->restrict_revision->object.oid)); ret = sequencer_make_script(the_repository, &todo_list.buf, make_script_args.argc, make_script_args.argv,
t/t3400-rebase.sh +19 -1
@@ -165,11 +165,29 @@ test_expect_success 'rebase works with format.useAutoBase' ' git rebase master ' -test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' ' +test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--merge)' ' git checkout -b default-base master && git checkout -b default topic && git config branch.default.remote . && git config branch.default.merge refs/heads/default-base && + git rebase --merge && + git rev-parse --verify default-base >expect && + git rev-parse default~1 >actual && + test_cmp expect actual && + git checkout default-base && + git reset --hard HEAD^ && + git checkout default && + git rebase --merge && + git rev-parse --verify default-base >expect && + git rev-parse default~1 >actual && + test_cmp expect actual +' + +test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' ' + git checkout -B default-base master && + git checkout -B default topic && + git config branch.default.remote . && + git config branch.default.merge refs/heads/default-base && git rebase && git rev-parse --verify default-base >expect && git rev-parse default~1 >actual &&
Signature · 1 line
gitgitgadget
PATCH v5 9/20 t3432: make these tests work with either am or merge backends
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
t3432 had several stress tests for can_fast_forward(), whose intent was to ensure we were using the optimization of just fast forwarding when possible. However, these tests verified that fast forwards had happened based on the output that rebase printed to the terminal. We can instead test more directly that we actually fast-forwarded by checking the reflog, which also has the side effect of making the tests applicable for the merge/interactive backend.
This change does lose the distinction between "noop" and "noop-force", but as stated in commit c9efc216830f ("t3432: test for --no-ff's interaction with fast-forward", 2019-08-27) which introduced that
distinction: "These tests aren't supposed to endorse the status quo, just test for what we're currently doing.".
This change does not actually run these tests with the merge/interactive backend; instead this is just a preparatory commit. A subsequent commit which fixes can_fast_forward() to work with that backend will then also change t3432 to add tests of that backend as well.
--- t/t3432-rebase-fast-forward.sh | 48 ++++++++++++++++------------------ 1 file changed, 22 insertions(+), 26 deletions(-)
t/t3432-rebase-fast-forward.sh +22 -26
@@ -44,19 +44,15 @@ test_rebase_same_head_ () { test_expect_$status "git rebase$flag $* with $changes is $what with $cmp HEAD" " oldhead=\$(git rev-parse HEAD) && test_when_finished 'git reset --hard \$oldhead' && + cp .git/logs/HEAD expect && git rebase$flag $* >stdout && if test $what = work then - # Must check this case first, for 'is up to - # date, rebase forced[...]rewinding head' cases - test_i18ngrep 'rewinding head' stdout + old=\$(wc -l <expect) && + test_line_count '-gt' \$old .git/logs/HEAD elif test $what = noop then - test_i18ngrep 'is up to date' stdout && - test_i18ngrep ! 'rebase forced' stdout - elif test $what = noop-force - then - test_i18ngrep 'is up to date, rebase forced' stdout + test_cmp expect .git/logs/HEAD fi && newhead=\$(git rev-parse HEAD) && if test $cmp = same
@@ -71,14 +67,14 @@ test_rebase_same_head_ () { changes='no changes' test_rebase_same_head success noop same success work same -test_rebase_same_head success noop same success noop-force same master -test_rebase_same_head success noop same success noop-force diff --onto B B -test_rebase_same_head success noop same success noop-force diff --onto B... B -test_rebase_same_head success noop same success noop-force same --onto master... master -test_rebase_same_head success noop same success noop-force same --keep-base master -test_rebase_same_head success noop same success noop-force same --keep-base -test_rebase_same_head success noop same success noop-force same --no-fork-point -test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point +test_rebase_same_head success noop same success work same master +test_rebase_same_head success noop same success work diff --onto B B +test_rebase_same_head success noop same success work diff --onto B... B +test_rebase_same_head success noop same success work same --onto master... master +test_rebase_same_head success noop same success work same --keep-base master +test_rebase_same_head success noop same success work same --keep-base +test_rebase_same_head success noop same success work same --no-fork-point +test_rebase_same_head success noop same success work same --keep-base --no-fork-point test_rebase_same_head success noop same success work same --fork-point master test_rebase_same_head success noop same success work diff --fork-point --onto B B test_rebase_same_head success noop same success work diff --fork-point --onto B... B
@@ -91,14 +87,14 @@ test_expect_success 'add work same to side' ' changes='our changes' test_rebase_same_head success noop same success work same -test_rebase_same_head success noop same success noop-force same master -test_rebase_same_head success noop same success noop-force diff --onto B B -test_rebase_same_head success noop same success noop-force diff --onto B... B -test_rebase_same_head success noop same success noop-force same --onto master... master -test_rebase_same_head success noop same success noop-force same --keep-base master -test_rebase_same_head success noop same success noop-force same --keep-base -test_rebase_same_head success noop same success noop-force same --no-fork-point -test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point +test_rebase_same_head success noop same success work same master +test_rebase_same_head success noop same success work diff --onto B B +test_rebase_same_head success noop same success work diff --onto B... B +test_rebase_same_head success noop same success work same --onto master... master +test_rebase_same_head success noop same success work same --keep-base master +test_rebase_same_head success noop same success work same --keep-base +test_rebase_same_head success noop same success work same --no-fork-point +test_rebase_same_head success noop same success work same --keep-base --no-fork-point test_rebase_same_head success noop same success work same --fork-point master test_rebase_same_head success noop same success work diff --fork-point --onto B B test_rebase_same_head success noop same success work diff --fork-point --onto B... B
@@ -112,8 +108,8 @@ test_expect_success 'add work same to upstream' ' ' changes='our and their changes' -test_rebase_same_head success noop same success noop-force diff --onto B B -test_rebase_same_head success noop same success noop-force diff --onto B... B +test_rebase_same_head success noop same success work diff --onto B B +test_rebase_same_head success noop same success work diff --onto B... B test_rebase_same_head success noop same success work diff --onto master... master test_rebase_same_head success noop same success work diff --keep-base master test_rebase_same_head success noop same success work diff --keep-base
Signature · 1 line
gitgitgadget
PATCH v5 10/20 rebase: allow more types of rebases to fast-forward
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
In the past, we dis-allowed rebases using the interactive backend from performing a fast-forward to short-circuit the rebase operation. This made sense for explicitly interactive rebases and some implicitly interactive rebases, but certainly became overly stringent when the merge backend was re-implemented via the interactive backend.
Just as the am-based rebase has always had to disable the fast-forward based on a variety of conditions or flags (e.g. --signoff, --whitespace, etc.), we need to do the same but now with a few more options. However, continuing to use REBASE_FORCE for tracking this is problematic because the interactive backend used it for a different purpose. (When REBASE_FORCE wasn't set, the interactive backend would not fast-forward the whole series but would fast-forward individual "pick" commits at the beginning of the todo list, and then a squash or something would cause it to start generating new commits.) So, introduce a new allow_preemptive_ff flag contained within cmd_rebase() and use it to track whether we are going to allow a pre-emptive fast-forward that short-circuits the whole rebase.
--- builtin/rebase.c | 18 ++++++++++++++---- t/t3432-rebase-fast-forward.sh | 2 ++ 2 files changed, 16 insertions(+), 4 deletions(-)
builtin/rebase.c +14 -4
@@ -1467,6 +1467,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)struct object_id squash_onto;char *squash_onto_name =NULL ;int reschedule_failed_exec =-1 ; +int allow_preemptive_ff =1 ;struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"),
@@ -1774,13 +1775,20 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) state_dir_base, cmd_live_rebase, buf.buf); } +if ((options.flags &REBASE_INTERACTIVE_EXPLICIT ) || + (action !=ACTION_NONE ) || + (exec.nr >0 ) || + options.autosquash) { + allow_preemptive_ff =0 ; + } +for (i =0 ; i < options.git_am_opts.argc; i++) {const char *option = options.git_am_opts.argv[i], *p;if (!strcmp (option,"--committer-date-is-author-date" ) || !strcmp (option,"--ignore-date" ) || !strcmp (option,"--whitespace=fix" ) || !strcmp (option,"--whitespace=strip" )) - options.flags |=REBASE_FORCE ; + allow_preemptive_ff =0 ;else if (skip_prefix (option,"-C" , &p)) {while (*p)if (!isdigit (*(p++)))
@@ -2116,12 +2124,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)/* * Check if we are already based on onto with linear history, * in which case we could fast-forward without replacing the commits -* with new commits recreated by replaying their changes. This -* optimization must not be done if this is an interactive rebase. +* with new commits recreated by replaying their changes. +* +* Note that can_fast_forward() initializes merge_base, so we have to +* call it before checking allow_preemptive_ff. */ if (can_fast_forward (options.onto, options.upstream, options.restrict_revision, &options.orig_head, &merge_base) && - !is_interactive (&options)) { + allow_preemptive_ff) {int flag;if (!(options.flags &REBASE_FORCE )) {
t/t3432-rebase-fast-forward.sh +2 -0
@@ -30,6 +30,8 @@ test_rebase_same_head () { shift && test_rebase_same_head_ $status_n $what_n $cmp_n "" "$*" && test_rebase_same_head_ $status_f $what_f $cmp_f " --no-ff" "$*" + test_rebase_same_head_ $status_n $what_n $cmp_n " --merge" "$*" && + test_rebase_same_head_ $status_f $what_f $cmp_f " --merge --no-ff" "$*" } test_rebase_same_head_ () {
Signature · 1 line
gitgitgadget
PATCH v5 11/20 git-rebase.txt: add more details about behavioral differences of backends
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
--- Documentation/git-rebase.txt | 85 +++++++++++++++++++++++++++++++++--- 1 file changed, 80 insertions(+), 5 deletions(-)
Documentation/git-rebase.txt +80 -5
@@ -416,7 +416,7 @@ with `--keep-base` in order to drop those commits from your branch. --ignore-whitespace:: --whitespace=<option>:: - These flag are passed to the 'git apply' program + These flags are passed to the 'git apply' program (see linkgit:git-apply[1]) that applies the patch. + See also INCOMPATIBLE OPTIONS below.
@@ -563,8 +563,8 @@ The following options: * --committer-date-is-author-date * --ignore-date - * --whitespace * --ignore-whitespace + * --whitespace * -C are incompatible with the following options:
@@ -615,9 +615,84 @@ handling commits that become empty. Directory rename detection ~~~~~~~~~~~~~~~~~~~~~~~~~~ -Directory rename heuristics are enabled in the merge and interactive -backends. Due to the lack of accurate tree information, directory -rename detection is disabled in the am backend. +Due to the lack of accurate tree information (arising from +constructing fake ancestors with the limited information available in +patches), directory rename detection is disabled in the am backend. +Disabled directory rename detection means that if one side of history +renames a directory and the other adds new files to the old directory, +then the new files will be left behind in the old directory without +any warning at the time of rebasing that you may want to move these +files into the new directory. + +Directory rename detection works with the merge and interactive +backends to provide you warnings in such cases. + +Context +~~~~~~~ + +The am backend works by creating a sequence of patches (by calling +`format-patch` internally), and then applying the patches in sequence +(calling `am` internally). Patches are composed of multiple hunks, +each with line numbers, a context region, and the actual changes. The +line numbers have to be taken with some fuzz, since the other side +will likely have inserted or deleted lines earlier in the file. The +context region is meant to help find how to adjust the line numbers in +order to apply the changes to the right lines. However, if multiple +areas of the code have the same surrounding lines of context, the +wrong one can be picked. There are real-world cases where this has +caused commits to be reapplied incorrectly with no conflicts reported. +Setting diff.context to a larger value may prevent such types of +problems, but increases the chance of spurious conflicts (since it +will require more lines of matching context to apply). + +The interactive backend works with a full copy of each relevant file, +insulating it from these types of problems. + +Labelling of conflicts markers +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When there are content conflicts, the merge machinery tries to +annotate each side's conflict markers with the commits where the +content came from. Since the am backend drops the original +information about the rebased commits and their parents (and instead +generates new fake commits based off limited information in the +generated patches), those commits cannot be identified; instead it has +to fall back to a commit summary. Also, when merge.conflictStyle is +set to diff3, the am backend will use "constructed merge base" to +label the content from the merge base, and thus provide no information +about the merge base commit whatsoever. + +The interactive backend works with the full commits on both sides of +history and thus has no such limitations. + +Hooks +~~~~~ + +The am backend has not traditionally called the post-commit hook, +while the merge/interactive backend has. However, this was by +accident of implementation rather than by design. Both backends +should have the same behavior, though it is not clear which one is +correct. + +Miscellaneous differences +~~~~~~~~~~~~~~~~~~~~~~~~~ + +There are a few more behavioral differences that most folks would +probably consider inconsequential but which are mentioned for +completeness: + +* Reflog: The two backends will use different wording when describing + the changes made in the reflog, though both will make use of the + word "rebase". + +* Progress, informational, and error messages: The two backends + provide slightly different progress and informational messages. + Also, the am backend writes error messages (such as "Your files + would be overwritten...") to stdout, while the interactive backend + writes them to stderr. + +* State directories: The two backends keep their state in different + directories under .git/ include::merge-strategies.txt[]
Signature · 1 line
gitgitgadget
PATCH v5 12/20 rebase: move incompatibility checks between backend options a bit earlier
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
--- builtin/rebase.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-)
builtin/rebase.c +11 -11
@@ -1878,6 +1878,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)if (isatty (2 ) && options.flags &REBASE_NO_QUIET )strbuf_addstr (&options.git_format_patch_opt," --progress" ); +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--) +if (strcmp (options.git_am_opts.argv[i],"-q" )) +break ; + +if (is_interactive (&options) && i >=0 ) +die (_ ("cannot combine am options with either " +"interactive or merge options" )); + } +switch (options.type) {case REBASE_MERGE :case REBASE_INTERACTIVE :
@@ -1908,17 +1919,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)if (reschedule_failed_exec >=0 ) options.reschedule_failed_exec = reschedule_failed_exec; -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--) -if (strcmp (options.git_am_opts.argv[i],"-q" )) -break ; - -if (is_interactive (&options) && i >=0 ) -die (_ ("cannot combine am options with either " -"interactive or merge options" )); - } -if (options.signoff) {if (options.type ==REBASE_PRESERVE_MERGES ) die("cannot combine '--signoff' with "
Signature · 1 line
gitgitgadget
PATCH v5 13/20 rebase: add an --am option
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
Currently, this option doesn't do anything except error out if any options requiring the interactive-backend are also passed. However, when we make the default backend configurable later in this series, this flag will provide a way to override the config setting.
--- Documentation/git-rebase.txt | 11 ++++++++++- builtin/rebase.c | 18 +++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-)
Documentation/git-rebase.txt +10 -1
@@ -258,6 +258,13 @@ See also INCOMPATIBLE OPTIONS below. original branch. The index and working tree are also left unchanged as a result. +--am: + Use git-am internally to rebase. This option may become a + no-op in the future once the interactive backend handles + everything the am one does. ++ +See also INCOMPATIBLE OPTIONS below. + --empty={drop,keep,ask}:: How to handle commits that are not empty to start and are not clean cherry-picks of any upstream commit, but which become
@@ -378,7 +385,7 @@ See also INCOMPATIBLE OPTIONS below. Ensure at least <n> lines of surrounding context match before and after each change. When fewer lines of surrounding context exist they all must match. By default no context is - ever ignored. + ever ignored. Implies --am. + See also INCOMPATIBLE OPTIONS below.
@@ -418,6 +425,7 @@ with `--keep-base` in order to drop those commits from your branch. --whitespace=<option>:: These flags are passed to the 'git apply' program (see linkgit:git-apply[1]) that applies the patch. + Implies --am. + See also INCOMPATIBLE OPTIONS below.
@@ -561,6 +569,7 @@ INCOMPATIBLE OPTIONS The following options: + * --am * --committer-date-is-author-date * --ignore-date * --ignore-whitespace
builtin/rebase.c +17 -1
@@ -1335,6 +1335,18 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,return res &&is_linear_history (onto, head); } +static int parse_opt_am (const struct option *opt,const char *arg,int unset) +{ +struct rebase_options *opts = opt->value; + +BUG_ON_OPT_NEG (unset); +BUG_ON_OPT_ARG (arg); + + opts->type =REBASE_AM ; + +return 0 ; +} +/* -i followed by -m is still -i */ static int parse_opt_merge (const struct option *opt,const char *arg,int unset) {
@@ -1519,6 +1531,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)OPT_CMDMODE (0 ,"show-current-patch" , &action,N_ ("show the patch file being applied or merged" ),ACTION_SHOW_CURRENT_PATCH ), + { OPTION_CALLBACK, 0, "am", &options, NULL, +N_ ("use apply-mail strategies to rebase" ), +PARSE_OPT_NOARG |PARSE_OPT_NONEG , + parse_opt_am }, { OPTION_CALLBACK, 'm', "merge", &options, NULL,N_ ("use merging strategies to rebase" ), PARSE_OPT_NOARG | PARSE_OPT_NONEG,
@@ -1878,7 +1894,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)if (isatty (2 ) && options.flags &REBASE_NO_QUIET )strbuf_addstr (&options.git_format_patch_opt," --progress" ); -if (options.git_am_opts.argc) { +if (options.git_am_opts.argc || options.type ==REBASE_AM ) {/* all am options except -q are compatible only with --am */ for (i = options.git_am_opts.argc -1 ; i >=0 ; i--)if (strcmp (options.git_am_opts.argv[i],"-q" ))
Signature · 1 line
gitgitgadget
PATCH v5 14/20 git-prompt: change the prompt for interactive-based rebases
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
In the past, we had different prompts for different types of rebases:
REBASE: for am-based rebases
REBASE-m: for merge-based rebases
REBASE-i: for interactive-based rebases
It's not clear why this distinction was necessary or helpful; when the prompt was added in commit e75201963f67 ("Improve bash prompt to detect various states like an unfinished merge", 2007-09-30), it simply added these three different types. Perhaps there was a useful purpose back then, but there have been some changes:
* The merge backend was deleted after being implemented on top of the interactive backend, causing the prompt for merge-based rebases to change from REBASE-m to REBASE-i.
* The interactive backend is used for multiple different types of non-interactive rebases, so the "-i" part of the prompt doesn't really mean what it used to.
* Rebase backends have gained more abilities and have a great deal of overlap, sometimes making it hard to distinguish them.
* Behavioral differences between the backends have also been ironed out.
* We want to change the default backend from am to interactive, which means people would get "REBASE-i" by default if we didn't change the prompt, and only if they specified --am or --whitespace or -C would they get the "REBASE" prompt.
* In the future, we plan to have "--whitespace", "-C", and even "--am" run the interactive backend once it can handle everything the am-backend can.
For all these reasons, make the prompt for any type of rebase just be "REBASE".
--- contrib/completion/git-prompt.sh | 6 +----- t/t9903-bash-prompt.sh | 8 ++++---- 2 files changed, 5 insertions(+), 9 deletions(-)
contrib/completion/git-prompt.sh +1 -5
@@ -429,11 +429,7 @@ __git_ps1 () __git_eread "$g/rebase-merge/head-name" b __git_eread "$g/rebase-merge/msgnum" step __git_eread "$g/rebase-merge/end" total - if [ -f "$g/rebase-merge/interactive" ]; then - r="|REBASE-i" - else - r="|REBASE-m" - fi + r="|REBASE" else if [ -d "$g/rebase-apply" ]; then __git_eread "$g/rebase-apply/next" step
t/t9903-bash-prompt.sh +4 -4
@@ -163,7 +163,7 @@ test_expect_success 'prompt - inside bare repository' ' ' test_expect_success 'prompt - interactive rebase' ' - printf " (b1|REBASE-i 2/3)" >expected && + printf " (b1|REBASE 2/3)" >expected && write_script fake_editor.sh <<-\EOF && echo "exec echo" >"$1" echo "edit $(git log -1 --format="%h")" >>"$1"
@@ -180,7 +180,7 @@ test_expect_success 'prompt - interactive rebase' ' ' test_expect_success 'prompt - rebase merge' ' - printf " (b2|REBASE-i 1/3)" >expected && + printf " (b2|REBASE 1/3)" >expected && git checkout b2 && test_when_finished "git checkout master" && test_must_fail git rebase --merge b1 b2 &&
@@ -189,11 +189,11 @@ test_expect_success 'prompt - rebase merge' ' test_cmp expected "$actual" ' -test_expect_success 'prompt - rebase' ' +test_expect_success 'prompt - rebase am' ' printf " (b2|REBASE 1/3)" >expected && git checkout b2 && test_when_finished "git checkout master" && - test_must_fail git rebase b1 b2 && + test_must_fail git rebase --am b1 b2 && test_when_finished "git rebase --abort" && __git_ps1 >"$actual" && test_cmp expected "$actual"
Signature · 1 line
gitgitgadget
PATCH v5 15/20 rebase: drop '-i' from the reflog for interactive-based rebases
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
A large variety of rebase types are supported by the interactive machinery, not just the explicitly interactive ones. These all share the same code and write the same reflog messages, but the "-i" moniker in those messages doesn't really have much meaning. It also becomes somewhat distracting once we switch the default from the am-backend to the interactive one. Just remove the "-i" from these messages.
--- builtin/rebase.c | 2 +- sequencer.c | 8 ++++---- t/t3404-rebase-interactive.sh | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-)
builtin/rebase.c +1 -1
@@ -1441,7 +1441,7 @@ static void set_reflog_action(struct rebase_options *options)if (env &&strcmp ("rebase" , env))return ;/* only override it if it is "rebase" */ -strbuf_addf (&buf,"rebase -i (%s)" , options->action); +strbuf_addf (&buf,"rebase (%s)" , options->action);setenv (GIT_REFLOG_ACTION_ENVIRONMENT , buf.buf,1 );strbuf_release (&buf); }
sequencer.c +4 -4
@@ -290,7 +290,7 @@ int sequencer_remove_state(struct replay_opts *opts)char *eol =strchr (p,'\n' );if (eol) *eol ='\0' ; -if (delete_ref ("(rebase -i) cleanup" , p,NULL ,0 ) <0 ) { +if (delete_ref ("(rebase) cleanup" , p,NULL ,0 ) <0 ) {warning (_ ("could not delete '%s'" ), p); ret =-1 ; }
@@ -324,7 +324,7 @@ static const char *action_name(const struct replay_opts *opts)case REPLAY_PICK :return N_ ("cherry-pick" );case REPLAY_INTERACTIVE_REBASE : -return N_ ("rebase -i" ); +return N_ ("rebase" ); }die (_ ("unknown action: %d" ), opts->action); }
@@ -628,7 +628,7 @@ static int do_recursive_merge(struct repository *r, COMMIT_LOCK | SKIP_IF_UNCHANGED))/* * TRANSLATORS: %s will be "revert", "cherry-pick" or -* "rebase -i". +* "rebase". */ return error (_ ("%s: Unable to write new index file" ),_ (action_name (opts)));
@@ -3199,7 +3199,7 @@ static int do_label(struct repository *r, const char *name, int len)return error (_ ("illegal label name: '%.*s'" ), len, name);strbuf_addf (&ref_name,"refs/rewritten/%.*s" , len, name); -strbuf_addf (&msg,"rebase -i (label) '%.*s'" , len, name); +strbuf_addf (&msg,"rebase (label) '%.*s'" , len, name); transaction =ref_store_transaction_begin (refs, &err);if (!transaction) {
t/t3404-rebase-interactive.sh +5 -5
@@ -223,7 +223,7 @@ test_expect_success 'reflog for the branch shows state before rebase' ' ' test_expect_success 'reflog for the branch shows correct finish message' ' - printf "rebase -i (finish): refs/heads/branch1 onto %s\n" \ + printf "rebase (finish): refs/heads/branch1 onto %s\n" \ "$(git rev-parse branch2)" >expected && git log -g --pretty=%gs -1 refs/heads/branch1 >actual && test_cmp expected actual
@@ -1162,10 +1162,10 @@ test_expect_success 'rebase -i produces readable reflog' ' git branch -f branch-reflog-test H && git rebase -i --onto I F branch-reflog-test && cat >expect <<-\EOF && - rebase -i (finish): returning to refs/heads/branch-reflog-test - rebase -i (pick): H - rebase -i (pick): G - rebase -i (start): checkout I + rebase (finish): returning to refs/heads/branch-reflog-test + rebase (pick): H + rebase (pick): G + rebase (start): checkout I EOF git reflog -n4 HEAD | sed "s/[^:]*: //" >actual &&
Signature · 1 line
gitgitgadget
PATCH v5 16/20 rebase tests: mark tests specific to the am-backend with --am
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
We have many rebase tests in the testsuite, and often the same test is repeated multiple times just testing different backends. For those tests that were specifically trying to test the am backend, add the --am flag.
--- t/t3400-rebase.sh | 10 ++++----- t/t3401-rebase-and-am-rename.sh | 4 ++-- t/t3404-rebase-interactive.sh | 2 +- t/t3406-rebase-message.sh | 12 +++++------ t/t3407-rebase-abort.sh | 6 +++--- t/t3420-rebase-autostash.sh | 2 +- t/t3421-rebase-topology-linear.sh | 34 +++++++++++++++---------------- t/t3425-rebase-topology-merges.sh | 8 ++++---- t/t3432-rebase-fast-forward.sh | 4 ++-- t/t5407-post-rewrite-hook.sh | 12 +++++------ t/t7512-status-help.sh | 12 +++++------ 11 files changed, 53 insertions(+), 53 deletions(-)
t/t3400-rebase.sh +5 -5
@@ -183,19 +183,19 @@ test_expect_success 'default to common base in @{upstream}s reflog if no upstrea test_cmp expect actual ' -test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg' ' +test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--am)' ' git checkout -B default-base master && git checkout -B default topic && git config branch.default.remote . && git config branch.default.merge refs/heads/default-base && - git rebase && + git rebase --am && git rev-parse --verify default-base >expect && git rev-parse default~1 >actual && test_cmp expect actual && git checkout default-base && git reset --hard HEAD^ && git checkout default && - git rebase && + git rebase --am && git rev-parse --verify default-base >expect && git rev-parse default~1 >actual && test_cmp expect actual
@@ -226,7 +226,7 @@ test_expect_success 'cherry-picked commits and fork-point work together' ' test_expect_success 'rebase --am -q is quiet' ' git checkout -b quiet topic && - git rebase -q master >output.out 2>&1 && + git rebase --am -q master >output.out 2>&1 && test_must_be_empty output.out '
@@ -325,7 +325,7 @@ test_expect_success 'rebase --am and --show-current-patch' ' echo two >>init.t && git commit -a -m two && git tag two && - test_must_fail git rebase -f --onto init HEAD^ && + test_must_fail git rebase --am -f --onto init HEAD^ && GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && grep "show.*$(git rev-parse two)" stderr )
t/t3401-rebase-and-am-rename.sh +2 -2
@@ -52,13 +52,13 @@ test_expect_success 'rebase --interactive: directory rename detected' ' ) ' -test_expect_failure 'rebase (am): directory rename detected' ' +test_expect_failure 'rebase --am: directory rename detected' ' ( cd dir-rename && git checkout B^0 && - git -c merge.directoryRenames=true rebase A && + git -c merge.directoryRenames=true rebase --am A && git ls-files -s >out && test_line_count = 5 out &&
t/t3404-rebase-interactive.sh +1 -1
@@ -1138,7 +1138,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int git checkout conflict-branch && ( set_fake_editor && - test_must_fail git rebase -f --onto HEAD~2 HEAD~ && + test_must_fail git rebase -f --am --onto HEAD~2 HEAD~ && test_must_fail git rebase --edit-todo ) && git rebase --abort
t/t3406-rebase-message.sh +6 -6
@@ -23,24 +23,24 @@ test_expect_success 'rebase -m' ' ' test_expect_success 'rebase against master twice' ' - git rebase master >out && + git rebase --am master >out && test_i18ngrep "Current branch topic is up to date" out ' test_expect_success 'rebase against master twice with --force' ' - git rebase --force-rebase master >out && + git rebase --force-rebase --am master >out && test_i18ngrep "Current branch topic is up to date, rebase forced" out ' test_expect_success 'rebase against master twice from another branch' ' git checkout topic^ && - git rebase master topic >out && + git rebase --am master topic >out && test_i18ngrep "Current branch topic is up to date" out ' test_expect_success 'rebase fast-forward to master' ' git checkout topic^ && - git rebase topic >out && + git rebase --am topic >out && test_i18ngrep "Fast-forwarded HEAD to topic" out '
@@ -89,7 +89,7 @@ test_expect_success 'GIT_REFLOG_ACTION' ' git checkout -b reflog-topic start && test_commit reflog-to-rebase && - git rebase reflog-onto && + git rebase --am reflog-onto && git log -g --format=%gs -3 >actual && cat >expect <<-\EOF && rebase finished: returning to refs/heads/reflog-topic
@@ -99,7 +99,7 @@ test_expect_success 'GIT_REFLOG_ACTION' ' test_cmp expect actual && git checkout -b reflog-prefix reflog-to-rebase && - GIT_REFLOG_ACTION=change-the-reflog git rebase reflog-onto && + GIT_REFLOG_ACTION=change-the-reflog git rebase --am reflog-onto && git log -g --format=%gs -3 >actual && cat >expect <<-\EOF && rebase finished: returning to refs/heads/reflog-prefix
t/t3407-rebase-abort.sh +3 -3
@@ -96,14 +96,14 @@ testrebase() { ' } -testrebase "" .git/rebase-apply +testrebase " --am" .git/rebase-apply testrebase " --merge" .git/rebase-merge -test_expect_success 'rebase --quit' ' +test_expect_success 'rebase --am --quit' ' cd "$work_dir" && # Clean up the state from the previous one git reset --hard pre-rebase && - test_must_fail git rebase master && + test_must_fail git rebase --am master && test_path_is_dir .git/rebase-apply && head_before=$(git rev-parse HEAD) && git rebase --quit &&
t/t3420-rebase-autostash.sh +1 -1
@@ -234,7 +234,7 @@ test_expect_success "rebase: noop rebase" ' git checkout feature-branch ' -testrebase "" .git/rebase-apply +testrebase " --am" .git/rebase-apply testrebase " --merge" .git/rebase-merge testrebase " --interactive" .git/rebase-merge
t/t3421-rebase-topology-linear.sh +17 -17
@@ -26,7 +26,7 @@ test_run_rebase () { test_linear_range 'd e' c.. " } -test_run_rebase success '' +test_run_rebase success --am test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -50,7 +50,7 @@ test_run_rebase () { test_cmp_rev e HEAD " } -test_run_rebase success '' +test_run_rebase success --am test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -66,7 +66,7 @@ test_run_rebase () { test_linear_range 'd e' b.. " } -test_run_rebase success '' +test_run_rebase success --am test_run_rebase success --fork-point test_run_rebase success -m test_run_rebase success -i
@@ -83,7 +83,7 @@ test_run_rebase () { test_linear_range 'd e' branch-b.. " } -test_run_rebase success '' +test_run_rebase success --am test_run_rebase success --fork-point test_run_rebase success -m test_run_rebase success -i
@@ -98,7 +98,7 @@ test_run_rebase () { test_cmp_rev e HEAD " } -test_run_rebase success '' +test_run_rebase success --am test_run_rebase success --fork-point test_run_rebase success -m test_run_rebase success -i
@@ -139,7 +139,7 @@ test_run_rebase () { test_linear_range 'd i' h.. " } -test_run_rebase success '' +test_run_rebase success --am test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -154,7 +154,7 @@ test_run_rebase () { test_linear_range 'd' h.. " } -test_run_rebase success '' +test_run_rebase success --am test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -169,7 +169,7 @@ test_run_rebase () { test_linear_range 'd i' f.. " } -test_run_rebase success '' +test_run_rebase success --am test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -184,7 +184,7 @@ test_run_rebase () { test_linear_range 'd gp i' h.. " } -test_run_rebase success '' +test_run_rebase success --am test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -212,7 +212,7 @@ test_run_rebase () { test_linear_range 'j d k l' c.. " } -test_run_rebase failure '' +test_run_rebase failure --am test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase failure -p
@@ -227,7 +227,7 @@ test_run_rebase () { test_linear_range 'd k l' c.. " } -test_run_rebase success '' +test_run_rebase success --am test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -242,7 +242,7 @@ test_run_rebase () { test_linear_range 'd k l' j.. " } -test_run_rebase success '' +test_run_rebase success --am test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -282,7 +282,7 @@ test_run_rebase () { test_linear_range 'x y' c.. " } -test_run_rebase success '' +test_run_rebase success --am test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -297,7 +297,7 @@ test_run_rebase () { test_linear_range 'x y' c.. " } -test_run_rebase success '' +test_run_rebase success --am test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase failure -p
@@ -312,7 +312,7 @@ test_run_rebase () { test_linear_range 'x y' m.. " } -test_run_rebase success '' +test_run_rebase success --am test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -328,7 +328,7 @@ test_run_rebase () { " } -test_run_rebase success '' +test_run_rebase success --am test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase failure -p
@@ -343,7 +343,7 @@ test_run_rebase () { test_linear_range 'x y' m.. " } -test_run_rebase success '' +test_run_rebase success --am test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase failure -p
t/t3425-rebase-topology-merges.sh +4 -4
@@ -54,7 +54,7 @@ test_run_rebase () { test_linear_range 'n o' e.. " } -test_run_rebase success '' +test_run_rebase success --am test_run_rebase success -m test_run_rebase success -i
@@ -70,7 +70,7 @@ test_run_rebase () { test_linear_range "\'"$expected"\'" d.. " } -test_run_rebase success 'n o e' '' +test_run_rebase success 'n o e' --am test_run_rebase success 'n o e' -m test_run_rebase success 'n o e' -i
@@ -86,7 +86,7 @@ test_run_rebase () { test_linear_range "\'"$expected"\'" c.. " } -test_run_rebase success 'd n o e' '' +test_run_rebase success 'd n o e' --am test_run_rebase success 'd n o e' -m test_run_rebase success 'd n o e' -i
@@ -102,7 +102,7 @@ test_run_rebase () { test_linear_range "\'"$expected"\'" c.. " } -test_run_rebase success 'd n o e' '' +test_run_rebase success 'd n o e' --am test_run_rebase success 'd n o e' -m test_run_rebase success 'd n o e' -i
t/t3432-rebase-fast-forward.sh +2 -2
@@ -28,8 +28,8 @@ test_rebase_same_head () { shift && cmp_f="$1" && shift && - test_rebase_same_head_ $status_n $what_n $cmp_n "" "$*" && - test_rebase_same_head_ $status_f $what_f $cmp_f " --no-ff" "$*" + test_rebase_same_head_ $status_n $what_n $cmp_n " --am" "$*" && + test_rebase_same_head_ $status_f $what_f $cmp_f " --am --no-ff" "$*" test_rebase_same_head_ $status_n $what_n $cmp_n " --merge" "$*" && test_rebase_same_head_ $status_f $what_f $cmp_f " --merge --no-ff" "$*" }
t/t5407-post-rewrite-hook.sh +6 -6
@@ -53,10 +53,10 @@ test_expect_success 'git commit --amend --no-post-rewrite' ' test ! -f post-rewrite.data ' -test_expect_success 'git rebase' ' +test_expect_success 'git rebase --am' ' git reset --hard D && clear_hook_input && - test_must_fail git rebase --onto A B && + test_must_fail git rebase --am --onto A B && echo C > foo && git add foo && git rebase --continue &&
@@ -68,10 +68,10 @@ test_expect_success 'git rebase' ' verify_hook_input ' -test_expect_success 'git rebase --skip' ' +test_expect_success 'git rebase --am --skip' ' git reset --hard D && clear_hook_input && - test_must_fail git rebase --onto A B && + test_must_fail git rebase --am --onto A B && test_must_fail git rebase --skip && echo D > foo && git add foo &&
@@ -84,10 +84,10 @@ test_expect_success 'git rebase --skip' ' verify_hook_input ' -test_expect_success 'git rebase --skip the last one' ' +test_expect_success 'git rebase --am --skip the last one' ' git reset --hard F && clear_hook_input && - test_must_fail git rebase --onto D A && + test_must_fail git rebase --am --onto D A && git rebase --skip && echo rebase >expected.args && cat >expected.data <<-EOF &&
t/t7512-status-help.sh +6 -6
@@ -71,10 +71,10 @@ test_expect_success 'prepare for rebase conflicts' ' ' -test_expect_success 'status when rebase in progress before resolving conflicts' ' +test_expect_success 'status when rebase --am in progress before resolving conflicts' ' test_when_finished "git rebase --abort" && ONTO=$(git rev-parse --short HEAD^^) && - test_must_fail git rebase HEAD^ --onto HEAD^^ && + test_must_fail git rebase --am HEAD^ --onto HEAD^^ && cat >expected <<EOF && rebase in progress; onto $ONTO You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
@@ -94,11 +94,11 @@ EOF ' -test_expect_success 'status when rebase in progress before rebase --continue' ' +test_expect_success 'status when rebase --am in progress before rebase --continue' ' git reset --hard rebase_conflicts && test_when_finished "git rebase --abort" && ONTO=$(git rev-parse --short HEAD^^) && - test_must_fail git rebase HEAD^ --onto HEAD^^ && + test_must_fail git rebase --am HEAD^ --onto HEAD^^ && echo three >main.txt && git add main.txt && cat >expected <<EOF &&
@@ -688,7 +688,7 @@ EOF ' -test_expect_success 'status when rebase conflicts with statushints disabled' ' +test_expect_success 'status when rebase --am conflicts with statushints disabled' ' git reset --hard master && git checkout -b statushints_disabled && test_when_finished "git config --local advice.statushints true" &&
@@ -698,7 +698,7 @@ test_expect_success 'status when rebase conflicts with statushints disabled' ' test_commit three_statushints main.txt three && test_when_finished "git rebase --abort" && ONTO=$(git rev-parse --short HEAD^^) && - test_must_fail git rebase HEAD^ --onto HEAD^^ && + test_must_fail git rebase --am HEAD^ --onto HEAD^^ && cat >expected <<EOF && rebase in progress; onto $ONTO You are currently rebasing branch '\''statushints_disabled'\'' on '\''$ONTO'\''.
Signature · 1 line
gitgitgadget
PATCH v5 17/20 rebase tests: repeat some tests using the merge backend instead of am
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
In order to ensure the merge/interactive backend gets similar coverage to the am one, add some tests for cases where previously only the am backend was tested.
--- t/t5520-pull.sh | 17 +++++++++++++++-- t/t6047-diff3-conflict-markers.sh | 13 +++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-)
t/t5520-pull.sh +15 -2
@@ -277,14 +277,27 @@ test_expect_success '--rebase' ' test_cmp expect actual ' -test_expect_success '--rebase fast forward' ' +test_expect_success '--rebase (merge) fast forward' ' git reset --hard before-rebase && git checkout -b ff && echo another modification >file && git commit -m third file && git checkout to-rebase && - git pull --rebase . ff && + git -c rebase.backend=merge pull --rebase . ff && + test_cmp_rev HEAD ff && + + # The above only validates the result. Did we actually bypass rebase? + git reflog -1 >reflog.actual && + sed "s/^[0-9a-f][0-9a-f]*/OBJID/" reflog.actual >reflog.fuzzy && + echo "OBJID HEAD@{0}: pull --rebase . ff: Fast-forward" >reflog.expected && + test_cmp reflog.expected reflog.fuzzy +' + +test_expect_success '--rebase (am) fast forward' ' + git reset --hard before-rebase && + + git -c rebase.backend=am pull --rebase . ff && test_cmp_rev HEAD ff && # The above only validates the result. Did we actually bypass rebase?
t/t6047-diff3-conflict-markers.sh +11 -2
@@ -186,7 +186,7 @@ test_expect_success 'check multiple merge bases' ' ) ' -test_expect_success 'rebase describes fake ancestor base' ' +test_expect_success 'rebase --merge describes parent of commit being picked' ' test_create_repo rebase && ( cd rebase &&
@@ -194,7 +194,16 @@ test_expect_success 'rebase describes fake ancestor base' ' test_commit master file && git checkout -b side HEAD^ && test_commit side file && - test_must_fail git -c merge.conflictstyle=diff3 rebase master && + test_must_fail git -c merge.conflictstyle=diff3 rebase --merge master && + grep "||||||| parent of" file + ) +' + +test_expect_success 'rebase --am describes fake ancestor base' ' + ( + cd rebase && + git rebase --abort && + test_must_fail git -c merge.conflictstyle=diff3 rebase --am master && grep "||||||| constructed merge base" file ) '
Signature · 1 line
gitgitgadget
PATCH v5 18/20 rebase: make the backend configurable via config setting
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
--- Documentation/config/rebase.txt | 8 ++++++++ builtin/rebase.c | 31 ++++++++++++++++++++++++------- 2 files changed, 32 insertions(+), 7 deletions(-)
Documentation/config/rebase.txt +8 -0
@@ -5,6 +5,14 @@ rebase.useBuiltin:: is always used. Setting this will emit a warning, to alert any remaining users that setting this now does nothing. +rebase.backend:: + Default backend to use for rebasing. Possible choices are + 'am' or 'merge' (note that the merge backend is sometimes also + refered to as the interactive backend or the interactive + machinery elsewhere in the docs). Also, in the future, if the + merge backend gains all remaining capabilities of the am + backend, this setting may become unused. + rebase.stat:: Whether to show a diffstat of what changed upstream since the last rebase. False by default.
builtin/rebase.c +24 -7
@@ -60,6 +60,7 @@ enum empty_type {struct rebase_options {enum rebase_type type;enum empty_type empty; +const char *default_backend;const char *state_dir;struct commit *upstream;const char *upstream_name;
@@ -100,6 +101,7 @@ struct rebase_options {#define REBASE_OPTIONS_INIT { \ .type = REBASE_UNSPECIFIED, \ .empty = EMPTY_UNSPECIFIED, \ + .default_backend = "am", \ .flags = REBASE_NO_QUIET, \ .git_am_opts = ARGV_ARRAY_INIT, \ .git_format_patch_opt = STRBUF_INIT \
@@ -1272,6 +1274,10 @@ static int rebase_config(const char *var, const char *value, void *data)return 0 ; } +if (!strcmp (var,"rebase.backend" )) { +return git_config_string (&opts->default_backend, var, value); + } +return git_default_config (var, value, data); }
@@ -1900,9 +1906,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)if (strcmp (options.git_am_opts.argv[i],"-q" ))break ; -if (is_interactive (&options) && i >=0 ) -die (_ ("cannot combine am options with either " -"interactive or merge options" )); +if (i >=0 ) { +if (is_interactive (&options)) +die (_ ("cannot combine am options with either " +"interactive or merge options" )); +else + options.type =REBASE_AM ; + } + } + +if (options.type ==REBASE_UNSPECIFIED ) { +if (!strcmp (options.default_backend,"merge" )) + options.type =REBASE_MERGE ; +else if (!strcmp (options.default_backend,"am" )) + options.type =REBASE_AM ; +else +die (_ ("Unknown rebase backend: %s" ), + options.default_backend); }switch (options.type) {
@@ -1915,10 +1935,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.state_dir =apply_dir ();break ;default : -/* the default rebase backend is `--am` */ - options.type =REBASE_AM ; - options.state_dir =apply_dir (); -break ; +BUG ("options.type was just set above; should be unreachable." ); }if (options.empty ==EMPTY_UNSPECIFIED ) {
Signature · 1 line
gitgitgadget
PATCH v5 19/20 rebase: change the default backend from "am" to "merge"
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
The am-backend drops information and thus limits what we can do:
* lack of full tree information from the original commits means we cannot do directory rename detection and warn users that they might want to move some of their new files that they placed in old directories to prevent their becoming orphaned.[1]
* reduction in context from only having a few lines beyond those changed means that when context lines are non-unique we can apply patches incorrectly.[2]
* lack of access to original commits means that conflict marker annotation has less information available.
* the am backend has safety problems with an ill-timed interrupt.
Also, the merge/interactive backend have far more abilities, appear to currently have a slight performance advantage[3] and have room for more optimizations than the am backend[4] (and work is underway to take advantage of some of those possibilities).
[1] https://lore.kernel.org/git/[email protected]/
[2] https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/
[3] https://public-inbox.org/git/CABPp-BF=ev03WgODk6TMQmuNoatg2kiEe5DR__gJ0OTVqHSnfQ@mail.gmail.com/
[4] https://lore.kernel.org/git/CABPp-BGh7yW69QwxQb13K0HM38NKmQif3A6C6UULEKYnkEJ5vA@mail.gmail.com/
--- Documentation/git-rebase.txt | 13 ++++++++++++- builtin/rebase.c | 4 ++-- t/t5520-pull.sh | 10 ++++++---- t/t9106-git-svn-commit-diff-clobber.sh | 3 ++- 4 files changed, 22 insertions(+), 8 deletions(-)
Documentation/git-rebase.txt +12 -1
@@ -315,7 +315,7 @@ See also INCOMPATIBLE OPTIONS below. --merge:: Use merging strategies to rebase. When the recursive (default) merge strategy is used, this allows rebase to be aware of renames on the - upstream side. + upstream side. This is the default. + Note that a rebase merge works by replaying each commit from the working branch on top of the <upstream> branch. Because of this, when a merge
@@ -683,6 +683,17 @@ accident of implementation rather than by design. Both backends should have the same behavior, though it is not clear which one is correct. +Interruptability +~~~~~~~~~~~~~~~~ + +The am backend has safety problems with an ill-timed interrupt; if the +user presses Ctrl-C at the wrong time to try to abort the rebase, the +rebase can enter a state where it cannot be aborted with a subsequent +`git rebase --abort`. The interactive backend does not appear to +suffer from the same shortcoming. (See +https://lore.kernel.org/git/[email protected]/ for +details.) + Miscellaneous differences ~~~~~~~~~~~~~~~~~~~~~~~~~
builtin/rebase.c +2 -2
@@ -101,7 +101,7 @@ struct rebase_options {#define REBASE_OPTIONS_INIT { \ .type = REBASE_UNSPECIFIED, \ .empty = EMPTY_UNSPECIFIED, \ - .default_backend = "am", \ + .default_backend = "merge", \ .flags = REBASE_NO_QUIET, \ .git_am_opts = ARGV_ARRAY_INIT, \ .git_format_patch_opt = STRBUF_INIT \
@@ -1917,7 +1917,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)if (options.type ==REBASE_UNSPECIFIED ) {if (!strcmp (options.default_backend,"merge" )) - options.type =REBASE_MERGE ; +imply_interactive (&options,"--merge" );else if (!strcmp (options.default_backend,"am" )) options.type =REBASE_AM ;else
t/t5520-pull.sh +6 -4
@@ -340,7 +340,7 @@ test_expect_success '--rebase with conflicts shows advice' ' test_tick && git commit -m "Create conflict" seq.txt && test_must_fail git pull --rebase . seq 2>err >out && - test_i18ngrep "Resolve all conflicts manually" out + test_i18ngrep "Resolve all conflicts manually" err ' test_expect_success 'failed --rebase shows advice' '
@@ -354,7 +354,7 @@ test_expect_success 'failed --rebase shows advice' ' git checkout -f -b fails-to-rebase HEAD^ && test_commit v2-without-cr file "2" file2-lf && test_must_fail git pull --rebase . diverging 2>err >out && - test_i18ngrep "Resolve all conflicts manually" out + test_i18ngrep "Resolve all conflicts manually" err ' test_expect_success '--rebase fails with multiple branches' '
@@ -774,8 +774,10 @@ test_expect_success 'git pull --rebase does not reapply old patches' ' ( cd dst && test_must_fail git pull --rebase && - find .git/rebase-apply -name "000*" >patches && - test_line_count = 1 patches + cat .git/rebase-merge/done .git/rebase-merge/git-rebase-todo >work && + grep -v -e \# -e ^$ work >patches && + test_line_count = 1 patches && + rm -f work ) '
t/t9106-git-svn-commit-diff-clobber.sh +2 -1
@@ -92,7 +92,8 @@ test_expect_success 'multiple dcommit from git svn will not clobber svn' " test_expect_success 'check that rebase really failed' ' - test -d .git/rebase-apply + git status >output && + grep currently.rebasing output ' test_expect_success 'resolve, continue the rebase and dcommit' "
Signature · 1 line
gitgitgadget
PATCH v5 20/20 rebase: rename the two primary rebase backends
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
Two related changes, with separate rationale for each:
Rename the 'interactive' backend to 'merge' because:
* 'interactive' as a name caused confusion; this backend has been used for many kinds of non-interactive rebases, and will probably be used in the future for more non-interactive rebases than interactive ones given that we are making it the default.
* 'interactive' is not the underlying strategy; merging is.
* the directory where state is stored is not called .git/rebase-interactive but .git/rebase-merge.
Rename the 'am' backend to 'apply' because:
* Few users are familiar with git-am as a reference point.
* Related to the above, the name 'am' makes sentences in the documentation harder for users to read and comprehend (they may read it as the verb from "I am"); avoiding this difficult places a large burden on anyone writing documentation about this backend to be very careful with quoting and sentence structure and often forces annoying redundancy to try to avoid such problems.
* Users stumble over pronunciation ("am" as in "I am a person not a backend" or "am" as in "the first and thirteenth letters in the alphabet in order are "A-M"); this may drive confusion when one user tries to explain to another what they are doing.
* While "am" is the tool driving this backend, the tool driving git-am is git-apply, and since we are driving towards lower-level tools for the naming of the merge backend we may as well do so here too.
* The directory where state is stored has never been called .git/rebase-am, it was always called .git/rebase-apply.
For all the reasons listed above:
* Modify the documentation to refer to the backends with the new names
* Provide a brief note in the documentation connecting the new names to the old names in case users run across the old names anywhere (e.g. in old release notes or older versions of the documentation)
* Change the (new) --am command line flag to --apply
* Rename some enums, variables, and functions to reinforce the new backend names for us as well.
--- Documentation/config/rebase.txt | 8 +-- Documentation/git-rebase.txt | 77 +++++++++++++----------- builtin/rebase.c | 99 +++++++++++++++---------------- t/t3400-rebase.sh | 16 ++--- t/t3401-rebase-and-am-rename.sh | 4 +- t/t3404-rebase-interactive.sh | 2 +- t/t3406-rebase-message.sh | 12 ++-- t/t3407-rebase-abort.sh | 6 +- t/t3420-rebase-autostash.sh | 22 +++---- t/t3421-rebase-topology-linear.sh | 34 +++++------ t/t3424-rebase-empty.sh | 6 +- t/t3425-rebase-topology-merges.sh | 8 +-- t/t3432-rebase-fast-forward.sh | 4 +- t/t5407-post-rewrite-hook.sh | 16 ++--- t/t5520-pull.sh | 2 +- t/t6047-diff3-conflict-markers.sh | 4 +- t/t7512-status-help.sh | 12 ++-- t/t9903-bash-prompt.sh | 2 +- 18 files changed, 166 insertions(+), 168 deletions(-)
Documentation/config/rebase.txt +3 -5
@@ -7,11 +7,9 @@ rebase.useBuiltin:: rebase.backend:: Default backend to use for rebasing. Possible choices are - 'am' or 'merge' (note that the merge backend is sometimes also - refered to as the interactive backend or the interactive - machinery elsewhere in the docs). Also, in the future, if the - merge backend gains all remaining capabilities of the am - backend, this setting may become unused. + 'apply' or 'merge'. In the future, if the merge backend gains + all remaining capabilities of the apply backend, this setting + may become unused. rebase.stat:: Whether to show a diffstat of what changed upstream since the last
Documentation/git-rebase.txt +41 -36
164 lines not on this page · raw message
builtin/rebase.c +47 -52
298 lines not on this page · raw message
t/t3400-rebase.sh +8 -8
@@ -183,19 +183,19 @@ test_expect_success 'default to common base in @{upstream}s reflog if no upstrea test_cmp expect actual ' -test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--am)' ' +test_expect_success 'default to common base in @{upstream}s reflog if no upstream arg (--apply)' ' git checkout -B default-base master && git checkout -B default topic && git config branch.default.remote . && git config branch.default.merge refs/heads/default-base && - git rebase --am && + git rebase --apply && git rev-parse --verify default-base >expect && git rev-parse default~1 >actual && test_cmp expect actual && git checkout default-base && git reset --hard HEAD^ && git checkout default && - git rebase --am && + git rebase --apply && git rev-parse --verify default-base >expect && git rev-parse default~1 >actual && test_cmp expect actual
@@ -224,9 +224,9 @@ test_expect_success 'cherry-picked commits and fork-point work together' ' test_cmp expect D ' -test_expect_success 'rebase --am -q is quiet' ' +test_expect_success 'rebase --apply -q is quiet' ' git checkout -b quiet topic && - git rebase --am -q master >output.out 2>&1 && + git rebase --apply -q master >output.out 2>&1 && test_must_be_empty output.out '
@@ -315,7 +315,7 @@ EOF test_cmp From_.msg out ' -test_expect_success 'rebase --am and --show-current-patch' ' +test_expect_success 'rebase --apply and --show-current-patch' ' test_create_repo conflict-apply && ( cd conflict-apply &&
@@ -325,13 +325,13 @@ test_expect_success 'rebase --am and --show-current-patch' ' echo two >>init.t && git commit -a -m two && git tag two && - test_must_fail git rebase --am -f --onto init HEAD^ && + test_must_fail git rebase --apply -f --onto init HEAD^ && GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && grep "show.*$(git rev-parse two)" stderr ) ' -test_expect_success 'rebase --am and .gitattributes' ' +test_expect_success 'rebase --apply and .gitattributes' ' test_create_repo attributes && ( cd attributes &&
t/t3401-rebase-and-am-rename.sh +2 -2
@@ -52,13 +52,13 @@ test_expect_success 'rebase --interactive: directory rename detected' ' ) ' -test_expect_failure 'rebase --am: directory rename detected' ' +test_expect_failure 'rebase --apply: directory rename detected' ' ( cd dir-rename && git checkout B^0 && - git -c merge.directoryRenames=true rebase --am A && + git -c merge.directoryRenames=true rebase --apply A && git ls-files -s >out && test_line_count = 5 out &&
t/t3404-rebase-interactive.sh +1 -1
@@ -1138,7 +1138,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int git checkout conflict-branch && ( set_fake_editor && - test_must_fail git rebase -f --am --onto HEAD~2 HEAD~ && + test_must_fail git rebase -f --apply --onto HEAD~2 HEAD~ && test_must_fail git rebase --edit-todo ) && git rebase --abort
t/t3406-rebase-message.sh +6 -6
@@ -23,24 +23,24 @@ test_expect_success 'rebase -m' ' ' test_expect_success 'rebase against master twice' ' - git rebase --am master >out && + git rebase --apply master >out && test_i18ngrep "Current branch topic is up to date" out ' test_expect_success 'rebase against master twice with --force' ' - git rebase --force-rebase --am master >out && + git rebase --force-rebase --apply master >out && test_i18ngrep "Current branch topic is up to date, rebase forced" out ' test_expect_success 'rebase against master twice from another branch' ' git checkout topic^ && - git rebase --am master topic >out && + git rebase --apply master topic >out && test_i18ngrep "Current branch topic is up to date" out ' test_expect_success 'rebase fast-forward to master' ' git checkout topic^ && - git rebase --am topic >out && + git rebase --apply topic >out && test_i18ngrep "Fast-forwarded HEAD to topic" out '
@@ -89,7 +89,7 @@ test_expect_success 'GIT_REFLOG_ACTION' ' git checkout -b reflog-topic start && test_commit reflog-to-rebase && - git rebase --am reflog-onto && + git rebase --apply reflog-onto && git log -g --format=%gs -3 >actual && cat >expect <<-\EOF && rebase finished: returning to refs/heads/reflog-topic
@@ -99,7 +99,7 @@ test_expect_success 'GIT_REFLOG_ACTION' ' test_cmp expect actual && git checkout -b reflog-prefix reflog-to-rebase && - GIT_REFLOG_ACTION=change-the-reflog git rebase --am reflog-onto && + GIT_REFLOG_ACTION=change-the-reflog git rebase --apply reflog-onto && git log -g --format=%gs -3 >actual && cat >expect <<-\EOF && rebase finished: returning to refs/heads/reflog-prefix
t/t3407-rebase-abort.sh +3 -3
@@ -96,14 +96,14 @@ testrebase() { ' } -testrebase " --am" .git/rebase-apply +testrebase " --apply" .git/rebase-apply testrebase " --merge" .git/rebase-merge -test_expect_success 'rebase --am --quit' ' +test_expect_success 'rebase --apply --quit' ' cd "$work_dir" && # Clean up the state from the previous one git reset --hard pre-rebase && - test_must_fail git rebase --am master && + test_must_fail git rebase --apply master && test_path_is_dir .git/rebase-apply && head_before=$(git rev-parse HEAD) && git rebase --quit &&
t/t3420-rebase-autostash.sh +11 -11
@@ -34,7 +34,7 @@ test_expect_success setup ' remove_progress_re="$(printf "s/.*\\r//")" ' -create_expected_success_am () { +create_expected_success_apply () { cat >expected <<-EOF $(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual) First, rewinding head to replay your work on top of it...
@@ -44,7 +44,7 @@ create_expected_success_am () { EOF } -create_expected_success_interactive () { +create_expected_success_merge () { q_to_cr >expected <<-EOF $(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual) Applied autostash.
@@ -52,7 +52,7 @@ create_expected_success_interactive () { EOF } -create_expected_failure_am () { +create_expected_failure_apply () { cat >expected <<-EOF $(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual) First, rewinding head to replay your work on top of it...
@@ -64,7 +64,7 @@ create_expected_failure_am () { EOF } -create_expected_failure_interactive () { +create_expected_failure_merge () { cat >expected <<-EOF $(grep "^Created autostash: [0-9a-f][0-9a-f]*\$" actual) Applying autostash resulted in conflicts.
@@ -101,9 +101,9 @@ testrebase () { test_expect_success "rebase$type --autostash: check output" ' test_when_finished git branch -D rebased-feature-branch && - suffix=${type#\ --} && suffix=${suffix:-am} && - if test ${suffix} = "merge"; then - suffix=interactive + suffix=${type#\ --} && suffix=${suffix:-apply} && + if test ${suffix} = "interactive"; then + suffix=merge fi && create_expected_success_$suffix && sed "$remove_progress_re" <actual >actual2 &&
@@ -202,9 +202,9 @@ testrebase () { test_expect_success "rebase$type: check output with conflicting stash" ' test_when_finished git branch -D rebased-feature-branch && - suffix=${type#\ --} && suffix=${suffix:-am} && - if test ${suffix} = "merge"; then - suffix=interactive + suffix=${type#\ --} && suffix=${suffix:-apply} && + if test ${suffix} = "interactive"; then + suffix=merge fi && create_expected_failure_$suffix && sed "$remove_progress_re" <actual >actual2 &&
@@ -234,7 +234,7 @@ test_expect_success "rebase: noop rebase" ' git checkout feature-branch ' -testrebase " --am" .git/rebase-apply +testrebase " --apply" .git/rebase-apply testrebase " --merge" .git/rebase-merge testrebase " --interactive" .git/rebase-merge
t/t3421-rebase-topology-linear.sh +17 -17
@@ -26,7 +26,7 @@ test_run_rebase () { test_linear_range 'd e' c.. " } -test_run_rebase success --am +test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -50,7 +50,7 @@ test_run_rebase () { test_cmp_rev e HEAD " } -test_run_rebase success --am +test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -66,7 +66,7 @@ test_run_rebase () { test_linear_range 'd e' b.. " } -test_run_rebase success --am +test_run_rebase success --apply test_run_rebase success --fork-point test_run_rebase success -m test_run_rebase success -i
@@ -83,7 +83,7 @@ test_run_rebase () { test_linear_range 'd e' branch-b.. " } -test_run_rebase success --am +test_run_rebase success --apply test_run_rebase success --fork-point test_run_rebase success -m test_run_rebase success -i
@@ -98,7 +98,7 @@ test_run_rebase () { test_cmp_rev e HEAD " } -test_run_rebase success --am +test_run_rebase success --apply test_run_rebase success --fork-point test_run_rebase success -m test_run_rebase success -i
@@ -139,7 +139,7 @@ test_run_rebase () { test_linear_range 'd i' h.. " } -test_run_rebase success --am +test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -154,7 +154,7 @@ test_run_rebase () { test_linear_range 'd' h.. " } -test_run_rebase success --am +test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -169,7 +169,7 @@ test_run_rebase () { test_linear_range 'd i' f.. " } -test_run_rebase success --am +test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -184,7 +184,7 @@ test_run_rebase () { test_linear_range 'd gp i' h.. " } -test_run_rebase success --am +test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -212,7 +212,7 @@ test_run_rebase () { test_linear_range 'j d k l' c.. " } -test_run_rebase failure --am +test_run_rebase failure --apply test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase failure -p
@@ -227,7 +227,7 @@ test_run_rebase () { test_linear_range 'd k l' c.. " } -test_run_rebase success --am +test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -242,7 +242,7 @@ test_run_rebase () { test_linear_range 'd k l' j.. " } -test_run_rebase success --am +test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -282,7 +282,7 @@ test_run_rebase () { test_linear_range 'x y' c.. " } -test_run_rebase success --am +test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -297,7 +297,7 @@ test_run_rebase () { test_linear_range 'x y' c.. " } -test_run_rebase success --am +test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase failure -p
@@ -312,7 +312,7 @@ test_run_rebase () { test_linear_range 'x y' m.. " } -test_run_rebase success --am +test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p
@@ -328,7 +328,7 @@ test_run_rebase () { " } -test_run_rebase success --am +test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase failure -p
@@ -343,7 +343,7 @@ test_run_rebase () { test_linear_range 'x y' m.. " } -test_run_rebase success --am +test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase failure -p
t/t3424-rebase-empty.sh +3 -3
@@ -34,11 +34,11 @@ test_expect_success 'setup test repository' ' git commit -m "Five letters ought to be enough for anybody" ' -test_expect_failure 'rebase (am-backend)' ' +test_expect_failure 'rebase (apply-backend)' ' test_when_finished "git rebase --abort" && git checkout -B testing localmods && - # rebase (--am) should not drop commits that start empty - git rebase upstream && + # rebase (--apply) should not drop commits that start empty + git rebase --apply upstream && test_write_lines D C B A >expect && git log --format=%s >actual &&
t/t3425-rebase-topology-merges.sh +4 -4
@@ -54,7 +54,7 @@ test_run_rebase () { test_linear_range 'n o' e.. " } -test_run_rebase success --am +test_run_rebase success --apply test_run_rebase success -m test_run_rebase success -i
@@ -70,7 +70,7 @@ test_run_rebase () { test_linear_range "\'"$expected"\'" d.. " } -test_run_rebase success 'n o e' --am +test_run_rebase success 'n o e' --apply test_run_rebase success 'n o e' -m test_run_rebase success 'n o e' -i
@@ -86,7 +86,7 @@ test_run_rebase () { test_linear_range "\'"$expected"\'" c.. " } -test_run_rebase success 'd n o e' --am +test_run_rebase success 'd n o e' --apply test_run_rebase success 'd n o e' -m test_run_rebase success 'd n o e' -i
@@ -102,7 +102,7 @@ test_run_rebase () { test_linear_range "\'"$expected"\'" c.. " } -test_run_rebase success 'd n o e' --am +test_run_rebase success 'd n o e' --apply test_run_rebase success 'd n o e' -m test_run_rebase success 'd n o e' -i
t/t3432-rebase-fast-forward.sh +2 -2
@@ -28,8 +28,8 @@ test_rebase_same_head () { shift && cmp_f="$1" && shift && - test_rebase_same_head_ $status_n $what_n $cmp_n " --am" "$*" && - test_rebase_same_head_ $status_f $what_f $cmp_f " --am --no-ff" "$*" + test_rebase_same_head_ $status_n $what_n $cmp_n " --apply" "$*" && + test_rebase_same_head_ $status_f $what_f $cmp_f " --apply --no-ff" "$*" test_rebase_same_head_ $status_n $what_n $cmp_n " --merge" "$*" && test_rebase_same_head_ $status_f $what_f $cmp_f " --merge --no-ff" "$*" }
t/t5407-post-rewrite-hook.sh +8 -8
@@ -53,10 +53,10 @@ test_expect_success 'git commit --amend --no-post-rewrite' ' test ! -f post-rewrite.data ' -test_expect_success 'git rebase --am' ' +test_expect_success 'git rebase --apply' ' git reset --hard D && clear_hook_input && - test_must_fail git rebase --am --onto A B && + test_must_fail git rebase --apply --onto A B && echo C > foo && git add foo && git rebase --continue &&
@@ -68,10 +68,10 @@ test_expect_success 'git rebase --am' ' verify_hook_input ' -test_expect_success 'git rebase --am --skip' ' +test_expect_success 'git rebase --apply --skip' ' git reset --hard D && clear_hook_input && - test_must_fail git rebase --am --onto A B && + test_must_fail git rebase --apply --onto A B && test_must_fail git rebase --skip && echo D > foo && git add foo &&
@@ -84,10 +84,10 @@ test_expect_success 'git rebase --am --skip' ' verify_hook_input ' -test_expect_success 'git rebase --am --skip the last one' ' +test_expect_success 'git rebase --apply --skip the last one' ' git reset --hard F && clear_hook_input && - test_must_fail git rebase --am --onto D A && + test_must_fail git rebase --apply --onto D A && git rebase --skip && echo rebase >expected.args && cat >expected.data <<-EOF &&
@@ -128,7 +128,7 @@ test_expect_success 'git rebase -m --skip' ' verify_hook_input ' -test_expect_success 'git rebase with implicit use of interactive backend' ' +test_expect_success 'git rebase with implicit use of merge backend' ' git reset --hard D && clear_hook_input && test_must_fail git rebase --keep-empty --onto A B &&
@@ -143,7 +143,7 @@ test_expect_success 'git rebase with implicit use of interactive backend' ' verify_hook_input ' -test_expect_success 'git rebase --skip with implicit use of interactive backend' ' +test_expect_success 'git rebase --skip with implicit use of merge backend' ' git reset --hard D && clear_hook_input && test_must_fail git rebase --keep-empty --onto A B &&
t/t5520-pull.sh +1 -1
@@ -297,7 +297,7 @@ test_expect_success '--rebase (merge) fast forward' ' test_expect_success '--rebase (am) fast forward' ' git reset --hard before-rebase && - git -c rebase.backend=am pull --rebase . ff && + git -c rebase.backend=apply pull --rebase . ff && test_cmp_rev HEAD ff && # The above only validates the result. Did we actually bypass rebase?
t/t6047-diff3-conflict-markers.sh +2 -2
@@ -199,11 +199,11 @@ test_expect_success 'rebase --merge describes parent of commit being picked' ' ) ' -test_expect_success 'rebase --am describes fake ancestor base' ' +test_expect_success 'rebase --apply describes fake ancestor base' ' ( cd rebase && git rebase --abort && - test_must_fail git -c merge.conflictstyle=diff3 rebase --am master && + test_must_fail git -c merge.conflictstyle=diff3 rebase --apply master && grep "||||||| constructed merge base" file ) '
t/t7512-status-help.sh +6 -6
@@ -71,10 +71,10 @@ test_expect_success 'prepare for rebase conflicts' ' ' -test_expect_success 'status when rebase --am in progress before resolving conflicts' ' +test_expect_success 'status when rebase --apply in progress before resolving conflicts' ' test_when_finished "git rebase --abort" && ONTO=$(git rev-parse --short HEAD^^) && - test_must_fail git rebase --am HEAD^ --onto HEAD^^ && + test_must_fail git rebase --apply HEAD^ --onto HEAD^^ && cat >expected <<EOF && rebase in progress; onto $ONTO You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
@@ -94,11 +94,11 @@ EOF ' -test_expect_success 'status when rebase --am in progress before rebase --continue' ' +test_expect_success 'status when rebase --apply in progress before rebase --continue' ' git reset --hard rebase_conflicts && test_when_finished "git rebase --abort" && ONTO=$(git rev-parse --short HEAD^^) && - test_must_fail git rebase --am HEAD^ --onto HEAD^^ && + test_must_fail git rebase --apply HEAD^ --onto HEAD^^ && echo three >main.txt && git add main.txt && cat >expected <<EOF &&
@@ -688,7 +688,7 @@ EOF ' -test_expect_success 'status when rebase --am conflicts with statushints disabled' ' +test_expect_success 'status when rebase --apply conflicts with statushints disabled' ' git reset --hard master && git checkout -b statushints_disabled && test_when_finished "git config --local advice.statushints true" &&
@@ -698,7 +698,7 @@ test_expect_success 'status when rebase --am conflicts with statushints disabled test_commit three_statushints main.txt three && test_when_finished "git rebase --abort" && ONTO=$(git rev-parse --short HEAD^^) && - test_must_fail git rebase --am HEAD^ --onto HEAD^^ && + test_must_fail git rebase --apply HEAD^ --onto HEAD^^ && cat >expected <<EOF && rebase in progress; onto $ONTO You are currently rebasing branch '\''statushints_disabled'\'' on '\''$ONTO'\''.
t/t9903-bash-prompt.sh +1 -1
@@ -193,7 +193,7 @@ test_expect_success 'prompt - rebase am' ' printf " (b2|REBASE 1/3)" >expected && git checkout b2 && test_when_finished "git checkout master" && - test_must_fail git rebase --am b1 b2 && + test_must_fail git rebase --apply b1 b2 && test_when_finished "git rebase --abort" && __git_ps1 >"$actual" && test_cmp expected "$actual"
Signature · 1 line
gitgitgadget
Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
Quoted 40 lines · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
Two related changes, with separate rationale for each:
Rename the 'interactive' backend to 'merge' because:
* 'interactive' as a name caused confusion; this backend has been used for many kinds of non-interactive rebases, and will probably be used in the future for more non-interactive rebases than interactive ones given that we are making it the default.
* 'interactive' is not the underlying strategy; merging is.
* the directory where state is stored is not called .git/rebase-interactive but .git/rebase-merge.Rename the 'am' backend to 'apply' because:
* Few users are familiar with git-am as a reference point.
* Related to the above, the name 'am' makes sentences in the documentation harder for users to read and comprehend (they may read it as the verb from "I am"); avoiding this difficult places a large burden on anyone writing documentation about this backend to be very careful with quoting and sentence structure and often forces annoying redundancy to try to avoid such problems.
* Users stumble over pronunciation ("am" as in "I am a person not a backend" or "am" as in "the first and thirteenth letters in the alphabet in order are "A-M"); this may drive confusion when one user tries to explain to another what they are doing.
* While "am" is the tool driving this backend, the tool driving git-am is git-apply, and since we are driving towards lower-level tools for the naming of the merge backend we may as well do so here too.
* The directory where state is stored has never been called .git/rebase-am, it was always called .git/rebase-apply.For all the reasons listed above:
* Modify the documentation to refer to the backends with the new names
* Provide a brief note in the documentation connecting the new names to the old names in case users run across the old names anywhere (e.g. in old release notes or older versions of the documentation)
* Change the (new) --am command line flag to --apply
* Rename some enums, variables, and functions to reinforce the new backend names for us as well.
Quoted 1 line
Signed-off-by Elijah Newren <[email protected]>
Hi,
This broke quite a few upstream users for us today when we rolled out a next with this patch added on top. To shim around the post-commit hook issue, we had set a system config for all our users to use merge.backend=am; the machinery is pretty intolerant to a wrongly configured backend name (die() rather than a warning and fallback).
Would it make more sense to deal with an unrecognized backend by falling back to the default backend, instead?
builtin/rebase.c int cmd_rebase(int argc, const char **argv, const char *prefix) · line 1921
Quoted 11 lines
builtin/rebase.c +3
-3if (options.type ==REBASE_UNSPECIFIED ) {if (!strcmp (options.default_backend,"merge" )) -imply_interactive (&options,"--merge" ); -else if (!strcmp (options.default_backend,"am" )) - options.type =REBASE_AM ; +imply_merge (&options,"--merge" ); +else if (!strcmp (options.default_backend,"apply" )) + options.type =REBASE_APPLY ;else die (_ ("Unknown rebase backend: %s" ), options.default_backend);
At the very least, can this die() explain that it found that string in the config so the user can have a guess as to how to fix it?
(I realize the complained code is from earlier in the series, but this patch - renaming something that used to be valid without a fallback - invalidated our configs, highlighting the problem for us. So I'm replying here instead.)
- Emily
Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
Quoted 67 lines · Emily Shaffer
Quoted 40 lines · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
Two related changes, with separate rationale for each:
Rename the 'interactive' backend to 'merge' because:
* 'interactive' as a name caused confusion; this backend has been used for many kinds of non-interactive rebases, and will probably be used in the future for more non-interactive rebases than interactive ones given that we are making it the default.
* 'interactive' is not the underlying strategy; merging is.
* the directory where state is stored is not called .git/rebase-interactive but .git/rebase-merge.Rename the 'am' backend to 'apply' because:
* Few users are familiar with git-am as a reference point.
* Related to the above, the name 'am' makes sentences in the documentation harder for users to read and comprehend (they may read it as the verb from "I am"); avoiding this difficult places a large burden on anyone writing documentation about this backend to be very careful with quoting and sentence structure and often forces annoying redundancy to try to avoid such problems.
* Users stumble over pronunciation ("am" as in "I am a person not a backend" or "am" as in "the first and thirteenth letters in the alphabet in order are "A-M"); this may drive confusion when one user tries to explain to another what they are doing.
* While "am" is the tool driving this backend, the tool driving git-am is git-apply, and since we are driving towards lower-level tools for the naming of the merge backend we may as well do so here too.
* The directory where state is stored has never been called .git/rebase-am, it was always called .git/rebase-apply.For all the reasons listed above:
* Modify the documentation to refer to the backends with the new names
* Provide a brief note in the documentation connecting the new names to the old names in case users run across the old names anywhere (e.g. in old release notes or older versions of the documentation)
* Change the (new) --am command line flag to --apply
* Rename some enums, variables, and functions to reinforce the new backend names for us as well.Quoted 1 line
Signed-off-by Elijah Newren <[email protected]>Hi,
This broke quite a few upstream users for us today when we rolled out a next with this patch added on top. To shim around the post-commit hook issue, we had set a system config for all our users to use merge.backend=am; the machinery is pretty intolerant to a wrongly configured backend name (
die()rather than a warning and fallback).Would it make more sense to deal with an unrecognized backend by falling back to the default backend, instead?
builtin/rebase.c int cmd_rebase(int argc, const char **argv, const char *prefix) · line 1921
Quoted 11 lines
if (options.type ==
REBASE_UNSPECIFIED) {
if (!strcmp(options.default_backend, "merge"))builtin/rebase.c +3
-3-imply_interactive (&options,"--merge" ); - else if (!strcmp(options.default_backend, "am")) - options.type =REBASE_AM ; +imply_merge (&options,"--merge" ); + else if (!strcmp(options.default_backend, "apply")) + options.type =REBASE_APPLY ; else die(_("Unknown rebase backend: %s"), options.default_backend);
Quoted 7 lines
At the very least, can this
die()explain that it found that string in the config so the user can have a guess as to how to fix it?(I realize the complained code is from earlier in the series, but this patch - renaming something that used to be valid without a fallback - invalidated our configs, highlighting the problem for us. So I'm replying here instead.)
Sorry for the pain. The earlier part of the series had only ever made it to next, and was reverted there, and thus, in my thinking, in the new cycle no one would have ever seen the intermediate state. (Oops, I forgot about cases where people tried out next towards the end of last cycle before it was reverted and decided to set config based upon it.)
I'm a little worried about ignoring the setting and just picking one; if the setting has been marked and they set it to e.g. "appply" (one too many p's), then does it really make sense to just show a warning but continue using the backend they didn't want, especially since they may miss the warning among the rest of the output? I'd rather go the route of improving the message, perhaps:
_("Unknown rebase.backend config setting: %s")
Would that work for you?
Also, I thought that you and Jonathan were going to be changing the post-commit hook handling[1][2]. Does this mean that you've punted on that, and I need to make some kind of change here to get you to switch over?
Elijah
[1] https://lore.kernel.org/git/[email protected]/
[2] https://lore.kernel.org/git/CABPp-BHONuRyt8VJqRuoCF2rGYZ5EhH9KJXQZ3NO69rYwA5J3g@mail.gmail.com/
Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
Quoted 10 lines · Elijah Newren
Quoted 8 lines · Emily Shaffer
This broke quite a few upstream users for us today when we rolled out a next with this patch added on top. To shim around the post-commit hook issue, we had set a system config for all our users to use merge.backend=am; the machinery is pretty intolerant to a wrongly configured backend name (
die()rather than a warning and fallback).Would it make more sense to deal with an unrecognized backend by falling back to the default backend, instead?
[...]
Quoted 6 lines
Sorry for the pain. The earlier part of the series had only ever made it to next, and was reverted there, and thus, in my thinking, in the new cycle no one would have ever seen the intermediate state. (Oops, I forgot about cases where people tried out next towards the end of last cycle before it was reverted and decided to set config based upon it.)
Right, I'm mostly interested in this for the future: do we expect the list of backends to only grow over time, or do we want to support removing and renaming backends? In the latter case, how can we support people sharing config between multiple Git versions with their merge.backend settings?
Quoted 8 lines
I'm a little worried about ignoring the setting and just picking one; if the setting has been marked and they set it to e.g. "appply" (one too many p's), then does it really make sense to just show a warning but continue using the backend they didn't want, especially since they may miss the warning among the rest of the output? I'd rather go the route of improving the message, perhaps:
_("Unknown rebase.backend config setting: %s")
Would that work for you?
What if we support multiple merge.backend values, with semantics:
- last recognized value wins
- if no value is specified, use the default
- if values are specified but none are recognized, error out with a clear error message
?
Quoted 4 lines
Also, I thought that you and Jonathan were going to be changing the post-commit hook handling[1][2]. Does this mean that you've punted on that, and I need to make some kind of change here to get you to switch over?
The setting was a stopgap; our interest in this upstream is primarily from the point of view of "we ran into this, so let's take the opportunity to help others that might run into similar issues in the future".
Thanks,
Jonathan
Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
Quoted 20 lines · Jonathan Nieder
Quoted 10 lines · Elijah Newren
Quoted 8 lines · Emily Shaffer
This broke quite a few upstream users for us today when we rolled out a next with this patch added on top. To shim around the post-commit hook issue, we had set a system config for all our users to use merge.backend=am; the machinery is pretty intolerant to a wrongly configured backend name (
die()rather than a warning and fallback).Would it make more sense to deal with an unrecognized backend by falling back to the default backend, instead?
[...]
Quoted 6 lines
Sorry for the pain. The earlier part of the series had only ever made it to next, and was reverted there, and thus, in my thinking, in the new cycle no one would have ever seen the intermediate state. (Oops, I forgot about cases where people tried out next towards the end of last cycle before it was reverted and decided to set config based upon it.)
Quoted 5 lines
Right, I'm mostly interested in this for the future: do we expect the list of backends to only grow over time, or do we want to support removing and renaming backends? In the latter case, how can we support people sharing config between multiple Git versions with their merge.backend settings?
Good questions. We have already stated that backends might be "removed" or at least "ignored"; from Documentation/config/rebase.txt:
rebase.backend::
Default backend to use for rebasing. Possible choices are 'apply' or 'merge'. In the future, if the merge backend gains all remaining capabilities of the apply backend, this setting may become unused.
In other words, we're already telling users that 'apply' might mean 'merge' in future versions of git. (Or at least that was my mental model; perhaps I should tighten up the wording to state it that way?)
I hadn't thought in terms of adding backends, especially since we've been trying to remove them, though given our history[1] maybe I should have. I guess it's possible we could introduce another backend[2], though we don't necessarily have to make it possible to configure every backend as the default. For example, despite the technical existence of another backend right now, I did not make 'preserve-merges' a possible setting since it is already deprecated. (And to go on a bit of a tangent, I think that if someone is not in the middle of a rebase and tries to start one with the -p flag, we should just throw a warning and pretend they passed -r. We should kill preserve-merges as much as possible. Anyway, enough of that tangent...)
[1] By my count, we've had at least five rebase backends: apply/am, merge-via-shell-out-to-merge-recursive-builtin, legacy-interactive-via-shell, interactive-as-builtin-and-now-called-merge, and preserve-merges. I think at one point all five co-existed. And I wouldn't be surprised if I've forgotten, overlooked, or otherwise missed others.
[2] Maybe I even started one with
https://github.com/newren/git/blob/git-merge-2020-demo/builtin/fast-rebase.c,
but unless it's just too hard to refactor sequencer.c & builtin/rebase.c to make use of merge-ort (AND quit forking subprocesses other than with --exec and -s AND avoid writing state files unless and until you hit a conflict), I'm thinking that dies as nothing more than a demo.
Quoted 9 lines
Quoted 8 lines
I'm a little worried about ignoring the setting and just picking one; if the setting has been marked and they set it to e.g. "appply" (one too many p's), then does it really make sense to just show a warning but continue using the backend they didn't want, especially since they may miss the warning among the rest of the output? I'd rather go the route of improving the message, perhaps:
_("Unknown rebase.backend config setting: %s")
Would that work for you?
Quoted 8 lines
What if we support multiple merge.backend values, with semantics:
- last recognized value wins
- if no value is specified, use the default
- if values are specified but none are recognized, error out with a clear error message?
Sure, but...isn't that what we already do, other than maybe the 'clear' part of step 3? ('merge' and 'apply' are recognized values. If no value is specified, the default of merge is used. If values are specified but none are recognized, it errors out.) Would my suggested change above fix it so that we can consider it sufficiently clear, or do you want something else?
Quoted 9 lines
Quoted 4 lines
Also, I thought that you and Jonathan were going to be changing the post-commit hook handling[1][2]. Does this mean that you've punted on that, and I need to make some kind of change here to get you to switch over?
The setting was a stopgap; our interest in this upstream is primarily from the point of view of "we ran into this, so let's take the opportunity to help others that might run into similar issues in the future".
Thanks for flagging it; I appreciate it. Is my suggested rewording of the error message helpful? Have I missed or misunderstood anything else you were trying to bring up?
Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
Quoted 9 lines · Elijah Newren
Quoted 7 lines · Jonathan Nieder
What if we support multiple merge.backend values, with semantics:
- last recognized value wins
- if no value is specified, use the default
- if values are specified but none are recognized, error out with a clear error message
Quoted 4 lines
Quoted 1 line
?
Sure, but...isn't that what we already do, other than maybe the 'clear' part of step 3?
Sorry for the lack of clarity. I mean allowing
[rebase]
backend = am
backend = apply
backend = futuristic
with behavior
- on "git" that understands am but not apply or futuristic, use the am backend
- on "git" that understands apply but not am or futuristic, use the apply backend
- on "git" that understands apply and futuristic, use the futuristic backend
That way, a single config file is usable on all three versions of Git.
Thanks,
Jonathan
Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
Quoted 25 lines · Jonathan Nieder
Quoted 9 lines · Elijah Newren
Quoted 7 lines · Jonathan Nieder
What if we support multiple merge.backend values, with semantics:
- last recognized value wins
- if no value is specified, use the default
- if values are specified but none are recognized, error out with a clear error messageQuoted 4 lines
Quoted 1 line
?
Sure, but...isn't that what we already do, other than maybe the 'clear' part of step 3?
Sorry for the lack of clarity. I mean allowing
[rebase]
backend = am
backend = apply
backend = futuristicwith behavior
Quoted 8 lines
- on "git" that understands am but not apply or futuristic, use the am backend
- on "git" that understands apply but not am or futuristic, use the apply backend
- on "git" that understands apply and futuristic, use the futuristic backendThat way, a single config file is usable on all three versions of Git.
Ah, gotcha, that makes sense though we'd need to make the thing multi-valued which is a bit late for 2.26. But we could at least extend the logic in that way for 2.27. Also, to further clarify, if they have
[rebase]
backend = misspelled
backend = futuristic
then what should the error be? A couple possibilities:
fatal: Unknown rebase backend: futuristic
fatal: Unknown rebase.backend config setting: futuristic
fatal: Unknown rebase.backend config setting; valid values are 'merge', 'apply'.
or should it be something else?
Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
Hi,
Quoted 18 lines · Elijah Newren
Quoted 16 lines · Jonathan Nieder
Sorry for the lack of clarity. I mean allowing
[rebase]
backend = am
backend = apply
backend = futuristicwith behavior
- on "git" that understands am but not apply or futuristic, use the am backend
- on "git" that understands apply but not am or futuristic, use the apply backend
- on "git" that understands apply and futuristic, use the futuristic backend
Quoted 5 lines
Quoted 1 line
That way, a single config file is usable on all three versions of Git.
Ah, gotcha, that makes sense though we'd need to make the thing multi-valued which is a bit late for 2.26. But we could at least extend the logic in that way for 2.27.
Here's a patch implementing that. I'm not convinced it's worth the complexity, mostly because I'm not convinced that rebase is going to have to select between additional new backends in the future. But if you think it will, then I think this would be a reasonable thing to do (maybe even without the documentation part of the patch).
Thoughts?
Thanks,
Jonathan
-- >8 --
Subject: rebase: allow specifying unrecognized rebase.backend with a fallback
In 8295ed690bf (rebase: make the backend configurable via config setting, 2020-02-15), Git learned a new rebase.backend setting that can be used to specify which implementation should be used for non-interactive rebases: "am" (now called "apply"), which uses "git am", or "merge", which uses the three-way merge machinery.
Most likely those are the only two backends that rebase will ever need to learn, so this level of configurability would be sufficient. At some point the "apply" backend would be retired, and the setting would be removed altogether.
Suppose, though, that rebase learns another backend --- e.g. "faster". In that case, a user might set configuration to request it:
[rebase]
backend = faster
If their configuration is shared between multiple versions of Git (think "home directory on NFS shared between machines"), this would produce errors when read by older versions of Git:
fatal: Unknown rebase backend: faster
On the other hand, if we ignore unrecognized rebase backend settings, then Git would fail to realize that
[rebase]
backend = appply
is a typo, producing a confusing user experience. Let's do something in between: when a rebase backend setting is unrecognized, fall back to the last earlier recognized value, but if no value was recognized, print an error message allowing the user to catch their typo.
--- Documentation/config/rebase.txt | 5 ++ builtin/rebase.c | 52 +++++++++++++++--- t/t3435-rebase-backend.sh | 97 +++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 8 deletions(-) create mode 100755 t/t3435-rebase-backend.sh
Documentation/config/rebase.txt +5 -0
@@ -10,6 +10,11 @@ rebase.backend:: 'apply' or 'merge'. In the future, if the merge backend gains all remaining capabilities of the apply backend, this setting may become unused. ++ +If set multiple times, the last value corresponding to a recognized +backend is used. This is for forward compatibility, as it allows +specifying a rebase backend that Git does not know about yet along +with a backend known today as a fallback. rebase.stat:: Whether to show a diffstat of what changed upstream since the last
builtin/rebase.c +44 -8
88 lines not on this page · raw message
t/t3435-rebase-backend.sh new file +97 -0
97 lines not on this page · raw message
Signature · 1 line
2.25.1.481.gfbce0eb801
Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
Hi Jonathan,
Quoted 28 lines · Jonathan Nieder
Hi,
Quoted 18 lines · Elijah Newren
Quoted 16 lines · Jonathan Nieder
Sorry for the lack of clarity. I mean allowing
[rebase]
backend = am
backend = apply
backend = futuristicwith behavior
- on "git" that understands am but not apply or futuristic, use the am backend
- on "git" that understands apply but not am or futuristic, use the apply backend
- on "git" that understands apply and futuristic, use the futuristic backendQuoted 5 lines
Quoted 1 line
That way, a single config file is usable on all three versions of Git.
Ah, gotcha, that makes sense though we'd need to make the thing multi-valued which is a bit late for 2.26. But we could at least extend the logic in that way for 2.27.
Quoted 7 lines
Here's a patch implementing that. I'm not convinced it's worth the complexity, mostly because I'm not convinced that rebase is going to have to select between additional new backends in the future. But if you think it will, then I think this would be a reasonable thing to do (maybe even without the documentation part of the patch).
Thoughts?
Thanks for investigating what's involved. If there will be new rebase backends, then this does look like nice future proofing to me. As for whether there will be...
Personally, I would rather decrease the number of backends than increase, and if it was up to just me, I'd like to drive the number of backends to one and then keep it there. But it feels hard to know for sure.
Quoted 238 lines
Thanks,
Jonathan-- >8 --
Subject: rebase: allow specifying unrecognized rebase.backend with a fallbackIn
8295ed690bf(rebase: make the backend configurable via config setting, 2020-02-15), Git learned a new rebase.backend setting that can be used to specify which implementation should be used for non-interactive rebases: "am" (now called "apply"), which uses "git am", or "merge", which uses the three-way merge machinery.Most likely those are the only two backends that rebase will ever need to learn, so this level of configurability would be sufficient. At some point the "apply" backend would be retired, and the setting would be removed altogether.
Suppose, though, that rebase learns another backend --- e.g. "faster". In that case, a user might set configuration to request it:
[rebase]
backend = fasterIf their configuration is shared between multiple versions of Git (think "home directory on NFS shared between machines"), this would produce errors when read by older versions of Git:
fatal: Unknown rebase backend: faster
On the other hand, if we ignore unrecognized rebase backend settings, then Git would fail to realize that
[rebase]
backend = appplyis a typo, producing a confusing user experience. Let's do something in between: when a rebase backend setting is unrecognized, fall back to the last earlier recognized value, but if no value was recognized, print an error message allowing the user to catch their typo.
Reported-by Emily Shaffer <[email protected]>Signed-off-by Jonathan Nieder <[email protected]>--- Documentation/config/rebase.txt | 5 ++ builtin/rebase.c | 52 +++++++++++++++--- t/t3435-rebase-backend.sh | 97 +++++++++++++++++++++++++++++++++ 3 files changed, 146 insertions(+), 8 deletions(-) create mode 100755 t/t3435-rebase-backend.shDocumentation/config/rebase.txt +5
-0@@ -10,6 +10,11 @@ rebase.backend:: 'apply' or 'merge'. In the future, if the merge backend gains all remaining capabilities of the apply backend, this setting may become unused. ++ +If set multiple times, the last value corresponding to a recognized +backend is used. This is for forward compatibility, as it allows +specifying a rebase backend that Git does not know about yet along +with a backend known today as a fallback. rebase.stat:: Whether to show a diffstat of what changed upstream since the lastbuiltin/rebase.c +44
-8@@ -56,10 +56,18 @@ enum empty_type {EMPTY_ASK }; +enum rebase_backend { +BACKEND_UNSPECIFIED =0 , +BACKEND_UNRECOGNIZED , +BACKEND_APPLY , +BACKEND_MERGE , +}; +struct rebase_options {enum rebase_type type;enum empty_type empty; -const char *default_backend; +enum rebase_backend configured_backend; +const char *last_specified_backend;const char *state_dir;struct commit *upstream;const char *upstream_name;@@ -100,7 +108,6 @@ struct rebase_options {#define REBASE_OPTIONS_INIT { \ .type = REBASE_UNSPECIFIED, \ .empty = EMPTY_UNSPECIFIED, \ - .default_backend = "merge", \ .flags = REBASE_NO_QUIET, \ .git_am_opts = ARGV_ARRAY_INIT, \ .git_format_patch_opt = STRBUF_INIT \@@ -1224,6 +1231,15 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action)return status ?-1 :0 ; } +static enum rebase_backend parse_rebase_backend (const char *value) +{ +if (!strcmp (value,"apply" )) +return BACKEND_APPLY ; +if (!strcmp (value,"merge" )) +return BACKEND_MERGE ; +return BACKEND_UNRECOGNIZED ; +} +static int rebase_config (const char *var,const char *value,void *data) {struct rebase_options *opts = data;@@ -1264,7 +1280,18 @@ static int rebase_config(const char *var, const char *value, void *data) }if (!strcmp (var,"rebase.backend" )) { -return git_config_string (&opts->default_backend, var, value); +enum rebase_backend val; +if (!value) +return config_error_nonbool (var); + val =parse_rebase_backend (value); +if (opts->configured_backend ==BACKEND_UNSPECIFIED ) + opts->configured_backend = val; +else if (val ==BACKEND_UNRECOGNIZED ) + ;/* Unrecognized rebase backend. Ignore it. */ +else + opts->configured_backend = val; + opts->last_specified_backend = value; +return 0 ; }return git_default_config (var, value, data);@@ -1903,14 +1930,23 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } } +if (options.configured_backend ==BACKEND_UNRECOGNIZED ) +die (_ ("unknown rebase backend: %s" ), + options.last_specified_backend); +if (options.type ==REBASE_UNSPECIFIED ) { -if (!strcmp (options.default_backend,"merge" )) +switch (options.configured_backend) { +case BACKEND_UNSPECIFIED : +case BACKEND_MERGE :imply_merge (&options,"--merge" ); -else if (!strcmp (options.default_backend,"apply" )) +break ; +case BACKEND_APPLY : options.type =REBASE_APPLY ; -else -die (_ ("Unknown rebase backend: %s" ), - options.default_backend); +break ; +default : +BUG ("unexpected backend %d" , + (int ) options.configured_backend); + } }switch (options.type) {t/t3435-rebase-backend.sh new file +70
-0@@ -0,0 +1,97 @@ +#!/bin/sh + +test_description='rebase.backend tests + +Checks of config parsing for the [rebase] backend setting. We detect +which backend was used by checking which directory was created to hold +state.' + +. ./test-lib.sh + +# usage: test_backend_choice <expectation> <command> +# +# Tests that the chosen backend for rebase command <command> +# is <expectation> ("merge" or "apply"). +test_backend_choice () { + expect=$1 && + shift && + + test_must_fail git "$@" master topic && + case $expect in + apply) + test_path_is_dir .git/rebase-apply && + test_path_is_missing .git/rebase-merge + ;; + merge) + test_path_is_dir .git/rebase-merge && + test_path_is_missing .git/rebase-apply + ;; + *) + error "unrecognized expectation $expect" + esac +} + +test_expect_success 'setup' ' + test_commit base && + test_commit sidea conflict.txt myway && + git checkout -b topic base && + test_commit sideb conflict.txt thehighway +' + +test_expect_success '--apply uses apply backend' ' + test_when_finished "git rebase --abort" && + test_backend_choice apply rebase --apply +' + +test_expect_success '--merge uses merge backend' ' + test_when_finished "git rebase --abort" && + test_backend_choice merge rebase --merge +' + +test_expect_success 'default to merge backend' ' + test_when_finished "git rebase --abort" && + test_backend_choice merge rebase +' + +test_expect_success 'config overrides default' ' + test_when_finished "git rebase --abort" && + test_backend_choice apply -c rebase.backend=apply rebase +' + +test_expect_success 'option overrides config' ' + test_when_finished "git rebase --abort" && + test_backend_choice merge -c rebase.backend=apply rebase --merge +' + +test_expect_success 'last config value wins' ' + test_when_finished "git rebase --abort" && + test_backend_choice merge \ + -c rebase.backend=apply \ + -c rebase.backend=merge \
t/t3435-rebase-backend.sh line 80
Quoted 10 lines
t/t3435-rebase-backend.sh new file +10
-0+ rebase +' + +test_expect_success 'last config value wins' ' + test_when_finished "git rebase --abort" && + test_backend_choice merge \ + -c rebase.backend=apply \ + -c rebase.backend=merge \ + rebase +'
Um, copy-and-paste-and-forget-to-edit? This test is identical to the one above it and thus is not useful; I think you meant to flip the order of config options and flip the result (and maybe give a slightly different test name to it)?
Quoted 7 lines
t/t3435-rebase-backend.sh new file +7
-0+ +test_expect_success 'misspelled backend without fallback is diagnosed' ' + test_must_fail \ + git -c rebase.backend=appply rebase master topic 2>message && + test_i18ngrep "unknown rebase backend" message && + grep appply message +'
t/t3435-rebase-backend.sh line 97
Quoted 10 lines
t/t3435-rebase-backend.sh new file +10
-0+ +test_expect_success 'forward compatibility by skipping unrecognized values' ' + test_when_finished "git rebase --abort" && + test_backend_choice apply \ + -c rebase.backend=apply \ + -c rebase.backend=futuristic \ + rebase +' + +test_done
Didn't spot anything other than that one test issue in looking over things.
Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
In reply to Jonathan NiederQuoted 9 lines · Jonathan Nieder
Sorry for the lack of clarity. I mean allowing
[rebase]
backend = am
backend = apply
backend = futuristicwith behavior
Quoted 8 lines
- on "git" that understands am but not apply or futuristic, use the am backend
- on "git" that understands apply but not am or futuristic, use the apply backend
- on "git" that understands apply and futuristic, use the futuristic backendThat way, a single config file is usable on all three versions of Git.
IOW, make rebase.backend a multi-valued one and use it when one and only one value is a recognised one? What would an error message say when none is recognised, and when would that be issued?
I like the general direction it takes, but I think it would be simpler to (1) die when we see what we do not recognise and (2) teach the latest one that "am" is a synonym for "apply", without even advertising "am".
Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
In reply to Elijah NewrenQuoted 1 line · Elijah Newren
I'm a little worried about ignoring the setting and just picking one;
I am more than a little worried, too. I think erroring out is warranted in this case for exactly the reason you gave here.
Quoted 6 lines
if the setting has been marked and they set it to e.g. "appply" (one too many p's), then does it really make sense to just show a warning but continue using the backend they didn't want, especially since they may miss the warning among the rest of the output? I'd rather go the route of improving the message, perhaps:
_("Unknown rebase.backend config setting: %s")
Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
Hi,
Quoted 6 lines · Junio C Hamano
Quoted 1 line · Elijah Newren
I'm a little worried about ignoring the setting and just picking one;
I am more than a little worried, too. I think erroring out is warranted in this case for exactly the reason you gave here.
To avoid misunderstandings: have you read the proposal I made that aims to avoid that problem?
Thanks,
Jonathan
Quoted 6 lines
Quoted 6 lines
if the setting has been marked and they set it to e.g. "appply" (one too many p's), then does it really make sense to just show a warning but continue using the backend they didn't want, especially since they may miss the warning among the rest of the output? I'd rather go the route of improving the message, perhaps:
_("Unknown rebase.backend config setting: %s")
Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
In reply to Elijah NewrenQuoted 3 lines · Elijah Newren
Quoted 2 lines · Emily Shaffer
...
This broke quite a few upstream users for us today...
One more thing I've been wondering was if we should really have said "ok, the two known gotchas have been dealt with, so let's ship it". It is quite likely that users may be hit by remaining differences between the merge and apply backends that we assume to be merely subtle and easy-to-work-around ones, and it may be prudent to use the "let's not change the default just yet, but ask people to test it in their workflow by advertising the configuration" patch.
Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
Quoted 7 lines · Junio C Hamano
Quoted 3 lines · Elijah Newren
Quoted 2 lines · Emily Shaffer
...
This broke quite a few upstream users for us today...
Quoted 7 lines
One more thing I've been wondering was if we should really have said "ok, the two known gotchas have been dealt with, so let's ship it". It is quite likely that users may be hit by remaining differences between the merge and apply backends that we assume to be merely subtle and easy-to-work-around ones, and it may be prudent to use the "let's not change the default just yet, but ask people to test it in their workflow by advertising the configuration" patch.
Good question, but there's several things to unpack here...
First, note that this particular breakage would have occurred regardless of the default setting, because the problem was that they setting rebase.backend to an unrecognized value, not that we used a different backend than they were used to.
Second, for the particular case of the post-commit hook that they referenced in their rationale, that item was documented near the end of the 2.25 cycle and mentioned in previous patchsets[1, 2] so I figured this case was already considered.
Those two points may obscure the issue, though; your question is still valid. I think the bigger question is whether there are other unknown differences, or even known differences that are a bigger issue than we currently realize. That's hard to judge, and it may not be possible to judge until we've flipped the default. As such, it's a judgement call. I can see the judgement call going either way. A couple things to weigh in on how to go:
- Making rebase.backend default to 'apply' for 2.26 is certainly
the conservative option and may give us more feedback and time to iron out differences
- We had multiple complaints this cycle about rebase.backend=apply
merging things incorrectly with the only workaround being to use the merge backend[3,4]
- The rebase-backend topic wasn't merged down to master until less
than a week before -rc0. (For a variety of reasons.) A big change like this probably would have been better to merge down earlier in some cycle.
To be honest, if I was maintainer, I'm not sure which direction I'd pick. If you feel safer switching the default backend to apply for this cycle and then (re-)revert it early next cycle to the merge backend, I think that's totally reasonable.
[1] https://lore.kernel.org/git/[email protected]/
[2] https://lore.kernel.org/git/CABPp-BHONuRyt8VJqRuoCF2rGYZ5EhH9KJXQZ3NO69rYwA5J3g@mail.gmail.com/
[3] https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/
[4] https://lore.kernel.org/git/[email protected]/
Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
Quoted 4 lines · Elijah Newren
First, note that this particular breakage would have occurred regardless of the default setting, because the problem was that they setting rebase.backend to an unrecognized value, not that we used a different backend than they were used to.
If I understand correctly, it was also a setting that never worked in any released version of Git. It was magic that was only ever in 'next'.
As annoying as it is to experience breakage, I'm not _too_ sympathetic to this case, because that is part of the cost of running the bleeding edge of 'next' or even 'master'. I.e., I think we have to make a cutoff _somewhere_ to say "this is something that made it to the general public, and therefore we can't break backwards compatibility" to keep our sanity during development. And it seems like tagged releases are a pretty good cutoff to me.
Though in this particular case, I don't mind too much just leaving "am" as an alias for "apply" (it was actually the first thing I tried when writing my earlier emails, but I'm probably not a representative user there). Putting that in a release, though, may mean supporting it forever. :)
Quoted 7 lines
- We had multiple complaints this cycle about rebase.backend=apply
merging things incorrectly with the only workaround being to use the merge backend[3,4]
- The rebase-backend topic wasn't merged down to master until less
than a week before -rc0. (For a variety of reasons.) A big change like this probably would have been better to merge down earlier in some cycle.
It did feel a bit quick to me, hitting near the end of the cycle. We've had the apply backend as the default for a decade, so even if there are problems with it, they're known issues. So I don't think there's a particular hurry. I'm not entirely convinced that cooking it longer during the next cycle will turn up a lot of new data (I did find a few issues, but the real test is the long-tail of weird use cases that we won't see until an actual release). But it probably doesn't hurt much to take it slow; it just delays a few bug-fixes (which people can still get by setting a config option).
I guess like your email I'm going back and forth between the two options. I think that means it probably doesn't matter _too_ much either way.
-Peff
Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
Quoted 5 lines · Jeff King
Though in this particular case, I don't mind too much just leaving "am" as an alias for "apply" (it was actually the first thing I tried when writing my earlier emails, but I'm probably not a representative user there). Putting that in a release, though, may mean supporting it forever. :)
Yeah, I agree that what used to exist only in 'next' and we chose to remove it before it its 'master', does not deserve to be supported forever. So let's scrap the "am is taken as a synonym for apply". It would not help the old version taken from 'next' grok a new configuration file that uses "apply" anyway ;-)
Quoted 3 lines
I guess like your email I'm going back and forth between the two options. I think that means it probably doesn't matter _too_ much either way.
OK, let's avoid screwing it up even further by doing no more damage than merging just the three fix-ups we discussed recently.
The fact that jrnieder runs 'next'+patches for his $DAYJOB users makes me hope that there may be other organizations that do the same, and cooking in 'next' would mean somthing, though.
We may want to think of a way to strongly encourage those who are in charge of choosing and maintaining the versions of Git that is used in their organization, whose operation depends on the healthy future versions of Git, to run 'next' or at least 'master', to stay ahead of the released versions. Some education and advocacy is needed?
Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
Quoted 26 lines · Junio C Hamano
Quoted 5 lines · Jeff King
Though in this particular case, I don't mind too much just leaving "am" as an alias for "apply" (it was actually the first thing I tried when writing my earlier emails, but I'm probably not a representative user there). Putting that in a release, though, may mean supporting it forever. :)
Yeah, I agree that what used to exist only in 'next' and we chose to remove it before it its 'master', does not deserve to be supported forever. So let's scrap the "am is taken as a synonym for apply". It would not help the old version taken from 'next' grok a new configuration file that uses "apply" anyway ;-)
Quoted 3 lines
I guess like your email I'm going back and forth between the two options. I think that means it probably doesn't matter _too_ much either way.
OK, let's avoid screwing it up even further by doing no more damage than merging just the three fix-ups we discussed recently.
The fact that jrnieder runs 'next'+patches for his $DAYJOB users makes me hope that there may be other organizations that do the same, and cooking in 'next' would mean somthing, though.
Quoted 5 lines
We may want to think of a way to strongly encourage those who are in charge of choosing and maintaining the versions of Git that is used in their organization, whose operation depends on the healthy future versions of Git, to run 'next' or at least 'master', to stay ahead of the released versions. Some education and advocacy is needed?
I agree, it's super cool that Emily and Jonathan distribute 'next' to their users at Google and provide us lots of early feedback. I wish we had something similar; currently, the only control I have is requesting that some documentation file that includes a recommended minimum git version be bumped to something newer, and I usually need to provide a reason (I can't just say, "It's the newest release"). Other than that, developers install whatever version of git they like, possibly unaware of what's in that documentation file.
In fact, not having a way to control git versions is leading to discussions of spending my time to not only fix issues in git (e.g. the read-tree -mu HEAD problems with sparse-checkout) but also writing some separate program that does that piece independent of git so that we can work with a variety of git versions. Such a pain...does anyone know how to educate me to advocate to the company that we come up with some more controlled git version?
Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
Quoted 6 lines · Elijah Newren
Quoted 5 lines
We may want to think of a way to strongly encourage those who are in charge of choosing and maintaining the versions of Git that is used in their organization, whose operation depends on the healthy future versions of Git, to run 'next' or at least 'master', to stay ahead of the released versions. Some education and advocacy is needed?
Quoted 6 lines
I agree, it's super cool that Emily and Jonathan distribute 'next' to their users at Google and provide us lots of early feedback. I wish we had something similar; currently, the only control I have is requesting that some documentation file that includes a recommended minimum git version be bumped to something newer, and I usually need to provide a reason (I can't just say, "It's the newest release").
Of course, within such an environment, "It's to help make sure that the upcoming release won't be broken for us" is hard to phrase in a palatable way for the management :-<.
Re: [PATCH v5 20/20] rebase: rename the two primary rebase backends
In reply to Junio C HamanoQuoted 3 lines · Junio C Hamano
So let's scrap the "am is taken as a synonym for apply".
It would not help the old version taken from 'next' grok a new configuration file that uses "apply" anyway ;-)
Agreed. For 2.26.0, I'm happy with either
- what we currently have, where it defaults to 'merge', or
- the more conservative approach where it supports 'merge' but defaults to 'apply'
Accepting multiple values for forward compatibility is an optional cherry on top: I would like us to eventually get there, but I don't mind if it doesn't make 2.26.0, and it's probably better to give a change like that some cooking time. (Although I won't complain if it does make 2.26.0. ;-))
[...]
Quoted 5 lines
We may want to think of a way to strongly encourage those who are in charge of choosing and maintaining the versions of Git that is used in their organization, whose operation depends on the healthy future versions of Git, to run 'next' or at least 'master', to stay ahead of the released versions. Some education and advocacy is needed?
It's possible we should write up some best practices somewhere in Documentation/ about how to make running "next" go smoothly:
- have a responsive infrastructure team. Pay attention to changes landing upstream and have the infra team test before the rest of your user population :)
- if you have a large user population, use gradual rollouts so that a subset of users can find problematic changes before they affect everyone
- fast rollbacks
- telemetry to catch regressions (in latency, for example) when people are too shy to report them
We can also advertise places, such as Debian experimental, that people can get snapshots without maintaining them themselves.
Thanks,
Jonathan
Re: [PATCH v5 00/20] rebase: make the default backend configurable and change the default
In reply to Elijah Newren via GitGitGadgetHi Elijah
Quoted 8 lines · Elijah Newren via GitGitGadget
This is v5 of en/rebase-backend based on v2.25.0. This series does a lot of work around making the default rebase backend configurable, and switches the default from the apply backend to the merge one.
Changes since v4:
* Added a note about an unfortunately-timed Ctrl-C to the docs, as suggested by SZEDER.
That's a good idea - I don't know why builtin/rebase.c only calls rebase_write_basic_state() which writes head-name after it has run format-patch and am. We write the patches to the state directory so it's not like it saves us from having to clean up.
Quoted 4 lines
* Addressed feedback from Phillip
* Added a new patch at the end renaming the two backends. PLEASE take a look at its commit message for rationale and let me know if you agree with it.
I had a look and I think it is a good idea - merge and apply make much more sense that interactive and am
Quoted 82 lines
Note that in particular, the last patch at the end could have been partially squashed into other patches, but if people objected to the patch in general I wanted Junio to be able to take the rest of the series and just drop that patch.
As with v4, there are some things mentioned earlier that are not addressed:
* I did not remove the --am (now --apply) option as suggested by Phillip, since Junio and Phillip were still discussing whether it is wanted/needed. I kind of like having it, but have no strong opinion.
* I noted the post-commit hook in the differences between backends. Emily is investigating what changes need to happen there, so I merely documented the existing differences.
* I did not address the last two items Jonathan brought up as I couldn't find enough information to reproduce or understand the problems.Elijah Newren (20):
git-rebase.txt: update description of --allow-empty-message
t3404: directly test the behavior of interest
rebase (interactive-backend): make --keep-empty the default rebase (interactive-backend): fix handling of commits that become
empty
t3406: simplify an already simple test
rebase, sequencer: remove the brokenGIT_QUIEThandling
rebase: make sure to pass along the quiet flag to the sequencer
rebase: fix handling ofrestrict_revision
t3432: make these tests work with either am or merge backends
rebase: allow more types of rebases to fast-forward git-rebase.txt: add more details about behavioral differences of
backends
rebase: move incompatibility checks between backend options a bit
earlier
rebase: add an --am option
git-prompt: change the prompt for interactive-based rebases
rebase: drop '-i' from the reflog for interactive-based rebases rebase tests: mark tests specific to the am-backend with --am rebase tests: repeat some tests using the merge backend instead of am
rebase: make the backend configurable via config setting
rebase: change the default backend from "am" to "merge"
rebase: rename the two primary rebase backendsDocumentation/config/rebase.txt | 6 + Documentation/git-rebase.txt | 170 +++++++++++++--- builtin/rebase.c | 257 +++++++++++++++++-------- contrib/completion/git-prompt.sh | 6 +- rebase-interactive.c | 7 +- rebase-interactive.h | 2 +- sequencer.c | 82 +++++--- sequencer.h | 3 +- t/t3400-rebase.sh | 40 +++- t/t3401-rebase-and-am-rename.sh | 4 +- t/t3404-rebase-interactive.sh | 19 +- t/t3406-rebase-message.sh | 19 +- t/t3407-rebase-abort.sh | 6 +- t/t3420-rebase-autostash.sh | 22 +-- t/t3421-rebase-topology-linear.sh | 48 ++--- t/t3424-rebase-empty.sh | 126 ++++++++++++ t/t3425-rebase-topology-merges.sh | 8 +- t/t3427-rebase-subtree.sh | 12 +- t/t3432-rebase-fast-forward.sh | 54 +++--- t/t5407-post-rewrite-hook.sh | 16 +- t/t5520-pull.sh | 27 ++- t/t6047-diff3-conflict-markers.sh | 13 +- t/t7512-status-help.sh | 12 +- t/t9106-git-svn-commit-diff-clobber.sh | 3 +- t/t9903-bash-prompt.sh | 8 +- 25 files changed, 693 insertions(+), 277 deletions(-)create mode 100755
t/t3424-rebase-empty.shbase-commit:
d0654dc308b0ba76dd8ed7bbb33c8d8f7aacd783
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v5
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v5
Pull-Request: https://github.com/git/git/pull/679Range-diff vs v4:
1: 3ea48d53940 = 1: 3ea48d53940 git-rebase.txt: update description of --allow-empty-message 2: 10fdd162a05 = 2: 10fdd162a05 t3404: directly test the behavior of interest 3: 179f82ab83e ! 3: 74cab9a32b3 rebase (interactive-backend): make --keep-empty the default@@ -4,13 +4,13 @@
Quoted 16 lines
Different rebase backends have different treatment for commits which start empty (i.e. have no changes relative to their parent), and the
- --keep-empty option was added at some point to allow adjusting behavior
- for the interactive backend. The handling of commits which start empty
- is actually quite similar to commitb00bf1c9a8dd(git-rebase: make
- --allow-empty-message the default, 2018-06-27), which pointed out that
- the behavior for various backends is often more happenstance than
- design. The specific change made in that commit is actually quite
- relevant as well and much of the logic there directly applies here.
+ --keep-empty option was added at some point to allow adjusting behavior.
+ The handling of commits which start empty is actually quite similar to
+ commitb00bf1c9a8dd(git-rebase: make --allow-empty-message the default,
+ 2018-06-27), which pointed out that the behavior for various backends is
+ often more happenstance than design. The specific change made in that
+ commit is actually quite relevant as well and much of the logic there
+ directly applies here.
Thanks for rewording this I think the new version is better for omitting the reference to the interactive backend. The interdiff for this patch and the next look good.
Thanks for working on this - it's turned into quite a large series
Best Wishes
Phillip
Quoted 349 lines
It makes a lot of sense in 'git commit' to error out on the creation of empty commits, unless an override flag is provided. However, once @@ -405,8 +405,6 @@ + test_cmp expect actual +' + -+GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR -+ +test_expect_success 'rebase --interactive with a variety of empty commits' ' + git checkout -B testing localmods && + test_must_fail git rebase --interactive upstream && 4: c9542a2abe0 ! 4: 54b745c4ab5 rebase (interactive-backend): fix handling of commits that become empty @@ -98,6 +98,8 @@ + With ask (implied by --interactive), the rebase will halt when + an empty commit is applied allowing you to choose whether to + drop it, edit files more, or just commit the empty changes. ++ Other options, like --exec, will use the default of drop unless ++ -i/--interactive is explicitly specified. ++ +Note that commits which start empty are kept, and commits which are +clean cherry-picks (as determined by `git log --cherry-mark ...`) are @@ -224,7 +226,7 @@ REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN), OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate), + OPT_CALLBACK_F(0, "empty", &options, N_("{drop,keep,ask}"), -+ N_("how to handle empty commits"), ++ N_("how to handle commits that become empty"), + PARSE_OPT_NONEG, parse_opt_empty), { OPTION_CALLBACK, 'k', "keep-empty", &options, NULL, N_("(DEPRECATED) keep empty commits"), @@ -342,8 +344,10 @@ - if (!opts->no_commit) { + } else if (allow == 2) { + drop_commit = 1; -+ fprintf(stderr, _("No changes -- Patch already applied.\n")); -+ } // else allow == 0 and there's nothing special to do ++ fprintf(stderr, ++ _("dropping %s %s -- patch contents already upstream\n"), ++ oid_to_hex(&commit->object.oid), msg.subject); ++ } /* else allow == 0 and there's nothing special to do */ + if (!opts->no_commit && !drop_commit) { if (author || command == TODO_REVERT || (flags & AMEND_MSG)) res = do_commit(r, msg_file, author, opts, flags); @@ -404,9 +408,7 @@ -test_expect_failure 'rebase --merge with a variety of empty commits' ' - test_when_finished "git rebase --abort" && +test_expect_success 'rebase --merge --empty=drop' ' - git checkout -B testing localmods && -- # rebase --merge should not halt on the commit that becomes empty -- git rebase --merge upstream && ++ git checkout -B testing localmods && + git rebase --merge --empty=drop upstream && + + test_write_lines D C B A >expect && @@ -414,6 +416,17 @@ + test_cmp expect actual +' + ++test_expect_success 'rebase --merge uses default of --empty=drop' ' + git checkout -B testing localmods && +- # rebase --merge should not halt on the commit that becomes empty + git rebase --merge upstream && + + test_write_lines D C B A >expect && +@@ + test_cmp expect actual + ' + +-test_expect_success 'rebase --interactive with a variety of empty commits' ' +test_expect_success 'rebase --merge --empty=keep' ' + git checkout -B testing localmods && + git rebase --merge --empty=keep upstream && @@ -428,14 +441,12 @@ + test_must_fail git rebase --merge --empty=ask upstream && + + git rebase --skip && - - test_write_lines D C B A >expect && - git log --format=%s >actual && -@@ - - GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR - --test_expect_success 'rebase --interactive with a variety of empty commits' ' ++ ++ test_write_lines D C B A >expect && ++ git log --format=%s >actual && ++ test_cmp expect actual ++' ++ +test_expect_success 'rebase --interactive --empty=drop' ' + git checkout -B testing localmods && + git rebase --interactive --empty=drop upstream && @@ -455,11 +466,19 @@ +' + +test_expect_success 'rebase --interactive --empty=ask' ' - git checkout -B testing localmods && -- test_must_fail git rebase --interactive upstream && ++ git checkout -B testing localmods && + test_must_fail git rebase --interactive --empty=ask upstream && - - git rebase --skip && ++ ++ git rebase --skip && ++ ++ test_write_lines D C B A >expect && ++ git log --format=%s >actual && ++ test_cmp expect actual ++' ++ ++test_expect_success 'rebase --interactive uses default of --empty=ask' ' + git checkout -B testing localmods && + test_must_fail git rebase --interactive upstream && diff --git a/t/t3427-rebase-subtree.sh b/t/t3427-rebase-subtree.sh 5: 9f66229d5cc = 5: f5170e71510 t3406: simplify an already simple test 6: 8d731fa39c3 = 6: 2791b818c4d rebase, sequencer: remove the broken GIT_QUIET handling 7: b6b6597eef8 = 7: 3dccb58d066 rebase: make sure to pass along the quiet flag to the sequencer 8: 0acefa988b1 = 8: 4e6f5ce8c46 rebase: fix handling of restrict_revision 9: 8c5b5b51330 = 9: bcd04ec4ded t3432: make these tests work with either am or merge backends 10: b8c087d6fb1 = 10: 45eb87b36b0 rebase: allow more types of rebases to fast-forward 11: b50a1741e0b = 11: 11e96b9a5fa git-rebase.txt: add more details about behavioral differences of backends 12: 58e6e4ffb3f = 12: 0c921919dce rebase: move incompatibility checks between backend options a bit earlier 13: 5478c730aca = 13: de1b0aaca88 rebase: add an --am option 14: db5e29bd818 = 14: e4c04d3cf8b git-prompt: change the prompt for interactive-based rebases 15: 413e190ac9a ! 15: f0f00c1e7fd rebase: drop '-i' from the reflog for interactive-based rebases@@ -54,17 +54,6 @@
*/
return error(_("%s: Unable to write new index file"),
_(action_name(opts)));
-@@
- int next =todo_list->current, offset, fd;
-
- /*
-- * rebase -i writes "git-rebase-todo" without the currently executing
-- * command, appending it to "done" instead.
-+ * interactive backend writes "git-rebase-todo" without the currently
-+ * executing command, appending it to "done" instead.
- */
- if (is_rebase_i(opts))
- next++;
@@
returnerror(_("illegal label name: '%.*s'"), len, name);16: 170be283a85 ! 16: 56486d4d694 rebase tests: mark tests specific to the am-backend with --am @@ -173,6 +173,163 @@ testrebase " --interactive" .git/rebase-merge+ diff --git
a/t/t3421-rebase-topology-linear.shb/t/t3421-rebase-topology-linear.sh
+ ---a/t/t3421-rebase-topology-linear.sh
+ +++b/t/t3421-rebase-topology-linear.sh
+@@
+test_linear_range'd e' c..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+test_run_rebasesuccess -m
+test_run_rebasesuccess -i
+test_have_prereq!REBASE_P ||test_run_rebasesuccess -p
+@@
+test_cmp_reve HEAD
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+test_run_rebasesuccess -m
+test_run_rebasesuccess -i
+test_have_prereq!REBASE_P ||test_run_rebasesuccess -p
+@@
+test_linear_range'd e' b..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+test_run_rebasesuccess --fork-point
+test_run_rebasesuccess -m
+test_run_rebasesuccess -i
+@@
+test_linear_range'd e' branch-b..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+test_run_rebasesuccess --fork-point
+test_run_rebasesuccess -m
+test_run_rebasesuccess -i
+@@
+test_cmp_reve HEAD
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+test_run_rebasesuccess --fork-point
+test_run_rebasesuccess -m
+test_run_rebasesuccess -i
+@@
+test_linear_range'd i' h..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+test_run_rebasesuccess -m
+test_run_rebasesuccess -i
+test_have_prereq!REBASE_P ||test_run_rebasesuccess -p
+@@
+test_linear_range'd' h..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+test_run_rebasesuccess -m
+test_run_rebasesuccess -i
+test_have_prereq!REBASE_P ||test_run_rebasesuccess -p
+@@
+test_linear_range'd i' f..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+test_run_rebasesuccess -m
+test_run_rebasesuccess -i
+test_have_prereq!REBASE_P ||test_run_rebasesuccess -p
+@@
+test_linear_range'd gp i' h..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+test_run_rebasesuccess -m
+test_run_rebasesuccess -i
+test_have_prereq!REBASE_P ||test_run_rebasesuccess -p
+@@
+test_linear_range'j d k l' c..
+ "
+ }
+-test_run_rebase failure ''
++test_run_rebase failure --am
+test_run_rebasesuccess -m
+test_run_rebasesuccess -i
+test_have_prereq!REBASE_P ||test_run_rebasefailure -p
+@@
+test_linear_range'd k l' c..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+test_run_rebasesuccess -m
+test_run_rebasesuccess -i
+test_have_prereq!REBASE_P ||test_run_rebasesuccess -p
+@@
+test_linear_range'd k l' j..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+test_run_rebasesuccess -m
+test_run_rebasesuccess -i
+test_have_prereq!REBASE_P ||test_run_rebasesuccess -p
+@@
+test_linear_range'x y' c..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+test_run_rebasesuccess -m
+test_run_rebasesuccess -i
+test_have_prereq!REBASE_P ||test_run_rebasesuccess -p
+@@
+test_linear_range'x y' c..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+test_run_rebasesuccess -m
+test_run_rebasesuccess -i
+test_have_prereq!REBASE_P ||test_run_rebasefailure -p
+@@
+test_linear_range'x y' m..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+test_run_rebasesuccess -m
+test_run_rebasesuccess -i
+test_have_prereq!REBASE_P ||test_run_rebasesuccess -p
+@@
+ "
+ }
+
+-test_run_rebase success ''
++test_run_rebase success --am
+test_run_rebasesuccess -m
+test_run_rebasesuccess -i
+test_have_prereq!REBASE_P ||test_run_rebasefailure -p
+@@
+test_linear_range'x y' m..
+ "
+ }
+-test_run_rebase success ''
++test_run_rebase success --am
+test_run_rebasesuccess -m
+test_run_rebasesuccess -i
+test_have_prereq!REBASE_P ||test_run_rebasefailure -p
+
diff --gita/t/t3425-rebase-topology-merges.shb/t/t3425-rebase-topology-merges.sh---a/t/t3425-rebase-topology-merges.sh
+++b/t/t3425-rebase-topology-merges.sh17: 1e3d4066c40 = 17: dc12574a8b3 rebase tests: repeat some tests using the merge backend instead of am 18: 9b4ac83d2d0 = 18: ba1f2699b39 rebase: make the backend configurable via config setting 19: 859a4a94d7f ! 19: 59faaa2f920 rebase: change the default backend from "am" to "merge"@@ -13,6 +13,7 @@
patches incorrectly.[2]
* lack of access to original commits means that conflict marker annotation has less information available.
+ * the am backend has safety problems with an ill-timed interrupt.Also, the merge/interactive backend have far more abilities, appear to currently have a slight performance advantage[3] and have room for more
@@ -38,6 +39,24 @@
+
Note that a rebase merge works by replaying each commit from the working branch on top of the <upstream> branch. Because of this, when a merge
+@@
+ should have the same behavior, though it is not clear which one is
+ correct.
+
++Interruptability
++~~~~~~~~~~~~~~~~
++
++The am backend has safety problems with an ill-timed interrupt; if the ++user presses Ctrl-C at the wrong time to try to abort the rebase, the ++rebase can enter a state where it cannot be aborted with a subsequent ++git rebase --abort. The interactive backend does not appear to ++suffer from the same shortcoming. (See ++https://lore.kernel.org/git/[email protected]/ for ++details.)
++
+ Miscellaneous differences
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+diff --git
a/builtin/rebase.cb/builtin/rebase.c
---a/builtin/rebase.c
-: ----------- > 20:ad8339aebf2rebase: rename the two primary rebase backends