rebase: make the default backend configurable
cover rebase: make the default backend configurable
This series does a lot of work around making the default rebase backend configurable:
* provide configurability of handling empty commits and iron out differences between backends
* increase/fix capabilities of the merge/interactive backend to make it workable as the default
* document the remaining differences in backends more thoroughly
* add an --am option for explicitly requesting the am-backend
* extend merge/interactive backend testing to put it on par with the am backend
* add a 'rebase.backend' config option and:
* switch the rebase.backend default value from 'am' to 'merge'.
Areas I'd like reviewers to focus, in priority order:
* Patch 15 (Are we ready to switch the default backend? Or should we leave this patch out for now? Junio suggested we may be ready or at least are close[1])
* Patch 1 (Does my empty handling make sense? Do others agree I fixed --keep-empty, or do they view it as breaking it?)
* Patch 8 (Do the updates to the documentation of behavioral differences make sense? Is it too long?) * Patch 11 (okay to change the git-completion shell prompt slightly, especially in light of patches 15 & 16? We did a prompt change previously when we merged the merge backend with the interactive one, so I assume so, but just want to make sure people have a chance to chime in.)
If it's too soon to switch from the 'am' to 'merge' backend, we can just drop the last patch and I'll resubmit it later. I generated this series mostly through switching the default first and then watching what broke, but moved the patch to the end to make it easy to drop.
Briefly, reasons for switching the default backend boil down to the fact that the am-backend drops information and thus limits what it can do. This manifests in different ways:
* lack of tree information that would allow us to warn users that new files in old directories might want to move along with the other files that were renamed with those directories[1]
* incorrect application of patches in the presence of non-unique context lines[2], which could be avoided with access to the original files involved.
* less information available to annotate conflict markers (since am creates fake ancestors and commits on top of them, and doesn't have access to the original commits)
[1] https://lore.kernel.org/git/[email protected]/
[2] https://lore.kernel.org/git/[email protected]/
[3]
https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/
Elijah Newren (15):
rebase: extend the options for handling of empty commits
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
contrib: change the prompt for am-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 | 181 +++++++++++++++++++----- contrib/completion/git-prompt.sh | 2 +- rebase-interactive.c | 4 +- rebase-interactive.h | 2 +- sequencer.c | 80 ++++++++--- sequencer.h | 6 +- t/t3400-rebase.sh | 36 ++++- t/t3401-rebase-and-am-rename.sh | 4 +- t/t3404-rebase-interactive.sh | 2 +- t/t3406-rebase-message.sh | 19 ++- t/t3407-rebase-abort.sh | 6 +- t/t3420-rebase-autostash.sh | 2 +- t/t3421-rebase-topology-linear.sh | 4 +- t/t3424-rebase-empty.sh | 89 ++++++++++++ t/t3425-rebase-topology-merges.sh | 8 +- t/t3427-rebase-subtree.sh | 16 ++- t/t3432-rebase-fast-forward.sh | 59 ++++---- t/t3433-rebase-options-compatibility.sh | 13 +- 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 | 6 +- 26 files changed, 582 insertions(+), 182 deletions(-) create mode 100755 t/t3424-rebase-empty.sh
base-commit: 12029dc57db23baef008e77db1909367599210ee
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v1
Pull-Request: https://github.com/git/git/pull/679
Signature · 1 line
gitgitgadget
PATCH 1/15 rebase: extend the options for handling of empty commits
From: Elijah Newren <[email protected]>
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
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.
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.
--- Documentation/git-rebase.txt | 35 ++++++------ builtin/rebase.c | 87 +++++++++++++++++++++++++++--- rebase-interactive.c | 4 +- rebase-interactive.h | 2 +- sequencer.c | 74 +++++++++++++++++++------ sequencer.h | 6 ++- t/t3421-rebase-topology-linear.sh | 4 +- t/t3424-rebase-empty.sh | 89 +++++++++++++++++++++++++++++++ t/t3427-rebase-subtree.sh | 16 +++--- 9 files changed, 267 insertions(+), 50 deletions(-) create mode 100755 t/t3424-rebase-empty.sh
Documentation/git-rebase.txt +20 -15
@@ -258,9 +258,25 @@ 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 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. + See also INCOMPATIBLE OPTIONS below.
@@ -569,6 +585,7 @@ are incompatible with the following options: * --interactive * --exec * --keep-empty + * --empty= * --edit-todo * --root when used in combination with --onto
@@ -580,6 +597,7 @@ In addition, the following pairs of options are incompatible: * --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
@@ -588,19 +606,6 @@ BEHAVIORAL DIFFERENCES 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 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. - Directory rename detection ~~~~~~~~~~~~~~~~~~~~~~~~~~
builtin/rebase.c +79 -8
161 lines not on this page · raw message
rebase-interactive.c +2 -2
@@ -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 (unsigned no_ask_empty,int command_count,const char *shortrevisions,const char *shortonto,struct strbuf *buf) {
@@ -81,7 +81,7 @@ void append_todo_help(unsigned keep_empty, int command_count,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)); }
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 (unsigned no_ask_empty,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 +59 -15
167 lines not on this page · raw message
sequencer.h +5 -1
@@ -39,7 +39,9 @@ struct replay_opts {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;
@@ -134,7 +136,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)
@@ -150,6 +152,8 @@ int sequencer_remove_state(struct replay_opts *opts);* `--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,
t/t3421-rebase-topology-linear.sh +2 -2
@@ -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 +89 -0
89 lines not on this page · raw message
t/t3427-rebase-subtree.sh +10 -6
@@ -85,23 +85,27 @@ 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 --empty=ask --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 && : 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 && 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' ' 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 && : 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 && 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
Re: [PATCH 01/15] rebase: extend the options for handling of empty commits
Quoted 8 lines · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
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:
Quoted 6 lines
--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
This looks like a logical and natural extension of the --keep-empty option.
After seeing the stress on "empty from the beginning and ending up to be empty" in the description, I somehow expected that we may be able to specify what happens to the empty commit separately, but that does not seem to be what the patch is about, which was somewhat disappointing.
builtin/rebase.c static int parse_opt_interactive(const struct option *opt, const char *arg, · line 1403
Quoted 10 lines
builtin/rebase.c +10
-0+static long parse_empty_value (const char *value) +{ +if (!value) +return EMPTY_UNSPECIFIED ; +else if (!strcasecmp (value,"drop" )) +return EMPTY_DROP ; +else if (!strcasecmp (value,"keep" )) +return EMPTY_KEEP ; +else if (!strcasecmp (value,"ask" )) +return EMPTY_ASK ;
Not an error but just silently ignored?
builtin/rebase.c static int parse_opt_interactive(const struct option *opt, const char *arg, · line 1410
Quoted 8 lines
builtin/rebase.c +7
-0+return EMPTY_UNSPECIFIED ; +} + +static int parse_opt_empty (const struct option *opt,const char *arg,int unset) +{ +struct rebase_options *options = opt->value; +long value =parse_empty_value (arg);
Ahh, OK.
Wouldn't it be better to make the variable and the parsing helper function of type "enum empty_type", not "long", just like the field in the rebase_options struct?
Quoted 8 lines
builtin/rebase.c +8
-0+BUG_ON_OPT_NEG (unset); +if (value <0 ) +return error (_ ("option empty accepts \"drop\", " +"\"keep\", and \"ask\"" )); + + options->empty = value; +return 0 ; +}
Re: [PATCH 01/15] rebase: extend the options for handling of empty commits
Quoted 18 lines · Junio C Hamano
Quoted 8 lines · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
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:
Quoted 6 lines
--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
Quoted 8 lines
This looks like a logical and natural extension of the --keep-empty option.
After seeing the stress on "empty from the beginning and ending up to be empty" in the description, I somehow expected that we may be able to specify what happens to the empty commit separately, but that does not seem to be what the patch is about, which was somewhat disappointing.
My main reason for stressing both was that I've commits and code that talked about "empty" commits, but which often only dealt with one of the two types (sometimes when I would expect both to have been addressed), so I was just trying to be careful in my explanation of what it was about.
If wanted, we could implement finer control, but I don't know what the UI would look like or what the range of options we might want. Do we want to mix all possible permutations and have two flags, e.g. --become-empty=drop --start-empty=keep or --start-empty=ask --become-empty=keep? Would --empty=<whatever> imply both --become-empty=<whatever> and --start-empty=<whatever> or would users need to specify both? Do we think all the permutations make sense, or would we want to limit users to some subset?
The current choice of three covered the things that I had wanted in the past, and which I think made for good defaults for various different flags, so I just implemented it. We could always extend it later.
Quoted 13 lines
builtin/rebase.c static int parse_opt_interactive(const struct option *opt, const char *arg, · line 1403
Quoted 11 lines
builtin/rebase.c +10
-0+static long parse_empty_value (const char *value) +{ +if (!value) +return EMPTY_UNSPECIFIED ; +else if (!strcasecmp (value,"drop" )) +return EMPTY_DROP ; +else if (!strcasecmp (value,"keep" )) +return EMPTY_KEEP ; +else if (!strcasecmp (value,"ask" )) +return EMPTY_ASK ;Not an error but just silently ignored?
Oops, I'll switch it to an error.
Quoted 11 lines
builtin/rebase.c static int parse_opt_interactive(const struct option *opt, const char *arg, · line 1410
Quoted 8 lines
builtin/rebase.c +7
-0+return EMPTY_UNSPECIFIED ; +} + +static int parse_opt_empty (const struct option *opt,const char *arg,int unset) +{ +struct rebase_options *options = opt->value; +long value =parse_empty_value (arg);Ahh, OK.
Quoted 3 lines
Wouldn't it be better to make the variable and the parsing helper function of type "enum
empty_type", not "long", just like the field in therebase_optionsstruct?
Indeed, I'll fix this up.
Re: [PATCH 01/15] rebase: extend the options for handling of empty commits
Quoted 19 lines · Elijah Newren
Quoted 4 lines · Junio C Hamano
"Elijah Newren via GitGitGadget" <[email protected]> writes:
Quoted 12 lines
builtin/rebase.c static int parse_opt_interactive(const struct option *opt, const char *arg, · line 1403
Quoted 10 lines
builtin/rebase.c +10
-0+static long parse_empty_value (const char *value) +{ +if (!value) +return EMPTY_UNSPECIFIED ; +else if (!strcasecmp (value,"drop" )) +return EMPTY_DROP ; +else if (!strcasecmp (value,"keep" )) +return EMPTY_KEEP ; +else if (!strcasecmp (value,"ask" )) +return EMPTY_ASK ;Not an error but just silently ignored?
Quoted 1 line
Oops, I'll switch it to an error.
By the way, diff.c's parse_algorithm_value() and diff_opt_diff_algorithm() behave the same way, where parse_algorithm_value() will return a negative value if it's not one of the valid known cases, and diff_opt_diff_algorithm() checks for negative and throws an error. Should I also modify those to put the error in parse_algorithm_value()? (Not part of this series, but as a consistency cleanup since it clearly surprised you and even I forgot that I had the calling function returning the error when I copied that style?)
Re: [PATCH 01/15] rebase: extend the options for handling of empty commits
In reply to Elijah NewrenQuoted 13 lines · Elijah Newren
Quoted 4 lines
Quoted 4 lines
builtin/rebase.c +4
-0+static long parse_empty_value (const char *value) +{ +if (!value) +return EMPTY_UNSPECIFIED ;Quoted 8 lines
builtin/rebase.c static int parse_opt_interactive(const struct option *opt, const char *arg, · line 1403
Quoted 6 lines
builtin/rebase.c +6
-0+else if (!strcasecmp (value,"drop" )) +return EMPTY_DROP ; +else if (!strcasecmp (value,"keep" )) +return EMPTY_KEEP ; +else if (!strcasecmp (value,"ask" )) +return EMPTY_ASK ;Not an error but just silently ignored?
Quoted 1 line
Oops, I'll switch it to an error.
Not necessarily (see "Ahh, OK" below).
Quoted 11 lines
Quoted 11 lines
builtin/rebase.c static int parse_opt_interactive(const struct option *opt, const char *arg, · line 1410
Quoted 8 lines
builtin/rebase.c +7
-0+return EMPTY_UNSPECIFIED ; +} + +static int parse_opt_empty (const struct option *opt,const char *arg,int unset) +{ +struct rebase_options *options = opt->value; +long value =parse_empty_value (arg);Ahh, OK.
Quoted 5 lines
Quoted 3 lines
Wouldn't it be better to make the variable and the parsing helper function of type "enum
empty_type", not "long", just like the field in therebase_optionsstruct?Indeed, I'll fix this up.
Thanks. I was wondering if there is something subtle I didn't see going on around here.
PATCH 2/15 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 3/15 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
@@ -718,8 +718,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)
@@ -1178,8 +1178,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
@@ -2688,8 +2688,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)
@@ -2698,8 +2696,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
Re: [PATCH 03/15] rebase, sequencer: remove the broken GIT_QUIET handling
Quoted 9 lines · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
The
GIT_QUIETenvironment 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.
Nice to see a useless code go ;-)
PATCH 4/15 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
@@ -126,6 +126,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts) 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 =
@@ -1506,7 +1507,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 5/15 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
@@ -364,8 +364,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
Re: [PATCH 05/15] rebase: fix handling of restrict_revision
Quoted 11 lines · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
restrict_revisionin 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 commit6ab54d17be3f("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.
Thanks.
Quoted 56 lines
Signed-off-by Elijah Newren <[email protected]>--- builtin/rebase.c | 4 ++-- t/t3400-rebase.sh | 20 +++++++++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-)builtin/rebase.c +2
-2@@ -364,8 +364,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 &&
PATCH 6/15 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 | 53 ++++++++++++++++------------------ 1 file changed, 25 insertions(+), 28 deletions(-)
t/t3432-rebase-fast-forward.sh +25 -28
@@ -44,19 +44,16 @@ 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 + wc -l .git/logs/HEAD >old && + wc -l .git/logs/HEAD >new && + test_line_count '-gt' $(($old + 2)) .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 +68,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 +88,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,13 +109,13 @@ 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 -test_rebase_same_head failure work same success work diff --fork-point --onto B B -test_rebase_same_head failure work same success work diff --fork-point --onto B... B +test_rebase_same_head success work same success work diff --fork-point --onto B B +test_rebase_same_head success work same success work diff --fork-point --onto B... B test_rebase_same_head success noop same success work diff --fork-point --onto master... master test_rebase_same_head success noop same success work diff --fork-point --keep-base master
Signature · 1 line
gitgitgadget
Re: [PATCH 06/15] t3432: make these tests work with either am or merge backends
Hi Elijah,
Quoted 7 lines · Elijah Newren via GitGitGadget
t/t3432-rebase-fast-forward.sh +0
-0@@ -44,19 +44,16 @@ test_rebase_same_head_ () { test_expect_$status "git rebase$flag $* with $changes is $what with $cmp HEAD" " oldhead=\$(git rev-parse HEAD) &&
t/t3432-rebase-fast-forward.sh test_rebase_same_head_ () { · line 52
Quoted 10 lines
t/t3432-rebase-fast-forward.sh +3
-3test_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 + wc -l .git/logs/HEAD >old && + wc -l .git/logs/HEAD >new &&
Are the above two lines some yet-to-be cleaned up leftovers? I can't see where old and new are being used elsewhere.
Quoted 12 lines
t/t3432-rebase-fast-forward.sh +2
-5+ test_line_count '-gt' $(($old + 2)) .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
Re: [PATCH 06/15] t3432: make these tests work with either am or merge backends
Quoted 16 lines · Denton Liu
Hi Elijah,
Quoted 12 lines · Elijah Newren via GitGitGadget
t/t3432-rebase-fast-forward.sh +1
-0@@ -44,19 +44,16 @@ 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
Quoted 8 lines
t/t3432-rebase-fast-forward.sh test_rebase_same_head_ () { · line 52
Quoted 5 lines
t/t3432-rebase-fast-forward.sh +2
-3- # Must check this case first, for 'is up to - # date, rebase forced[...]rewinding head' cases - test_i18ngrep 'rewinding head' stdout + wc -l .git/logs/HEAD >old && + wc -l .git/logs/HEAD >new &&Are the above two lines some yet-to-be cleaned up leftovers? I can't see where
oldandneware being used elsewhere.
Um, this whole section is broken and causes it to pass the test for stupid reasons. Not sure how I bungled that so badly. I'll send a re-roll with a corrected 6/15.
Quoted 13 lines
Quoted 13 lines
t/t3432-rebase-fast-forward.sh +2
-5+ test_line_count '-gt' $(($old + 2)) .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
PATCH 7/15 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
@@ -1497,6 +1497,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"),
@@ -1808,11 +1809,18 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.ignore_date) options.flags |=REBASE_FORCE ; +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,"--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++)))
@@ -2148,12 +2156,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 8/15 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 | 102 +++++++++++++++++++++--- t/t3433-rebase-options-compatibility.sh | 5 +- 2 files changed, 94 insertions(+), 13 deletions(-)
Documentation/git-rebase.txt +92 -10
@@ -409,13 +409,10 @@ your branch contains commits which were dropped, this option can be used with `--keep-base` in 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
@@ -609,9 +606,94 @@ There are some subtle differences how the backends behave. 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. + +--ignore-whitespace +~~~~~~~~~~~~~~~~~~~ + +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. + +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[]
t/t3433-rebase-options-compatibility.sh +2 -3
@@ -10,9 +10,8 @@ test_description='tests to ensure compatibility between am and interactive backe GIT_AUTHOR_DATE="1999-04-02T08:03:20+05:30" export GIT_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 &&
Signature · 1 line
gitgitgadget
PATCH 9/15 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
@@ -1910,6 +1910,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 :
@@ -1940,17 +1951,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 10/15 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 become empty (because they contain a subset of already upstream changes) or start empty. With drop
@@ -372,7 +379,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.
@@ -417,6 +424,7 @@ with `--keep-base` in order to drop those commits from your branch. --whitespace=<option>:: This flag is passed to the 'git apply' program (see linkgit:git-apply[1]) that applies the patch. + Implies --am. + See also INCOMPATIBLE OPTIONS below.
@@ -567,6 +575,7 @@ INCOMPATIBLE OPTIONS The following options: + * --am * --whitespace * -C
builtin/rebase.c +17 -1
@@ -1361,6 +1361,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) {
@@ -1550,6 +1562,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,
@@ -1910,7 +1926,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 11/15 contrib: change the prompt for am-based rebases
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
The prompt for am-based rebases was REBASE, while for interactive-based rebases was REBASE-i. A while ago, we switched merge-based rebases from using REBASE-m to REBASE-i via re-implementing the merge backend based on the interactive backend. We will soon be changing the default rebase backend to the interactive one, meaning the default prompt will be REBASE-i rather than REBASE. We have also noted in the documentation that currently am-specific options will be implemented in the interactive backend, and even the --am flag may eventually imply an interactive-based rebase. As such, change the prompt for an am-based rebase from REBASE to REBASE-a.
--- contrib/completion/git-prompt.sh | 2 +- t/t9903-bash-prompt.sh | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-)
contrib/completion/git-prompt.sh +1 -1
@@ -440,7 +440,7 @@ __git_ps1 () __git_eread "$g/rebase-apply/last" total if [ -f "$g/rebase-apply/rebasing" ]; then __git_eread "$g/rebase-apply/head-name" b - r="|REBASE" + r="|REBASE-a" elif [ -f "$g/rebase-apply/applying" ]; then r="|AM" else
t/t9903-bash-prompt.sh +3 -3
@@ -189,11 +189,11 @@ test_expect_success 'prompt - rebase merge' ' test_cmp expected "$actual" ' -test_expect_success 'prompt - rebase' ' - printf " (b2|REBASE 1/3)" >expected && +test_expect_success 'prompt - rebase am' ' + printf " (b2|REBASE-a 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
Re: [PATCH 11/15] contrib: change the prompt for am-based rebases
The subject prefix for this file is most of the time 'bash prompt' or 'git-prompt'.
Quoted 12 lines · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
The prompt for am-based rebases was REBASE, while for interactive-based rebases was REBASE-i. A while ago, we switched merge-based rebases from using REBASE-m to REBASE-i via re-implementing the merge backend based on the interactive backend. We will soon be changing the default rebase backend to the interactive one, meaning the default prompt will be REBASE-i rather than REBASE. We have also noted in the documentation that currently am-specific options will be implemented in the interactive backend, and even the --am flag may eventually imply an interactive-based rebase. As such, change the prompt for an am-based rebase from REBASE to REBASE-a.
I had a bit of a hard time following which rebase variant is implemented with which backend and when it was changed, and... etc.
Makes me wonder: do we really need those "-i", "-m" or "-a" suffixes? What benefit do they bring? Why don't we just say "REBASE" in the prompt?
Quoted 41 lines
Signed-off-by Elijah Newren <[email protected]>--- contrib/completion/git-prompt.sh | 2 +- t/t9903-bash-prompt.sh | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-)contrib/completion/git-prompt.sh +1
-1@@ -440,7 +440,7 @@ __git_ps1 () __git_eread "$g/rebase-apply/last" total if [ -f "$g/rebase-apply/rebasing" ]; then __git_eread "$g/rebase-apply/head-name" b - r="|REBASE" + r="|REBASE-a" elif [ -f "$g/rebase-apply/applying" ]; then r="|AM" elset/t9903-bash-prompt.sh +3
-3@@ -189,11 +189,11 @@ test_expect_success 'prompt - rebase merge' ' test_cmp expected "$actual" ' -test_expect_success 'prompt - rebase' ' - printf " (b2|REBASE 1/3)" >expected && +test_expect_success 'prompt - rebase am' ' + printf " (b2|REBASE-a 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
Re: [PATCH 11/15] contrib: change the prompt for am-based rebases
Quoted 18 lines · SZEDER Gábor
The subject prefix for this file is most of the time 'bash prompt' or 'git-prompt'.
Quoted 12 lines · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
The prompt for am-based rebases was REBASE, while for interactive-based rebases was REBASE-i. A while ago, we switched merge-based rebases from using REBASE-m to REBASE-i via re-implementing the merge backend based on the interactive backend. We will soon be changing the default rebase backend to the interactive one, meaning the default prompt will be REBASE-i rather than REBASE. We have also noted in the documentation that currently am-specific options will be implemented in the interactive backend, and even the --am flag may eventually imply an interactive-based rebase. As such, change the prompt for an am-based rebase from REBASE to REBASE-a.
Quoted 2 lines
I had a bit of a hard time following which rebase variant is implemented with which backend and when it was changed, and... etc.
Sorry about that. I could fix it up, but...
Quoted 3 lines
Makes me wonder: do we really need those "-i", "-m" or "-a" suffixes? What benefit do they bring? Why don't we just say "REBASE" in the prompt?
Ooh, I like this idea. A lot easier to explain in the commit message and probably less confusing for users as well.
PATCH 12/15 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/t3433-rebase-options-compatibility.sh | 8 ++++---- t/t5407-post-rewrite-hook.sh | 12 ++++++------ t/t7512-status-help.sh | 12 ++++++------ 11 files changed, 40 insertions(+), 40 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
@@ -1137,7 +1137,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/t3433-rebase-options-compatibility.sh +4 -4
@@ -51,9 +51,9 @@ test_expect_success '--ignore-whitespace works with am backend' ' new line 2 line 3 EOF - test_must_fail git rebase main side && + test_must_fail git rebase --am main side && git rebase --abort && - git rebase --ignore-whitespace main side && + git rebase --am --ignore-whitespace main side && test_cmp expect file '
@@ -71,7 +71,7 @@ test_expect_success '--ignore-whitespace works with interactive backend' ' 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_cmp authortime committertime
@@ -103,7 +103,7 @@ test_expect_success '--committer-date-is-author-date works with rebase -r' ' # 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 '
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 13/15 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 14/15 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;
@@ -103,6 +104,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 \
@@ -1298,6 +1300,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); }
@@ -1932,9 +1938,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) {
@@ -1947,10 +1967,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 15/15 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
@@ -309,7 +309,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
@@ -104,7 +104,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 \
@@ -1949,7 +1949,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,
Le 20/12/2019 à 18:09, Elijah Newren via GitGitGadget a écrit :
Quoted 103 lines
This series does a lot of work around making the default rebase backend configurable:
* provide configurability of handling empty commits and iron out differences between backends
* increase/fix capabilities of the merge/interactive backend to make it workable as the default
* document the remaining differences in backends more thoroughly
* add an --am option for explicitly requesting the am-backend
* extend merge/interactive backend testing to put it on par with the am backend
* add a 'rebase.backend' config option and:
* switch the rebase.backend default value from 'am' to 'merge'.Areas I'd like reviewers to focus, in priority order:
* Patch 15 (Are we ready to switch the default backend? Or should we leave this patch out for now? Junio suggested we may be ready or at least are close[1])
* Patch 1 (Does my empty handling make sense? Do others agree I fixed --keep-empty, or do they view it as breaking it?)
* Patch 8 (Do the updates to the documentation of behavioral differences make sense? Is it too long?) * Patch 11 (okay to change the git-completion shell prompt slightly, especially in light of patches 15 & 16? We did a prompt change previously when we merged the merge backend with the interactive one, so I assume so, but just want to make sure people have a chance to chime in.)If it's too soon to switch from the 'am' to 'merge' backend, we can just drop the last patch and I'll resubmit it later. I generated this series mostly through switching the default first and then watching what broke, but moved the patch to the end to make it easy to drop.
Briefly, reasons for switching the default backend boil down to the fact that the am-backend drops information and thus limits what it can do. This manifests in different ways:
* lack of tree information that would allow us to warn users that new files in old directories might want to move along with the other files that were renamed with those directories[1]
* incorrect application of patches in the presence of non-unique context lines[2], which could be avoided with access to the original files involved.
* less information available to annotate conflict markers (since am creates fake ancestors and commits on top of them, and doesn't have access to the original commits)[1] https://lore.kernel.org/git/[email protected]/
[2] https://lore.kernel.org/git/[email protected]/
[3]
https://lore.kernel.org/git/CABPp-BGiu2nVMQY_t-rnFR5GQUz_ipyEE8oDocKeO+h+t4Mn4A@mail.gmail.com/Elijah Newren (15):
rebase: extend the options for handling of empty commits
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
contrib: change the prompt for am-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 | 181 +++++++++++++++++++----- contrib/completion/git-prompt.sh | 2 +- rebase-interactive.c | 4 +- rebase-interactive.h | 2 +- sequencer.c | 80 ++++++++--- sequencer.h | 6 +- t/t3400-rebase.sh | 36 ++++- t/t3401-rebase-and-am-rename.sh | 4 +- t/t3404-rebase-interactive.sh | 2 +- t/t3406-rebase-message.sh | 19 ++- t/t3407-rebase-abort.sh | 6 +- t/t3420-rebase-autostash.sh | 2 +- t/t3421-rebase-topology-linear.sh | 4 +- t/t3424-rebase-empty.sh | 89 ++++++++++++ t/t3425-rebase-topology-merges.sh | 8 +- t/t3427-rebase-subtree.sh | 16 ++- t/t3432-rebase-fast-forward.sh | 59 ++++---- t/t3433-rebase-options-compatibility.sh | 13 +- 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 | 6 +- 26 files changed, 582 insertions(+), 182 deletions(-) create mode 100755 t/t3424-rebase-empty.sh
Quoted 5 lines
base-commit:
12029dc57db23baef008e77db1909367599210ee
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v1
Pull-Request: https://github.com/git/git/pull/679
BTW, I have investigated on the performance regression when split-index is enabled, I’ll give my conclusions next week.
Cheers,
Alban
Hi Alban,
Quoted 8 lines · Alban Gruin
Hi Elijah,
Quoted 3 lines · Elijah Newren via GitGitGadget
This series does a lot of work around making the default rebase backend configurable:
Quoted 5 lines
BTW, I have investigated on the performance regression when split-index is enabled, I’ll give my conclusions next week.
Cheers,
Alban
Nice!
v2 cover rebase: make the default backend configurable
In reply to Elijah Newren via GitGitGadgetThis series does a lot of work around making the default rebase backend configurable, and switching the default from the am backend to the merge/interactive one.
See the cover letter for v1[*] for a more detailed motivation for the series, the type of changes being made, and areas I'd like to reviewers to focus on.
Changes since v1:
* Made some cleanups to the empty type parsing, as suggested by Junio (Patch 1)
* Changed the shell prompt for all rebases to just be "REBASE", as suggested by SZEDER (Patch 11)
* Correctly check that work was done (by checking that reflog grew, patch 6); flagged by Denton
[*]
https://lore.kernel.org/git/[email protected]/
Elijah Newren (15):
rebase: extend the options for handling of empty commits
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
contrib: change the prompt 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 | 177 +++++++++++++++++++----- contrib/completion/git-prompt.sh | 4 +- rebase-interactive.c | 4 +- rebase-interactive.h | 2 +- sequencer.c | 80 ++++++++--- sequencer.h | 6 +- t/t3400-rebase.sh | 36 ++++- t/t3401-rebase-and-am-rename.sh | 4 +- t/t3404-rebase-interactive.sh | 2 +- t/t3406-rebase-message.sh | 19 ++- t/t3407-rebase-abort.sh | 6 +- t/t3420-rebase-autostash.sh | 2 +- t/t3421-rebase-topology-linear.sh | 4 +- t/t3424-rebase-empty.sh | 89 ++++++++++++ t/t3425-rebase-topology-merges.sh | 8 +- t/t3427-rebase-subtree.sh | 16 ++- t/t3432-rebase-fast-forward.sh | 54 ++++---- t/t3433-rebase-options-compatibility.sh | 13 +- 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 +- 26 files changed, 577 insertions(+), 182 deletions(-) create mode 100755 t/t3424-rebase-empty.sh
base-commit: 12029dc57db23baef008e77db1909367599210ee
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v2
Pull-Request: https://github.com/git/git/pull/679
Range-diff vs v1:
1: 13e2056e78 ! 1: 1c2b77e94d rebase: extend the options for handling of empty commits
@@ -219,28 +219,24 @@
return 0;
}
-+static long parse_empty_value(const char *value)
++static enum empty_type parse_empty_value(const char *value)
+{
-+ if (!value)
-+ return EMPTY_UNSPECIFIED;
-+ else if (!strcasecmp(value, "drop"))
++ if (!strcasecmp(value, "drop"))
+ return EMPTY_DROP;
+ else if (!strcasecmp(value, "keep"))
+ return EMPTY_KEEP;
+ else if (!strcasecmp(value, "ask"))
+ return EMPTY_ASK;
-+ return EMPTY_UNSPECIFIED;
++
++ 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;
-+ long value = parse_empty_value(arg);
++ enum empty_type value = parse_empty_value(arg);
+
+ BUG_ON_OPT_NEG(unset);
-+ if (value < 0)
-+ return error(_("option empty accepts \"drop\", "
-+ "\"keep\", and \"ask\""));
+
+ options->empty = value;
+ return 0;
2: 47ea99fb30 = 2: bd3c5ec155 t3406: simplify an already simple test
3: b99feebae1 = 3: 49388b79fd rebase, sequencer: remove the broken GIT_QUIET handling
4: 66bee10d1c = 4: 478479358f rebase: make sure to pass along the quiet flag to the sequencer
5: b84faa8684 = 5: ee26f5a161 rebase: fix handling of restrict_revision
6: 9b1ad46c58 ! 6: 34a69def33 t3432: make these tests work with either am or merge backends
@@ -37,9 +37,8 @@
- # Must check this case first, for 'is up to
- # date, rebase forced[...]rewinding head' cases
- test_i18ngrep 'rewinding head' stdout
-+ wc -l .git/logs/HEAD >old &&
-+ wc -l .git/logs/HEAD >new &&
-+ test_line_count '-gt' $(($old + 2)) .git/logs/HEAD
++ old=\$(wc -l <expect) &&
++ test_line_count '-gt' \$old .git/logs/HEAD
elif test $what = noop
then
- test_i18ngrep 'is up to date' stdout &&
@@ -108,10 +107,3 @@
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
--test_rebase_same_head failure work same success work diff --fork-point --onto B B
--test_rebase_same_head failure work same success work diff --fork-point --onto B... B
-+test_rebase_same_head success work same success work diff --fork-point --onto B B
-+test_rebase_same_head success work same success work diff --fork-point --onto B... B
- test_rebase_same_head success noop same success work diff --fork-point --onto master... master
- test_rebase_same_head success noop same success work diff --fork-point --keep-base master
-
7: a4073df910 = 7: f2c92853b4 rebase: allow more types of rebases to fast-forward
8: 90e8927ea0 = 8: b307340f7c git-rebase.txt: add more details about behavioral differences of backends
9: 5c31d664f3 = 9: 7c3f2e07f3 rebase: move incompatibility checks between backend options a bit earlier
10: 340bf71c5c = 10: 1df11f0b51 rebase: add an --am option
11: 1c3f8ba328 ! 11: 94b5a3051d contrib: change the prompt for am-based rebases @@ -1,17 +1,38 @@
Author: Elijah Newren <[email protected]>
- contrib: change the prompt for am-based rebases
-
- The prompt for am-based rebases was REBASE, while for interactive-based
- rebases was REBASE-i. A while ago, we switched merge-based rebases from
- using REBASE-m to REBASE-i via re-implementing the merge backend based
- on the interactive backend. We will soon be changing the default rebase
- backend to the interactive one, meaning the default prompt will be
- REBASE-i rather than REBASE. We have also noted in the documentation
- that currently am-specific options will be implemented in the
- interactive backend, and even the --am flag may eventually imply an
- interactive-based rebase. As such, change the prompt for an am-based
- rebase from REBASE to REBASE-a.
+ contrib: change the prompt for interactive-based rebases
+
+ 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".
@@ -19,26 +40,46 @@
--- a/contrib/completion/git-prompt.sh
+++ b/contrib/completion/git-prompt.sh
@@
- __git_eread "$g/rebase-apply/last" total
- if [ -f "$g/rebase-apply/rebasing" ]; then
- __git_eread "$g/rebase-apply/head-name" b
-- r="|REBASE"
-+ r="|REBASE-a"
- elif [ -f "$g/rebase-apply/applying" ]; then
- r="|AM"
- else
+ __git_eread "$g/rebase-merge/msgnum" step
+ __git_eread "$g/rebase-merge/end" total
+ if [ -f "$g/rebase-merge/interactive" ]; then
+- r="|REBASE-i"
++ r="|REBASE"
+ else
+- r="|REBASE-m"
++ r="|REBASE"
+ fi
+ else
+ if [ -d "$g/rebase-apply" ]; then
diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh --- a/t/t9903-bash-prompt.sh
+++ b/t/t9903-bash-prompt.sh
+@@
+ '
+
+ 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"
+@@
+ '
+
+ 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 &&
@@
test_cmp expected "$actual"
'
-test_expect_success 'prompt - rebase' '
-- printf " (b2|REBASE 1/3)" >expected &&
+test_expect_success 'prompt - rebase am' '
-+ printf " (b2|REBASE-a 1/3)" >expected &&
+ printf " (b2|REBASE 1/3)" >expected &&
git checkout b2 && test_when_finished "git checkout master" &&
- test_must_fail git rebase b1 b2 &&
12: bcac3b77e8 = 12: c905d288bf rebase tests: mark tests specific to the am-backend with --am 13: 81d0f18b3e = 13: 0287881361 rebase tests: repeat some tests using the merge backend instead of am 14: 04db4472d3 = 14: ec782e711c rebase: make the backend configurable via config setting 15: 999587933b = 15: 7adcbc0bc5 rebase: change the default backend from "am" to "merge"
Signature · 1 line
gitgitgadget
PATCH v2 1/15 rebase: extend the options for handling of empty commits
From: Elijah Newren <[email protected]>
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
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.
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.
--- Documentation/git-rebase.txt | 35 ++++++------ builtin/rebase.c | 83 +++++++++++++++++++++++++--- rebase-interactive.c | 4 +- rebase-interactive.h | 2 +- sequencer.c | 74 +++++++++++++++++++------ sequencer.h | 6 ++- t/t3421-rebase-topology-linear.sh | 4 +- t/t3424-rebase-empty.sh | 89 +++++++++++++++++++++++++++++++ t/t3427-rebase-subtree.sh | 16 +++--- 9 files changed, 263 insertions(+), 50 deletions(-) create mode 100755 t/t3424-rebase-empty.sh
Documentation/git-rebase.txt +20 -15
@@ -258,9 +258,25 @@ 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 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. + See also INCOMPATIBLE OPTIONS below.
@@ -569,6 +585,7 @@ are incompatible with the following options: * --interactive * --exec * --keep-empty + * --empty= * --edit-todo * --root when used in combination with --onto
@@ -580,6 +597,7 @@ In addition, the following pairs of options are incompatible: * --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
@@ -588,19 +606,6 @@ BEHAVIORAL DIFFERENCES 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 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. - Directory rename detection ~~~~~~~~~~~~~~~~~~~~~~~~~~
builtin/rebase.c +75 -8
157 lines not on this page · raw message
rebase-interactive.c +2 -2
@@ -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 (unsigned no_ask_empty,int command_count,const char *shortrevisions,const char *shortonto,struct strbuf *buf) {
@@ -81,7 +81,7 @@ void append_todo_help(unsigned keep_empty, int command_count,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)); }
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 (unsigned no_ask_empty,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 +59 -15
167 lines not on this page · raw message
sequencer.h +5 -1
@@ -39,7 +39,9 @@ struct replay_opts {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;
@@ -134,7 +136,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)
@@ -150,6 +152,8 @@ int sequencer_remove_state(struct replay_opts *opts);* `--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,
t/t3421-rebase-topology-linear.sh +2 -2
@@ -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 +89 -0
89 lines not on this page · raw message
t/t3427-rebase-subtree.sh +10 -6
@@ -85,23 +85,27 @@ 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 --empty=ask --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 && : 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 && 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' ' 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 && : 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 && 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 v2 2/15 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 v2 3/15 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
@@ -718,8 +718,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)
@@ -1178,8 +1178,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
@@ -2688,8 +2688,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)
@@ -2698,8 +2696,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 v2 4/15 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
@@ -126,6 +126,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts) 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 =
@@ -1502,7 +1503,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 v2 5/15 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
@@ -364,8 +364,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 v2 6/15 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 v2 7/15 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
@@ -1493,6 +1493,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"),
@@ -1804,11 +1805,18 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.ignore_date) options.flags |=REBASE_FORCE ; +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,"--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++)))
@@ -2144,12 +2152,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 v2 8/15 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 | 102 +++++++++++++++++++++--- t/t3433-rebase-options-compatibility.sh | 5 +- 2 files changed, 94 insertions(+), 13 deletions(-)
Documentation/git-rebase.txt +92 -10
@@ -409,13 +409,10 @@ your branch contains commits which were dropped, this option can be used with `--keep-base` in 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
@@ -609,9 +606,94 @@ There are some subtle differences how the backends behave. 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. + +--ignore-whitespace +~~~~~~~~~~~~~~~~~~~ + +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. + +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[]
t/t3433-rebase-options-compatibility.sh +2 -3
@@ -10,9 +10,8 @@ test_description='tests to ensure compatibility between am and interactive backe GIT_AUTHOR_DATE="1999-04-02T08:03:20+05:30" export GIT_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 &&
Signature · 1 line
gitgitgadget
PATCH v2 9/15 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
@@ -1906,6 +1906,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 :
@@ -1936,17 +1947,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 v2 10/15 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 become empty (because they contain a subset of already upstream changes) or start empty. With drop
@@ -372,7 +379,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.
@@ -417,6 +424,7 @@ with `--keep-base` in order to drop those commits from your branch. --whitespace=<option>:: This flag is passed to the 'git apply' program (see linkgit:git-apply[1]) that applies the patch. + Implies --am. + See also INCOMPATIBLE OPTIONS below.
@@ -567,6 +575,7 @@ INCOMPATIBLE OPTIONS The following options: + * --am * --whitespace * -C
builtin/rebase.c +17 -1
@@ -1361,6 +1361,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) {
@@ -1546,6 +1558,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,
@@ -1906,7 +1922,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 v2 11/15 contrib: 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 | 4 ++-- t/t9903-bash-prompt.sh | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-)
contrib/completion/git-prompt.sh +2 -2
@@ -430,9 +430,9 @@ __git_ps1 () __git_eread "$g/rebase-merge/msgnum" step __git_eread "$g/rebase-merge/end" total if [ -f "$g/rebase-merge/interactive" ]; then - r="|REBASE-i" + r="|REBASE" else - r="|REBASE-m" + r="|REBASE" fi else if [ -d "$g/rebase-apply" ]; then
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
Re: [PATCH v2 11/15] contrib: change the prompt for interactive-based rebases
Hi Elijah,
Quoted 1 line
Subject: contrib: change the prompt for interactive-based rebases
I'll also echo Gábor's comments and suggest that you use "git-prompt" for the change area.
Quoted 45 lines · Elijah Newren via GitGitGadget
From: 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 rebasesIt'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".
Signed-off-by Elijah Newren <[email protected]>--- contrib/completion/git-prompt.sh | 4 ++-- t/t9903-bash-prompt.sh | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-)contrib/completion/git-prompt.sh +0
-0
contrib/completion/git-prompt.sh __git_ps1 () · line 435
Quoted 8 lines
contrib/completion/git-prompt.sh +2
-2@@ -430,9 +430,9 @@ __git_ps1 () __git_eread "$g/rebase-merge/msgnum" step __git_eread "$g/rebase-merge/end" total if [ -f "$g/rebase-merge/interactive" ]; then - r="|REBASE-i" + r="|REBASE" else - r="|REBASE-m" + r="|REBASE"
We should just drop the if here since both arms are the same..
Thanks,
Denton
Quoted 42 lines
contrib/completion/git-prompt.sh +0
-0fi else if [ -d "$g/rebase-apply" ]; thent/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 v2 12/15 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/t3433-rebase-options-compatibility.sh | 8 ++++---- t/t5407-post-rewrite-hook.sh | 12 ++++++------ t/t7512-status-help.sh | 12 ++++++------ 11 files changed, 40 insertions(+), 40 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
@@ -1137,7 +1137,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/t3433-rebase-options-compatibility.sh +4 -4
@@ -51,9 +51,9 @@ test_expect_success '--ignore-whitespace works with am backend' ' new line 2 line 3 EOF - test_must_fail git rebase main side && + test_must_fail git rebase --am main side && git rebase --abort && - git rebase --ignore-whitespace main side && + git rebase --am --ignore-whitespace main side && test_cmp expect file '
@@ -71,7 +71,7 @@ test_expect_success '--ignore-whitespace works with interactive backend' ' 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_cmp authortime committertime
@@ -103,7 +103,7 @@ test_expect_success '--committer-date-is-author-date works with rebase -r' ' # 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 '
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 v2 13/15 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 v2 14/15 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;
@@ -103,6 +104,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 \
@@ -1298,6 +1300,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); }
@@ -1928,9 +1934,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) {
@@ -1943,10 +1963,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 v2 15/15 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
@@ -309,7 +309,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
@@ -104,7 +104,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 \
@@ -1945,7 +1945,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
v3 cover rebase: make the default backend configurable
In reply to Elijah Newren via GitGitGadgetThis series does a lot of work around making the default rebase backend configurable, and switching the default from the am backend to the merge/interactive one.
See the cover letter for v1[*] for a more detailed motivation for the series, the type of changes being made, and areas I'd like to reviewers to focus on.
Changes since v2:
* Tweaked patch 11 (commit message, and condensing if-else block that was same on both sides), as suggested by Denton
[*]
https://lore.kernel.org/git/[email protected]/
Elijah Newren (15):
rebase: extend the options for handling of empty commits
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 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 | 177 +++++++++++++++++++----- contrib/completion/git-prompt.sh | 6 +- rebase-interactive.c | 4 +- rebase-interactive.h | 2 +- sequencer.c | 80 ++++++++--- sequencer.h | 6 +- t/t3400-rebase.sh | 36 ++++- t/t3401-rebase-and-am-rename.sh | 4 +- t/t3404-rebase-interactive.sh | 2 +- t/t3406-rebase-message.sh | 19 ++- t/t3407-rebase-abort.sh | 6 +- t/t3420-rebase-autostash.sh | 2 +- t/t3421-rebase-topology-linear.sh | 4 +- t/t3424-rebase-empty.sh | 89 ++++++++++++ t/t3425-rebase-topology-merges.sh | 8 +- t/t3427-rebase-subtree.sh | 16 ++- t/t3432-rebase-fast-forward.sh | 54 ++++---- t/t3433-rebase-options-compatibility.sh | 13 +- 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 +- 26 files changed, 576 insertions(+), 185 deletions(-) create mode 100755 t/t3424-rebase-empty.sh
base-commit: 12029dc57db23baef008e77db1909367599210ee
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-679%2Fnewren%2Frebase-fixes-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-679/newren/rebase-fixes-v3
Pull-Request: https://github.com/git/git/pull/679
Range-diff vs v2:
1: 1c2b77e94d = 1: 1c2b77e94d rebase: extend the options for handling of empty commits
2: bd3c5ec155 = 2: bd3c5ec155 t3406: simplify an already simple test
3: 49388b79fd = 3: 49388b79fd rebase, sequencer: remove the broken GIT_QUIET handling
4: 478479358f = 4: 478479358f rebase: make sure to pass along the quiet flag to the sequencer
5: ee26f5a161 = 5: ee26f5a161 rebase: fix handling of restrict_revision
6: 34a69def33 = 6: 34a69def33 t3432: make these tests work with either am or merge backends
7: f2c92853b4 = 7: f2c92853b4 rebase: allow more types of rebases to fast-forward
8: b307340f7c = 8: b307340f7c git-rebase.txt: add more details about behavioral differences of backends
9: 7c3f2e07f3 = 9: 7c3f2e07f3 rebase: move incompatibility checks between backend options a bit earlier
10: 1df11f0b51 = 10: 1df11f0b51 rebase: add an --am option
11: 94b5a3051d ! 11: ff43593211 contrib: change the prompt for interactive-based rebases
@@ -1,6 +1,6 @@
Author: Elijah Newren <[email protected]>
- contrib: change the prompt for interactive-based rebases
+ git-prompt: change the prompt for interactive-based rebases
In the past, we had different prompts for different types of rebases:
REBASE: for am-based rebases
@@ -40,17 +40,18 @@
--- a/contrib/completion/git-prompt.sh
+++ b/contrib/completion/git-prompt.sh
@@
+ __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
+- if [ -f "$g/rebase-merge/interactive" ]; then
- r="|REBASE-i"
-+ r="|REBASE"
- else
+- else
- r="|REBASE-m"
-+ r="|REBASE"
- fi
+- fi
++ r="|REBASE"
else
if [ -d "$g/rebase-apply" ]; then
+ __git_eread "$g/rebase-apply/next" step
diff --git a/t/t9903-bash-prompt.sh b/t/t9903-bash-prompt.sh
--- a/t/t9903-bash-prompt.sh
12: c905d288bf = 12: 99388f24e5 rebase tests: mark tests specific to the am-backend with --am
13: 0287881361 = 13: c2ba6317bf rebase tests: repeat some tests using the merge backend instead of am
14: ec782e711c = 14: 8bec6df51a rebase: make the backend configurable via config setting
15: 7adcbc0bc5 = 15: 044853fd61 rebase: change the default backend from "am" to "merge"Signature · 1 line
gitgitgadget
PATCH v3 1/15 rebase: extend the options for handling of empty commits
From: Elijah Newren <[email protected]>
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
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.
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.
--- Documentation/git-rebase.txt | 35 ++++++------ builtin/rebase.c | 83 +++++++++++++++++++++++++--- rebase-interactive.c | 4 +- rebase-interactive.h | 2 +- sequencer.c | 74 +++++++++++++++++++------ sequencer.h | 6 ++- t/t3421-rebase-topology-linear.sh | 4 +- t/t3424-rebase-empty.sh | 89 +++++++++++++++++++++++++++++++ t/t3427-rebase-subtree.sh | 16 +++--- 9 files changed, 263 insertions(+), 50 deletions(-) create mode 100755 t/t3424-rebase-empty.sh
Documentation/git-rebase.txt +20 -15
@@ -258,9 +258,25 @@ 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 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. + See also INCOMPATIBLE OPTIONS below.
@@ -569,6 +585,7 @@ are incompatible with the following options: * --interactive * --exec * --keep-empty + * --empty= * --edit-todo * --root when used in combination with --onto
@@ -580,6 +597,7 @@ In addition, the following pairs of options are incompatible: * --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
@@ -588,19 +606,6 @@ BEHAVIORAL DIFFERENCES 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 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. - Directory rename detection ~~~~~~~~~~~~~~~~~~~~~~~~~~
builtin/rebase.c +75 -8
157 lines not on this page · raw message
rebase-interactive.c +2 -2
@@ -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 (unsigned no_ask_empty,int command_count,const char *shortrevisions,const char *shortonto,struct strbuf *buf) {
@@ -81,7 +81,7 @@ void append_todo_help(unsigned keep_empty, int command_count,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)); }
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 (unsigned no_ask_empty,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 +59 -15
167 lines not on this page · raw message
sequencer.h +5 -1
@@ -39,7 +39,9 @@ struct replay_opts {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;
@@ -134,7 +136,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)
@@ -150,6 +152,8 @@ int sequencer_remove_state(struct replay_opts *opts);* `--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,
t/t3421-rebase-topology-linear.sh +2 -2
@@ -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 +89 -0
89 lines not on this page · raw message
t/t3427-rebase-subtree.sh +10 -6
@@ -85,23 +85,27 @@ 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 --empty=ask --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 && : 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 && 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' ' 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 && : 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 && 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
Re: [PATCH v3 01/15] rebase: extend the options for handling of empty commits
Hi Elijah
Thanks for working on this series, I think making the sequencer the default backend is a good idea. I have a few reservations about this path though...
Quoted 8 lines · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
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:
Quoted 6 lines
--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
I think we want to distinguish between commits that are empty before
rebasing and those that become empty when they are rebased. --keep-empty explicily only applies to commits that are already empty. Cherry-pick
has distinct options for those two cases. If I've explicitly created an empty commit then I want to keep it but I don't want to keep commits that become empty because the changes they contain are already upstream.
If we want an option that keeps commits that become empty (Off hand I don't know why we would though) we should consider if that option should disable --cherry-mark when we create the todo list so that it keeps all commits that become empty when they're rebased.
Quoted 7 lines
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.
I don't see why it makes sense to drop an empty commit that I've made just because it is being rebased. I'm pretty sure the behavor of the am-based rebase is a function of git am not being able to create empty commits.
Quoted 5 lines
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.
Quoted 7 lines
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
I'm not sure I like changing the behavior based on --exec, I see what you're getting at but it has the potential to be confusing. What if I want to rearrange the commits without changing the base - why must I specify --empty=keep there but not if I add --exec to the command line?
Quoted 10 lines
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.
It certainly changes the behavior of --keep-empty but I'm not sure it "fixes" it. If I have some empty commits I want to keep as placeholders then that's different from wanting to keep commits that become empty because their changes are upstream but --cherry-mark didn't detect them.
In summary I'm in favor of making it easier to drop commits that become empty but not tying that to the handling of commits that are empty before they are rebased.
I'm also not happy that the deprecation of --keep-empty suddenly makes --no-keep-empty an error.
Quoted 164 lines
Signed-off-by Elijah Newren <[email protected]>--- Documentation/git-rebase.txt | 35 ++++++------ builtin/rebase.c | 83 +++++++++++++++++++++++++--- rebase-interactive.c | 4 +- rebase-interactive.h | 2 +- sequencer.c | 74 +++++++++++++++++++------ sequencer.h | 6 ++- t/t3421-rebase-topology-linear.sh | 4 +- t/t3424-rebase-empty.sh | 89 +++++++++++++++++++++++++++++++ t/t3427-rebase-subtree.sh | 16 +++--- 9 files changed, 263 insertions(+), 50 deletions(-) create mode 100755 t/t3424-rebase-empty.shDocumentation/git-rebase.txt +20
-15@@ -258,9 +258,25 @@ 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 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. + See also INCOMPATIBLE OPTIONS below.@@ -569,6 +585,7 @@ are incompatible with the following options: * --interactive * --exec * --keep-empty + * --empty= * --edit-todo * --root when used in combination with --onto@@ -580,6 +597,7 @@ In addition, the following pairs of options are incompatible: * --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@@ -588,19 +606,6 @@ BEHAVIORAL DIFFERENCES 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 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. - Directory rename detection ~~~~~~~~~~~~~~~~~~~~~~~~~~builtin/rebase.c +30
-2@@ -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;@@ -77,7 +85,6 @@ struct rebase_options {const char *action;int signoff;int allow_rerere_autoupdate; -int keep_empty;int autosquash;int ignore_whitespace;char *gpg_sign_opt;@@ -95,6 +102,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 \@@ -114,6 +122,10 @@ 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.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 =@@ -389,7 +401,10 @@ 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 |= (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 ;@@ -453,6 +468,19 @@ 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_NEG (unset); +BUG_ON_OPT_ARG (arg); + + opts->empty =EMPTY_KEEP ; + opts->type =REBASE_INTERACTIVE ; +return 0 ; +} +static const char *const builtin_rebase_interactive_usage[] = {N_ ("git rebase--interactive [<options>]" ),NULL
builtin/rebase.c int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) · line 499
Quoted 7 lines
builtin/rebase.c +3
-1@@ -466,7 +494,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_NONEG |PARSE_OPT_HIDDEN ,
It is all very well deprecating --keep-empty but suddenly making '--no-keep-empty' an error goes beyond deprecation. Also I'm not sure it's worth changing these options as I think the only user is git-rebase--preserve-merges.sh
Best Wishes
Phillip
sequencer.c static int populate_opts_cb(const char *key, const char *value, void *data)
Quoted 181 lines
builtin/rebase.c +42
-5+ 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")),@@ -1166,7 +1197,7 @@ 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,"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);@@ -1360,6 +1391,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 );@@ -1505,8 +1559,13 @@ 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" )), +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_keep_empty },OPT_BOOL (0 ,"autosquash" , &options.autosquash,N_ ("move commits that begin with " "squash!/fixup! under -i")),@@ -1770,8 +1829,8 @@ 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 (options.empty !=EMPTY_UNSPECIFIED ) +imply_interactive (&options,"--empty" );if (gpg_sign) {free (options.gpg_sign_opt);@@ -1856,6 +1915,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" ));rebase-interactive.c +2
-2@@ -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 (unsigned no_ask_empty,int command_count,const char *shortrevisions,const char *shortonto,struct strbuf *buf) {@@ -81,7 +81,7 @@ void append_todo_help(unsigned keep_empty, int command_count,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)); }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 (unsigned no_ask_empty,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 +19
-9@@ -160,6 +160,9 @@ 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 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) {@@ -1623,7 +1626,7 @@ static int allow_empty(struct repository *r, 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)return 0 ;else return 1 ;@@ -1837,7 +1840,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) {/* @@ -2042,13 +2045,20 @@ static int do_pick_commit(struct repository *r, 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 @@ -2501,9 +2511,15 @@ static int populate_opts_cb(const char *key, const char *value, void *data)
This function is used by cherry-pick/revert not rebase do we need to change it?
sequencer.c static int save_opts(struct replay_opts *opts)
Quoted 44 lines
sequencer.c +21
-0else 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" ))@@ -2612,6 +2628,15 @@ 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 ; + +if (file_exists (rebase_path_ask_on_initially_empty ())) + opts->ask_on_initially_empty =1 ; +read_strategy_opts (opts, &buf);strbuf_release (&buf);@@ -2695,6 +2720,12 @@ int write_basic_state(struct replay_opts *opts, const char *head_name,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" ,"" );@@ -3033,9 +3064,15 @@ static int save_opts(struct replay_opts *opts)
again this is for cherry-pick/revert
Quoted 267 lines
sequencer.c +19
-6if (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" );@@ -4691,7 +4728,8 @@ 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 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 ;@@ -4746,6 +4784,8 @@ static int make_script_with_merges(struct pretty_print_context *pp, 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);@@ -4754,7 +4794,7 @@ 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) +if (is_empty && ask_empty)strbuf_addf (&buf,"%c " , comment_line_char); strbuf_addf(&buf, "%s %s %s", cmd_pick, oid_to_hex(&commit->object.oid),@@ -4922,7 +4962,8 @@ 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 ; +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 ;@@ -4958,11 +4999,13 @@ 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) +if (is_empty && drop_empty) +continue ; +if (is_empty && ask_empty)strbuf_addf (out,"%c " , comment_line_char);strbuf_addf (out,"%s %s " , insn,oid_to_hex (&commit->object.oid));@@ -5100,7 +5143,8 @@ 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 (!(flags &TODO_LIST_ASK_EMPTY ), +count_commands (todo_list), shortrevisions, shortonto, &buf); res =write_message (buf.buf, buf.len, file,0 );sequencer.h +5
-1@@ -39,7 +39,9 @@ struct replay_opts {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;@@ -134,7 +136,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)@@ -150,6 +152,8 @@ int sequencer_remove_state(struct replay_opts *opts);* `--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,t/t3421-rebase-topology-linear.sh +2
-2@@ -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 # mt/t3424-rebase-empty.sh new file +89
-0@@ -0,0 +1,89 @@ +#!/bin/sh + +test_description='git rebase of commits that start or become empty' + +. ./test-lib.sh + +test_expect_success 'setup test repository' ' + test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers && + test_write_lines A B C D E F G H I J >letters && + git add numbers letters && + git commit -m A && + + git branch upstream && + git branch localmods && + + git checkout upstream && + test_write_lines A B C D E >letters && + git add letters && + git commit -m B && + + test_write_lines 1 2 3 4 five 6 7 8 9 ten >numbers && + git add numbers && + git commit -m C && + + git checkout localmods && + test_write_lines 1 2 3 4 five 6 7 8 9 10 >numbers && + git add numbers && + git commit -m C2 && + + git commit --allow-empty -m D && + + test_write_lines A B C D E >letters && + git add letters && + git commit -m "Five letters ought to be enough for anybody" +' + +test_expect_success 'rebase --merge --empty=drop' ' + 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 +' + +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 && + + test_must_fail git rebase --skip && + git commit --allow-empty && + git rebase --continue && + + test_write_lines D C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + +GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR + +test_expect_success 'rebase --interactive --empty=drop' ' + git checkout -B testing localmods && + git rebase --interactive --empty=drop 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 && + + test_write_lines D C2 C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + + +test_donet/t3427-rebase-subtree.sh +10
-6@@ -85,23 +85,27 @@ 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 --empty=ask --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 && : 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 && 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' ' 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 && : 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 && verbose test "$(commit_message HEAD~2)" = "master4" && verbose test "$(commit_message HEAD~)" = "files_subtree/master5" && verbose test "$(commit_message HEAD)" = "Empty commit"
Re: [PATCH v3 01/15] rebase: extend the options for handling of empty commits
Hi Phillip,
Quoted 6 lines · Phillip Wood
Hi Elijah
Thanks for working on this series, I think making the sequencer the default backend is a good idea. I have a few reservations about this path though...
Thanks for the feedback. You've provided some good food for thought. While responding, I came up with an alternate proposal below which I think may be more in line with what you want. Let me know...
Quoted 17 lines · Elijah Newren via GitGitGadget
Quoted 8 lines
From: Elijah Newren <[email protected]>
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:
Quoted 6 lines
--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
Quoted 3 lines
I think we want to distinguish between commits that are empty before rebasing and those that become empty when they are rebased. --keep-empty explicily only applies to commits that are already empty. Cherry-pick
I am open to changing how the empty handling works; I figured it was the most likely area people might have feedback about.
However, I do strongly disagree with the "explicit" claim here. It is possible that --keep-empty was designed to only apply to commits that begin empty, but that is not at all explicit. The only place in the documentation I could find that mentions the distinction at all was in the "Behavioral Differences" section, which was written by me as part of commit 0661e49aeb84 ("git-rebase.txt: document behavioral differences between modes", 2018-06-27). Quoting from that commit message:
"(In fact, it's not clear -- to me at least -- that these differences were even desirable or intentional.) Document these differences."
I cannot find anywhere else making a distinction in either the documentation or the commit messages or even any code comments, which has left me wondering whether the distinction was intentional at all. Things like commit b00bf1c9a8dd ("git-rebase: make --allow-empty-message the default", 2018-06-27) give heavy precedence to the assumption that git-rebase is often more happenstance than design when it comes to edge cases like these.
Quoted 8 lines
has distinct options for those two cases. If I've explicitly created an empty commit then I want to keep it but I don't want to keep commits that become empty because the changes they contain are already upstream.
If we want an option that keeps commits that become empty (Off hand I don't know why we would though) we should consider if that option should disable --cherry-mark when we create the todo list so that it keeps all commits that become empty when they're rebased.
To answer the particular reason I have seen why folks might want to keep both commits that start empty and become empty, but still use --cherry-mark to discard commits that are already upstream:
* If cherry-mark can determine the commit was "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).
* If a commit does not start empty, but becomes so because its
changes were a subset of those upstream, then while there are clearly no more changes that need to be applied, the commit messages can be expected to differ -- either there will be an upstream commit that included a bunch of other changes too, or the commit we are rebasing will be spread across multiple upstream commits. If the project has bad commit message hygiene (because e.g. they are using atrocious code review tools like GitHub that don't allow you to review them), there's a reasonable likelihood that the upstream version only talks about the other changes in the commit instead of the fix we are rebasing. If so, there may be useful information in this commit message that we want to copy somewhere.
It's a somewhat rare usecase involving projects that generally aren't careful with commit messages but which has some people working on it who are careful. But even for these projects, the odds that a commit message will just get tossed anyway seem fairly high, so I will not strongly defend this choice; it just seemed like "--keep-empty" or "--empty=keep" should mean "keep empty", not "sometimes keep empty". So, if it'd be nice if we could come up with clear wording if we are going to implement other possibilities.
Anyway, am I correctly understanding that you would like to see the following matrix of options?
* Drop commits that either start or become empty [the current --empty=drop]
* Pause and ask users whether they want commits that either start or
become empty [the current --empty=ask]
* Keep commits that start empty, but drop ones that become empty [a
new option]
and possibly drop the current --empty=keep behavior?
Quoted 10 lines
Quoted 7 lines
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.
I don't see why it makes sense to drop an empty commit that I've made just because it is being rebased.
Are you arguing that keeping commits that started empty should actually be the default, i.e. that all three rebase backends have traditionally had the wrong default?
Quoted 3 lines
I'm pretty sure the behavor of the
am-based rebase is a function ofgit amnot being able to create empty commits.
That may be, but it's not quite clear. Junio kind of commented on this in https://lore.kernel.org/git/[email protected]/.
His comments that "lack of --keep-empty on the 'am' side is probably a bug that wants to be fixed" might imply that he only sees it as a useful option. If the behavior of the am-based rebase dropping commits which started empty was solely a function of git-am not being able to create empty commits, then the correct fix would be to make keeping the commits which started empty as the default or maybe only behavior rather than just making it an option.
I'd actually be open to changing the default here. We have a precedent in doing so with commit b00bf1c9a8dd ("git-rebase: make --allow-empty-message the default", 2018-06-27) where we stated that really rare edge cases (which I think both empty commit messages and commits which start empty qualify as) probably have the wrong default in rebase. And changing the default would yield a much easier to understand matrix of options:
* Drop commits that become empty (modification to --empty=drop in
that it keeps commits which started empty)
* Pause and ask users if commits become empty (modification to
--empty=ask in that it keeps commits which started empty)
* Keep commits that become empty, in addition to those that started
empty (--empty=keep).
Quoted 13 lines
Quoted 5 lines
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.Quoted 7 lines
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
Quoted 4 lines
I'm not sure I like changing the behavior based on --exec, I see what you're getting at but it has the potential to be confusing. What if I want to rearrange the commits without changing the base - why must I specify --empty=keep there but not if I add --exec to the command line?
This was probably poorly explained and also poorly implemented. Let me try from a slightly different angle, and come to a slightly different result than I did previously:
Quoting from Junio in b00bf1c9a8dd ("git-rebase: make --allow-empty-message the default", 2018-06-27),
'"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'
I would modify Junio's wording to expand "am based rebase" to say that if the user hasn't requested an interactive rebase or otherwise explicitly signalled that they want some measure of interactivity (e.g. specified --empty=ask), then we should not stop and ask how to handle commits which started OR became empty. In other worse, it's not just the am-based rebase that is about transplanting commits but any rebase where interactivity isn't requested. I think the traditional --keep-empty is quite broken in this regard since it does stop and ask with commits that become empty. rebase -m, rebase --exec, etc. should not stop when a commit becomes empty, there should be some default it just uses unless the user also adds --interactive/-i/--empty=ask.
But, if we take your above suggestion to make keeping the commits which started empty not only the default but remove it from the options, then this becomes much easier. The choices become (kind of repeated from above):
[Keep the commits that started empty in all three cases, otherwise:]
* Drop commits that become empty (--empty=drop)
* Pause and ask users if commits become empty (--empty=ask)
* Keep commits that become empty (--empty=keep)
and the defaults are simply:
--interactive/-i: --empty=ask
everything else: --empty=drop
(And as a bonus, in the thread I've referenced twice above Junio mentioned -- in respect to commits that become empty -- that he thinks these are the right defaults. He didn't comment on the behavior of commits that start empty, which seems to be your primary concern.)
Quoted 11 lines
Quoted 10 lines
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.
Quoted 8 lines
It certainly changes the behavior of --keep-empty but I'm not sure it "fixes" it. If I have some empty commits I want to keep as placeholders then that's different from wanting to keep commits that become empty because their changes are upstream but --cherry-mark didn't detect them.
In summary I'm in favor of making it easier to drop commits that become empty but not tying that to the handling of commits that are empty before they are rebased.
Sounds like my modified proposal above may be in line with your tastes?
Quoted 2 lines
I'm also not happy that the deprecation of --keep-empty suddenly makes --no-keep-empty an error.
We could translate that to --empty=drop; does that sound reasonable?
[...]
Quoted 5 lines
Quoted 5 lines
builtin/rebase.c +0
-1@@ -466,7 +494,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" )),
Quoted 8 lines
builtin/rebase.c int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) · line 499
Quoted 3 lines
builtin/rebase.c +3
-0+ {OPTION_CALLBACK ,'k' ,"keep-empty" , &options,NULL , +N_ ("(DEPRECATED) keep empty commits" ), + PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,It is all very well deprecating --keep-empty but suddenly making '--no-keep-empty' an error goes beyond deprecation. Also I'm not sure it's worth changing these options as I think the only user is
git-rebase--preserve-merges.sh
Side track: Since git-rebase--preserve-merges.sh is deprecated and we want to get rid of it, and rebase-merges exists and is a better implementation of the original idea, can we just translate rebase -p into rebase -r and delete git-rebase--preserve-merges.sh? (With a few wrinkles, such as telling users in the middle of an existing preserve-merges-rebase that they either need to use an old version of git to continue their rebase or else abort the rebase?)
Quoted 4 lines
Best Wishes
Phillip
[...]
Quoted 2 lines
This function is used by cherry-pick/revert not rebase do we need to change it?
Possibly not; I can take a look. I had quite a difficult time trying to figure out how to make sure that --empty options were saved and restored across rebase --continue; it's not at all unlikely that I modified more than I needed to "for consistency".
[...]
Quoted 3 lines
Review static int save_opts(struct replay_opts *opts)
Quoted 1 line
(quoted diff) +0
-0@@ -3033,9 +3064,15 @@ static int save_opts(struct replay_opts *opts)again this is for cherry-pick/revert
Okay, will double check this too.
[...]
Re: [PATCH v3 01/15] rebase: extend the options for handling of empty commits
Hi Elijah
Thanks for such a detailed reply, I'll try and do it justice.
TLDR I like your new proposal
Quoted 35 lines · Elijah Newren
Hi Phillip,
Quoted 6 lines · Phillip Wood
Hi Elijah
Thanks for working on this series, I think making the sequencer the default backend is a good idea. I have a few reservations about this path though...
Thanks for the feedback. You've provided some good food for thought. While responding, I came up with an alternate proposal below which I think may be more in line with what you want. Let me know...
Quoted 17 lines · Elijah Newren via GitGitGadget
Quoted 8 lines
From: Elijah Newren <[email protected]>
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:
Quoted 6 lines
--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 basisQuoted 3 lines
I think we want to distinguish between commits that are empty before rebasing and those that become empty when they are rebased. --keep-empty explicily only applies to commits that are already empty. Cherry-pick
Quoted 6 lines
I am open to changing how the empty handling works; I figured it was the most likely area people might have feedback about.
However, I do strongly disagree with the "explicit" claim here. It is possible that --keep-empty was designed to only apply to commits that begin empty, but that is not at all explicit.
Maybe explicit wasn't the best choice of words. I was going off the implementation which takes pains to only allow and empty commit when the original was empty. This has been the case since the feature was
introduced in 90e1818f9a ("git-rebase: add keep_empty flag",
2012-04-20). I did some digging in the mail archive this morning and
found [1] which seems to confirm the intention was to preserve existing
empty commits, not keep those that become empty. This fits with the
discussion [2] which lead to this feature. The same patch series that
introduced --keep-empty for rebase introduced the two cherry-pick flags --allow-empty and --keep-redundant-commits to distinguish between the
two cases and used cherry-pick --allow-empty to implement rebase --keep-empty (and failed to provide a way to continue after stopping for a conflict resolution)
[1] https://lore.kernel.org/git/[email protected]/
[2]
https://lore.kernel.org/git/[email protected]/
Quoted 39 lines
The only place in the
documentation I could find that mentions the distinction at all was in the "Behavioral Differences" section, which was written by me as part of commit0661e49aeb84("git-rebase.txt: document behavioral differences between modes", 2015-06-27). Quoting from that commit message:"(In fact, it's not clear -- to me at least -- that these differences were even desirable or intentional.) Document these differences."
I cannot find anywhere else making a distinction in either the documentation or the commit messages or even any code comments, which has left me wondering whether the distinction was intentional at all. Things like commit
b00bf1c9a8dd("git-rebase: make --allow-empty-message the default", 2018-06-27) give heavy precedence to the assumption that git-rebase is often more happenstance than design when it comes to edge cases like these.Quoted 8 lines
has distinct options for those two cases. If I've explicitly created an empty commit then I want to keep it but I don't want to keep commits that become empty because the changes they contain are already upstream.
If we want an option that keeps commits that become empty (Off hand I don't know why we would though) we should consider if that option should disable --cherry-mark when we create the todo list so that it keeps all commits that become empty when they're rebased.
To answer the particular reason I have seen why folks might want to keep both commits that start empty and become empty, but still use --cherry-mark to discard commits that are already upstream:
* If cherry-mark can determine the commit was "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).
* If a commit does not start empty, but becomes so because its
changes were a subset of those upstream, then while there are clearly no more changes that need to be applied, the commit messages can be expected to differ -- either there will be an upstream commit that
Quoted 8 lines
included a bunch of other changes too, or the commit we are rebasing will be spread across multiple upstream commits. If the project has bad commit message hygiene (because e.g. they are using atrocious code review tools like GitHub that don't allow you to review them), there's a reasonable likelihood that the upstream version only talks about the other changes in the commit instead of the fix we are rebasing. If so, there may be useful information in this commit message that we want to copy somewhere.
That sounds reasonable, (I'm not sure what one would do with the commit message though as creating an empty commit wont be any use for blame or someone looking at the commit that introduced the change upstream. One could use add a note to the upstream commit but then there's the problem of sharing notes)
Quoted 9 lines
It's a somewhat rare usecase involving projects that generally aren't careful with commit messages but which has some people working on it who are careful. But even for these projects, the odds that a commit message will just get tossed anyway seem fairly high, so I will not strongly defend this choice; it just seemed like "--keep-empty" or "--empty=keep" should mean "keep empty", not "sometimes keep empty". So, if it'd be nice if we could come up with clear wording if we are going to implement other possibilities.
Quoted 3 lines
Anyway, am I correctly understanding that you would like to see the following matrix of options?
* Drop commits that either start or become empty [the current --empty=drop]
Yes please
Quoted 2 lines
* Pause and ask users whether they want commits that either start or
become empty [the current --empty=ask]
I'd prefer an option to keep the commits that start empty and ask about the ones that become empty (just like --keep-empty does now)
Quoted 2 lines
* Keep commits that start empty, but drop ones that become empty [a
new option]
Yes please
Quoted 1 line
and possibly drop the current --empty=keep behavior?
Yes as I'm not sure it's that useful, if users want to keep the information from the message of a commit that becomes empty I'm not sure just creating an empty commit is going to be much use in practice.
Quoted 8 lines
Quoted 8 lines
Quoted 7 lines
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.
Quoted 6 lines
Quoted 2 lines
I don't see why it makes sense to drop an empty commit that I've made just because it is being rebased.
Are you arguing that keeping commits that started empty should actually be the default, i.e. that all three rebase backends have traditionally had the wrong default?
Yes, I think rebase should preserve the commits that are being rebased as far as possible and the current default is probably a historical accident
Quoted 4 lines
Quoted 3 lines
I'm pretty sure the behavor of the
am-based rebase is a function ofgit amnot being able to create empty commits.
Quoted 9 lines
That may be, but it's not quite clear. Junio kind of commented on this in https://lore.kernel.org/git/[email protected]/.
His comments that "lack of --keep-empty on the 'am' side is probably a bug that wants to be fixed" might imply that he only sees it as a useful option. If the behavior of the am-based rebase dropping commits which started empty was solely a function of git-am not being able to create empty commits, then the correct fix would be to make keeping the commits which started empty as the default or maybe only behavior rather than just making it an option.
Yeah I'm not sure how Junio feels defaulting to keeping commits that start empty
Quoted 1 line
I'd actually be open to changing the default here.
I'd be keen to do that but we should try and find out what others think.
Quoted 8 lines
We have a
precedent in doing so with commitb00bf1c9a8dd("git-rebase: make --allow-empty-message the default", 2018-06-27) where we stated that really rare edge cases (which I think both empty commit messages and commits which start empty qualify as) probably have the wrong default in rebase. And changing the default would yield a much easier to understand matrix of options:
Quoted 6 lines
* Drop commits that become empty (modification to --empty=drop in
that it keeps commits which started empty)
* Pause and ask users if commits become empty (modification to
--empty=ask in that it keeps commits which started empty)
* Keep commits that become empty, in addition to those that started
empty (--empty=keep).
That matrix is easier to understand and covers all the cases I'm interested in.
Quoted 48 lines
Quoted 13 lines
Quoted 5 lines
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.Quoted 7 lines
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: dropQuoted 4 lines
I'm not sure I like changing the behavior based on --exec, I see what you're getting at but it has the potential to be confusing. What if I want to rearrange the commits without changing the base - why must I specify --empty=keep there but not if I add --exec to the command line?
This was probably poorly explained and also poorly implemented. Let me try from a slightly different angle, and come to a slightly different result than I did previously:
Quoting from Junio in
b00bf1c9a8dd("git-rebase: make --allow-empty-message the default", 2018-06-27),
'"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'I would modify Junio's wording to expand "am based rebase" to say that if the user hasn't requested an interactive rebase or otherwise explicitly signalled that they want some measure of interactivity (e.g. specified --empty=ask), then we should not stop and ask how to handle commits which started OR became empty. In other worse, it's not just the am-based rebase that is about transplanting commits but any rebase where interactivity isn't requested. I think the traditional --keep-empty is quite broken in this regard since it does stop and ask with commits that become empty. rebase -m, rebase --exec, etc. should not stop when a commit becomes empty, there should be some default it just uses unless the user also adds --interactive/-i/--empty=ask.
But, if we take your above suggestion to make keeping the commits which started empty not only the default but remove it from the options, then this becomes much easier. The choices become (kind of repeated from above):
Quoted 4 lines
[Keep the commits that started empty in all three cases, otherwise:]
* Drop commits that become empty (--empty=drop)
* Pause and ask users if commits become empty (--empty=ask)
* Keep commits that become empty (--empty=keep)
That's nice - I like it
Quoted 3 lines
and the defaults are simply:
--interactive/-i: --empty=ask everything else: --empty=drop
While I see the point about the difference between -i and the other rebase modes any flavor of rebase can stop at any time due to conflicts so it can never be fully automatic. Given that commits becoming empty is a bit of a corner case I'm not sure the complexity of different defaults is justified but I don't feel that strongly about it.
Quoted 21 lines
(And as a bonus, in the thread I've referenced twice above Junio mentioned -- in respect to commits that become empty -- that he thinks these are the right defaults. He didn't comment on the behavior of commits that start empty, which seems to be your primary concern.)
Quoted 16 lines
Quoted 10 lines
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.
It certainly changes the behavior of --keep-empty but I'm not sure it "fixes" it. If I have some empty commits I want to keep as placeholders then that's different from wanting to keep commits that become empty because their changes are upstream but --cherry-mark didn't detect them.
Quoted 5 lines
Quoted 3 lines
In summary I'm in favor of making it easier to drop commits that become empty but not tying that to the handling of commits that are empty before they are rebased.
Sounds like my modified proposal above may be in line with your tastes?
Yes. Keep commits that start empty and have an option to control what happens to those that become empty (defaulting to --empty=ask for interactive rebases at least).
Quoted 4 lines
Quoted 2 lines
I'm also not happy that the deprecation of --keep-empty suddenly makes --no-keep-empty an error.
We could translate that to --empty=drop; does that sound reasonable?
translating it to --empty=ask would keep the behavior the same even if it sounds a bit odd. I don't think we want to change the behavior of --[no-]keep-empty while it is deprecated.
Quoted 16 lines
[...]
Quoted 5 lines
Quoted 5 lines
builtin/rebase.c +0
-1@@ -466,7 +494,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" )),Quoted 8 lines
builtin/rebase.c int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) · line 499
Quoted 3 lines
builtin/rebase.c +3
-0+ {OPTION_CALLBACK ,'k' ,"keep-empty" , &options,NULL , +N_ ("(DEPRECATED) keep empty commits" ), + PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,It is all very well deprecating --keep-empty but suddenly making '--no-keep-empty' an error goes beyond deprecation. Also I'm not sure it's worth changing these options as I think the only user is
git-rebase--preserve-merges.sh
Quoted 7 lines
Side track: Since
git-rebase--preserve-merges.shis deprecated and we want to get rid of it, and rebase-merges exists and is a better implementation of the original idea, can we just translate rebase -p into rebase -r and deletegit-rebase--preserve-merges.sh? (With a few wrinkles, such as telling users in the middle of an existing preserve-merges-rebase that they either need to use an old version of git to continue their rebase or else abort the rebase?)
dscho posted some patches in November or December moving along this path, I'm not sure what the outcome was. It's a bit complicated by the different todo list formats between -p and -r but I think the end game should be to treat -p as -r as you suggest.
Quoted 23 lines
Quoted 5 lines
Best Wishes
Phillip
[...]
Quoted 2 lines
This function is used by cherry-pick/revert not rebase do we need to change it?
Possibly not; I can take a look. I had quite a difficult time trying to figure out how to make sure that --empty options were saved and restored across rebase --continue; it's not at all unlikely that I modified more than I needed to "for consistency".
[...]
Quoted 3 lines
Review static int save_opts(struct replay_opts *opts)
Quoted 1 line
(quoted diff) +0
-0@@ -3033,9 +3064,15 @@ static int save_opts(struct replay_opts *opts)again this is for cherry-pick/revert
Okay, will double check this too.
[...]
Re: [PATCH v3 01/15] rebase: extend the options for handling of empty commits
Hi Phillip,
Quoted 33 lines · Phillip Wood
Quoted 24 lines · Elijah Newren
Quoted 2 lines · Phillip Wood
On 24/12/2019 19:54, Elijah Newren via GitGitGadget wrote:
[...]
Quoted 8 lines
Quoted 8 lines
builtin/rebase.c +1
-1@@ -466,7 +494,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 ,Quoted 8 lines
Review int cmd_rebase__interactive(int argc, const char · line 502
Quoted 3 lines
(quoted diff) +2
-0+ N_("(DEPRECATED) keep empty commits"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_HIDDEN,It is all very well deprecating --keep-empty but suddenly making '--no-keep-empty' an error goes beyond deprecation. Also I'm not sure it's worth changing these options as I think the only user is
git-rebase--preserve-merges.shQuoted 7 lines
Side track: Since
git-rebase--preserve-merges.shis deprecated and we want to get rid of it, and rebase-merges exists and is a better implementation of the original idea, can we just translate rebase -p into rebase -r and deletegit-rebase--preserve-merges.sh? (With a few wrinkles, such as telling users in the middle of an existing preserve-merges-rebase that they either need to use an old version of git to continue their rebase or else abort the rebase?)
Quoted 4 lines
dscho posted some patches in November or December moving along this path, I'm not sure what the outcome was. It's a bit complicated by the different todo list formats between -p and -r but I think the end game should be to treat -p as -r as you suggest.
I sent some patches to switch git svn over to rebase -r, but that was only in non-interactive mode.
In interactive mode, the todo lists indeed look too different and I think we'll just need to wait a year or so and then we can just delete git-rebase--preserve-merges.sh.
Ciao,
Dscho
PATCH v3 2/15 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 v3 3/15 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
@@ -718,8 +718,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)
@@ -1178,8 +1178,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
@@ -2688,8 +2688,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)
@@ -2698,8 +2696,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 v3 4/15 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
@@ -126,6 +126,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts) 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 =
@@ -1502,7 +1503,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 v3 5/15 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
@@ -364,8 +364,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 v3 6/15 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 v3 7/15 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
@@ -1493,6 +1493,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"),
@@ -1804,11 +1805,18 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.ignore_date) options.flags |=REBASE_FORCE ; +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,"--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++)))
@@ -2144,12 +2152,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 v3 8/15 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 | 102 +++++++++++++++++++++--- t/t3433-rebase-options-compatibility.sh | 5 +- 2 files changed, 94 insertions(+), 13 deletions(-)
Documentation/git-rebase.txt +92 -10
@@ -409,13 +409,10 @@ your branch contains commits which were dropped, this option can be used with `--keep-base` in 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
@@ -609,9 +606,94 @@ There are some subtle differences how the backends behave. 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. + +--ignore-whitespace +~~~~~~~~~~~~~~~~~~~ + +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. + +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[]
t/t3433-rebase-options-compatibility.sh +2 -3
@@ -10,9 +10,8 @@ test_description='tests to ensure compatibility between am and interactive backe GIT_AUTHOR_DATE="1999-04-02T08:03:20+05:30" export GIT_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 &&
Signature · 1 line
gitgitgadget
PATCH v3 9/15 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
@@ -1906,6 +1906,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 :
@@ -1936,17 +1947,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 v3 10/15 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 become empty (because they contain a subset of already upstream changes) or start empty. With drop
@@ -372,7 +379,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.
@@ -417,6 +424,7 @@ with `--keep-base` in order to drop those commits from your branch. --whitespace=<option>:: This flag is passed to the 'git apply' program (see linkgit:git-apply[1]) that applies the patch. + Implies --am. + See also INCOMPATIBLE OPTIONS below.
@@ -567,6 +575,7 @@ INCOMPATIBLE OPTIONS The following options: + * --am * --whitespace * -C
builtin/rebase.c +17 -1
@@ -1361,6 +1361,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) {
@@ -1546,6 +1558,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,
@@ -1906,7 +1922,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
Re: [PATCH v3 10/15] rebase: add an --am option
Hi Elijah
Quoted 6 lines · Elijah Newren via GitGitGadget
From: 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.
I wonder if we really want to add a new option that we know we don't want to keep in the long term. Could we not just default to the interactive backend and fallback to the am backend if the user passes any of the am-based options on the commandline? i.e. reverse the current situation of defaulting to the am backend and selecting the interactive backend when the user passes certain options.
Best Wishes
Phillip
Quoted 93 lines
Signed-off-by Elijah Newren <[email protected]>--- 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 become empty (because they contain a subset of already upstream changes) or start empty. With drop@@ -372,7 +379,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.@@ -417,6 +424,7 @@ with `--keep-base` in order to drop those commits from your branch. --whitespace=<option>:: This flag is passed to the 'git apply' program (see linkgit:git-apply[1]) that applies the patch. + Implies --am. + See also INCOMPATIBLE OPTIONS below.@@ -567,6 +575,7 @@ INCOMPATIBLE OPTIONS The following options: + * --am * --whitespace * -Cbuiltin/rebase.c +17
-1@@ -1361,6 +1361,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) {@@ -1546,6 +1558,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,@@ -1906,7 +1922,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" ))
Re: [PATCH v3 10/15] rebase: add an --am option
Hi Phillip,
Quoted 11 lines · Phillip Wood
Hi Elijah
Quoted 6 lines · Elijah Newren via GitGitGadget
From: 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.
Quoted 6 lines
I wonder if we really want to add a new option that we know we don't want to keep in the long term. Could we not just default to the interactive backend and fallback to the am backend if the user passes any of the am-based options on the commandline? i.e. reverse the current situation of defaulting to the am backend and selecting the interactive backend when the user passes certain options.
Good question. For context, this option served a few purposes:
* Providing an escape hatch during the transition in case the merge
backend doesn't handle everything the am one does in our first attempts (i.e. similar to how we allowed using the scripted rebase even after providing a built-in one).
* Makes the documentation much more concise and clear (being able to
specify that various flags "imply --am" is simple, whereas removing the flag makes me feel I need the full description of --am in each of the few places a flag that would have implied it).
* Provides an easy way to make the plethora of am-specific rebase
tests use the am-backend.
However, I thought of this option before Junio suggested a rebase.backend config setting, so we could just rely on that instead. Thus, getting rid of the "--am" flag in detail would mean:
* I need to redo the test changes in this series to use "-c
rebase.backend=am" instead of "--am"
* It will be slightly harder for users to use the escape hatch in
one-off cases during the transition
* We need to figure out the right way to reword the documentation
The first two are pretty minor, so that probably means I just need to come up with some good wording for the documentation (suggestions welcome...)
Elijah
Re: [PATCH v3 10/15] rebase: add an --am option
Quoted 9 lines · Elijah Newren
However, I thought of this option before Junio suggested a rebase.backend config setting, so we could just rely on that instead. Thus, getting rid of the "--am" flag in detail would mean:
* I need to redo the test changes in this series to use "-c
rebase.backend=am" instead of "--am"
* It will be slightly harder for users to use the escape hatch in
one-off cases during the transition
* We need to figure out the right way to reword the documentation
Quoted 3 lines
The first two are pretty minor, so that probably means I just need to come up with some good wording for the documentation (suggestions welcome...)
It probably is a good idea to keep --am (and possibly --[no-]am) as long as rebase.backend option exists. A configuration variable that changes behaviour without allowing it to be overridden from the command line is not a good idea.
Re: [PATCH v3 10/15] rebase: add an --am option
Quoted 11 lines · Junio C Hamano
Quoted 9 lines · Elijah Newren
However, I thought of this option before Junio suggested a rebase.backend config setting, so we could just rely on that instead. Thus, getting rid of the "--am" flag in detail would mean:
* I need to redo the test changes in this series to use "-c
rebase.backend=am" instead of "--am"
* It will be slightly harder for users to use the escape hatch in
one-off cases during the transition
* We need to figure out the right way to reword the documentation
Quoted 8 lines
Quoted 3 lines
The first two are pretty minor, so that probably means I just need to come up with some good wording for the documentation (suggestions welcome...)
It probably is a good idea to keep --am (and possibly --[no-]am) as long as rebase.backend option exists. A configuration variable that changes behaviour without allowing it to be overridden from the command line is not a good idea.
I view this change in the default backend as similar to the rewrite in C in that it is an implementation detail we're changing that should be transparent (and beneficial in terms of performance) to the user. There we provided a configuration variable but not a command line option to control if it was used or not. I don't see it as something the user will want to change from rebase to rebase but perhaps I'm missing something.
Best Wishes
Phillip
Re: [PATCH v3 10/15] rebase: add an --am option
Quoted 5 lines · Phillip Wood
I view this change in the default backend as similar to the rewrite in C in that it is an implementation detail we're changing that should be transparent (and beneficial in terms of performance) to the user. There we provided a configuration variable but not a command line option to control if it was used or not.
Do you mean things like GIT_TEST_ADD_I_USE_BUILTIN? I think it is OK to have such an escape mechanism to allow people to opt out of a new reimplementation until it matures, but I do not offhand recall doing so with config.
Re: [PATCH v3 10/15] rebase: add an --am option
Quoted 8 lines · Junio C Hamano
Quoted 5 lines · Phillip Wood
I view this change in the default backend as similar to the rewrite in C in that it is an implementation detail we're changing that should be transparent (and beneficial in terms of performance) to the user. There we provided a configuration variable but not a command line option to control if it was used or not.
Quoted 4 lines
Do you mean things like
GIT_TEST_ADD_I_USE_BUILTIN? I think it is OK to have such an escape mechanism to allow people to opt out of a new reimplementation until it matures, but I do not offhand recall doing so with config.
I was thinking of rebase.usebuiltin
Best Wishes
Phillip
PATCH v3 11/15 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 v3 12/15 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/t3433-rebase-options-compatibility.sh | 8 ++++---- t/t5407-post-rewrite-hook.sh | 12 ++++++------ t/t7512-status-help.sh | 12 ++++++------ 11 files changed, 40 insertions(+), 40 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
@@ -1137,7 +1137,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/t3433-rebase-options-compatibility.sh +4 -4
@@ -51,9 +51,9 @@ test_expect_success '--ignore-whitespace works with am backend' ' new line 2 line 3 EOF - test_must_fail git rebase main side && + test_must_fail git rebase --am main side && git rebase --abort && - git rebase --ignore-whitespace main side && + git rebase --am --ignore-whitespace main side && test_cmp expect file '
@@ -71,7 +71,7 @@ test_expect_success '--ignore-whitespace works with interactive backend' ' 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_cmp authortime committertime
@@ -103,7 +103,7 @@ test_expect_success '--committer-date-is-author-date works with rebase -r' ' # 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 '
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 v3 13/15 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 v3 14/15 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;
@@ -103,6 +104,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 \
@@ -1298,6 +1300,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); }
@@ -1928,9 +1934,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) {
@@ -1943,10 +1963,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 v3 15/15 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
@@ -309,7 +309,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
@@ -104,7 +104,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 \
@@ -1945,7 +1945,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
Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
Hi,
Quoted 22 lines · Elijah Newren via GitGitGadget
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/
Quoted 7 lines
Signed-off-by Elijah Newren <[email protected]>--- 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(-)
Thanks for writing this. We finally rolled this out to our internal population at $DAYJOB and ran into a couple of issues:
1. "git rebase --am" does not invoke the post-commit hook, but "git rebase --merge" does. Is this behavior change intended?
Noticed because jiri[1] installs a post-commit hook that warns about commits on detached HEAD, so this change makes rebases more noisy in repositories that were set up using jiri.
2. GIT_REFLOG_ACTION contains "rebase -i" even though the rebase is not interactive.
3. In circumstances I haven't pinned down yet, we get the error
message "invalid date format: @@2592000 +0000":
$ git rebase --committer-date-is-author-date --onto branch_K branch_L~1 branch_L
$ git checkout --theirs file
$ git add file
$ git rebase --continue
fatal: invalid date format: @@2592000 +0000
error: could not commit staged changes.
This isn't reproducible without --committer-date-is-author-date.
More context (the test where it happens) is in [2].4. I suspect the exit status in the "you need to resolve conflicts" case has changed. With rebase --am, [3] would automatically invoke rebase --abort when conflicts are present, but with rebase --merge it does not.
Known?
Thanks,
Jonathan
[1] https://fuchsia.googlesource.com/jiri/+/60436c301224231cb99be41ce937dfc223bee272/project/manifest.go#1347
[2] https://source.chromium.org/chromium/chromium/tools/depot_tools/+/master:tests/git_common_test.py;l=721;drc=6b52dc21e166c46707b4c8eb26c74c70d4f9977e;bpv=1;bpt=0
[3] https://fuchsia.googlesource.com/jiri/+/60436c301224231cb99be41ce937dfc223bee272/project/project.go#1664
Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
Hi Jonathan,
Quoted 35 lines · Jonathan Nieder
Hi,
Quoted 22 lines · Elijah Newren via GitGitGadget
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/Quoted 7 lines
Signed-off-by Elijah Newren <[email protected]>--- 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(-)
Quoted 2 lines
Thanks for writing this. We finally rolled this out to our internal population at $DAYJOB and ran into a couple of issues:
Cool, thanks for testing it out.
Quoted 6 lines
1. "git rebase --am" does not invoke the post-commit hook, but "git rebase --merge" does. Is this behavior change intended?
Noticed because jiri[1] installs a post-commit hook that warns about commits on detached HEAD, so this change makes rebases more noisy in repositories that were set up using jiri.
I've never used a post-commit hook or seen one in the wild. Certainly wasn't intentional, but it's not clear to me if it's wrong or right either. I don't see why it would make sense to distinguish between any of git rebase --am/--merge/--interactive, but it isn't too surprising that by historical accident the two rebase backends which happened to call git-commit behind the scenes would call a post-commit hook and the other rebase backend that didn't call git-commit wouldn't.
But the big question here, is what is correct behavior? Should rebase call the post-commit hook, or should it skip it? I haven't any clue what the answer to that is.
Quoted 2 lines
2.
GIT_REFLOG_ACTIONcontains "rebase -i" even though the rebase is not interactive.
Yep, as does --keep, --exec, --rebase-merges, etc. There are lots of rebases which use the interactive machinery even if they aren't explicitly interactive. I've never seen the "-i" in the reflog message defined, but clearly it has always been used whenever the interactive machinery was in play regardless of whether the rebase was interactive. In that regard, I figured that --merge fit in rather nicely. (And I noted the fact that reflog messages were different between the backends among the "BEHAVIORAL DIFFERENCES" section of git-rebase.txt). But if others think we should just drop the -i (much as we did for the bash prompt), I'd be happy with that too. If we go that route, I think I'd rather drop the -i in the reflog for all rebases, not just the using-the-interactive-machinery-but-not-explicitly-interactive ones.
Quoted 12 lines
3. In circumstances I haven't pinned down yet, we get the error
message "invalid date format: @@2592000 +0000": $ git rebase --committer-date-is-author-date --onto branch_K branch_L~1 branch_L $ git checkout --theirs file $ git add file $ git rebase --continue fatal: invalid date format: @@2592000 +0000 error: could not commit staged changes. This isn't reproducible without --committer-date-is-author-date. More context (the test where it happens) is in [2].
Interesting. Do you happen to know if this started happening with ra/rebase-i-more-options, or did it just become an issue with en/rebase-backend? I looked around at the link you provided and feel a bit confused; I'm not sure which test does this or how I'd reproduce.
Quoted 6 lines
4. I suspect the exit status in the "you need to resolve conflicts" case has changed. With rebase --am, [3] would automatically invoke rebase --abort when conflicts are present, but with rebase --merge it does not.
Known?
Nope, but I would certainly hope that "you need to resolve conflicts" would result in a non-zero exit status. If it doesn't, that sounds like a bug in the interactive backend that we need to fix. I'll dig in.
Thanks for the reports!
Elijah
Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
Hi Jonathan & Elijah
Quoted 38 lines · Elijah Newren
Hi Jonathan,
Quoted 35 lines · Jonathan Nieder
Hi,
Quoted 29 lines · Elijah Newren via GitGitGadget
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/Signed-off-by Elijah Newren <[email protected]>--- 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(-)
Quoted 11 lines
Quoted 2 lines
Thanks for writing this. We finally rolled this out to our internal population at $DAYJOB and ran into a couple of issues:
Cool, thanks for testing it out.
Quoted 6 lines
1. "git rebase --am" does not invoke the post-commit hook, but "git rebase --merge" does. Is this behavior change intended?
Noticed because jiri[1] installs a post-commit hook that warns about commits on detached HEAD, so this change makes rebases more noisy in repositories that were set up using jiri.
Perhaps that hook could learn not to warn if a branch is being rebased? git could be more helpful there by having a porcelain option to status that prints the branch name if we're rebasing (git worktree --list shows the branch correctly when it's being rebased but does not (yet - I have a patch to do it) mark the current worktree so isn't very helpful.)
Quoted 8 lines
I've never used a post-commit hook or seen one in the wild. Certainly wasn't intentional, but it's not clear to me if it's wrong or right either. I don't see why it would make sense to distinguish between any of git rebase --am/--merge/--interactive, but it isn't too surprising that by historical accident the two rebase backends which happened to call git-commit behind the scenes would call a post-commit hook and the other rebase backend that didn't call git-commit wouldn't.
Looking through the history the am based rebase has never run the post-commit hook as am has its own set of hooks and the scripted version used commit-tree. The merge based rebase ran git commit which ran the post commit hook. The interactive rebase ran the hook until and I broke it in a356ee4659b ("sequencer: try to commit without forking 'git commit'", 2017-11-24) and after I fixed it in 4627bc777e ("sequencer: run post-commit hook", 2019-10-15). As it was broken for two years with no one noticing it can't be that popular.
Quoted 3 lines
But the big question here, is what is correct behavior? Should rebase call the post-commit hook, or should it skip it? I haven't any clue what the answer to that is.
It's creating a new commit so I lean towards thinking it should run the post-commit hook. As an example I have a post-commit hook that prints a warning if a commit is created on a branch that is being rewritten by one of my scripts in another worktree. There are pre-commit and pre-rebase hooks to try and prevent that, but the warning is there as a last resort if those hooks are by-passed.
Quoted 2 lines
Quoted 2 lines
2.
GIT_REFLOG_ACTIONcontains "rebase -i" even though the rebase is not interactive.
If this is important to people I think it should be easy enough to set GIT_REFLOG_ACTION to the appropriate string in builtin/rebase.c (so long as it hasn't already been set by the user) rather than relying on sequencer.c to do it.
Quoted 24 lines
Yep, as does --keep, --exec, --rebase-merges, etc. There are lots of rebases which use the interactive machinery even if they aren't explicitly interactive. I've never seen the "-i" in the reflog message defined, but clearly it has always been used whenever the interactive machinery was in play regardless of whether the rebase was interactive. In that regard, I figured that --merge fit in rather nicely. (And I noted the fact that reflog messages were different between the backends among the "BEHAVIORAL DIFFERENCES" section of git-rebase.txt). But if others think we should just drop the -i (much as we did for the bash prompt), I'd be happy with that too. If we go that route, I think I'd rather drop the -i in the reflog for all rebases, not just the using-the-interactive-machinery-but-not-explicitly-interactive ones.
Quoted 10 lines
3. In circumstances I haven't pinned down yet, we get the error
message "invalid date format: @@2592000 +0000": $ git rebase --committer-date-is-author-date --onto branch_K branch_L~1 branch_L $ git checkout --theirs file $ git add file $ git rebase --continue fatal: invalid date format: @@2592000 +0000 error: could not commit staged changes.
Quoted 8 lines
Quoted 2 lines
This isn't reproducible without --committer-date-is-author-date. More context (the test where it happens) is in [2].
Interesting. Do you happen to know if this started happening with ra/rebase-i-more-options, or did it just become an issue with en/rebase-backend? I looked around at the link you provided and feel a bit confused; I'm not sure which test does this or how I'd reproduce.
I'm confused by the test as well. As ra/rebase-i-more-options only touched the sequencer then any bugs would only show up in this test (which runs a non-interactive rebase) once en/rbease-backend switched to that backend. It seems likely that ra/rebase-i-more-options is to blame.
Jonathan - do you happen to know if your users create empty commits at all? and if so what do they expect rebase to do with them (and any that become empty when they are rebased) - cf https://lore.kernel.org/git/<CABPp-BEH=9qejeqysHYE+AJ+JPaBympZizq-bx_OjArYFa4xUQ@mail.gmail.com>
Best Wishes
Phillip
Quoted 17 lines
Quoted 6 lines
4. I suspect the exit status in the "you need to resolve conflicts" case has changed. With rebase --am, [3] would automatically invoke rebase --abort when conflicts are present, but with rebase --merge it does not.
Known?
Nope, but I would certainly hope that "you need to resolve conflicts" would result in a non-zero exit status. If it doesn't, that sounds like a bug in the interactive backend that we need to fix. I'll dig in.
Thanks for the reports!
Elijah
Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
Hi,
Quoted 28 lines · Phillip Wood
Quoted 11 lines · Elijah Newren
Quoted 9 lines · Jonathan Nieder
Elijah Newren via GitGitGadget wrote:
1. "git rebase --am" does not invoke the post-commit hook, but "git rebase --merge" does. Is this behavior change intended?
Noticed because jiri[1] installs a post-commit hook that warns about commits on detached HEAD, so this change makes rebases more noisy in repositories that were set up using jiri.
Perhaps that hook could learn not to warn if a branch is being rebased? git could be more helpful there by having a porcelain option to status that prints the branch name if we're rebasing (
git worktree --listshows the branch correctly when it's being rebased but does not (yet - I have a patch to do it) mark the current worktree so isn't very helpful.)Quoted 8 lines
I've never used a post-commit hook or seen one in the wild. Certainly wasn't intentional, but it's not clear to me if it's wrong or right either. I don't see why it would make sense to distinguish between any of git rebase --am/--merge/--interactive, but it isn't too surprising that by historical accident the two rebase backends which happened to call git-commit behind the scenes would call a post-commit hook and the other rebase backend that didn't call git-commit wouldn't.
Quoted 8 lines
Looking through the history the am based rebase has never run the post-commit hook as am has its own set of hooks and the scripted version used commit-tree. The merge based rebase ran
git commitwhich ran the post commit hook. The interactive rebase ran the hook until and I broke it ina356ee4659b("sequencer: try to commit without forking 'git commit'", 2017-11-24) and after I fixed it in4627bc777e("sequencer: run post-commit hook", 2019-10-15). As it was broken for two years with no one noticing it can't be that popular.
Maybe a crazy idea, but maybe not: how about running the post-commit hook _only_ if --merge was specified explicitly, and in that case (and guarded behind a check verifying that the post-commit hook _actually_ exists _and_ is executable) warn the user that this hook won't be run in future versions?
To make things better for users who actually want to run that hook during rebases, we could introduce a config option, say, rebase.runPostCommitHook that is a tri-state (true, false, onlyForDashDashMerge, at first defaulting to the last, eventually to false).
Crazy? Or helpful?
Quoted 10 lines
Quoted 3 lines
But the big question here, is what is correct behavior? Should rebase call the post-commit hook, or should it skip it? I haven't any clue what the answer to that is.
It's creating a new commit so I lean towards thinking it should run the post-commit hook. As an example I have a post-commit hook that prints a warning if a commit is created on a branch that is being rewritten by one of my scripts in another worktree. There are pre-commit and pre-rebase hooks to try and prevent that, but the warning is there as a last resort if those hooks are by-passed.
I guess you're right, it is quite surprising that the post-commit hook is _not_ run for --am rebases even though commits are created.
Quoted 7 lines
Quoted 2 lines
Quoted 2 lines
2.
GIT_REFLOG_ACTIONcontains "rebase -i" even though the rebase is not interactive.If this is important to people I think it should be easy enough to set
GIT_REFLOG_ACTIONto the appropriate string inbuiltin/rebase.c(so long as it hasn't already been set by the user) rather than relying onsequencer.cto do it.
I agree (but won't have time to implement it, so maybe I should shut up already...)
Quoted 39 lines
Quoted 25 lines
Yep, as does --keep, --exec, --rebase-merges, etc. There are lots of rebases which use the interactive machinery even if they aren't explicitly interactive. I've never seen the "-i" in the reflog message defined, but clearly it has always been used whenever the interactive machinery was in play regardless of whether the rebase was interactive. In that regard, I figured that --merge fit in rather nicely. (And I noted the fact that reflog messages were different between the backends among the "BEHAVIORAL DIFFERENCES" section of git-rebase.txt). But if others think we should just drop the -i (much as we did for the bash prompt), I'd be happy with that too. If we go that route, I think I'd rather drop the -i in the reflog for all rebases, not just the using-the-interactive-machinery-but-not-explicitly-interactive ones.
Quoted 11 lines
3. In circumstances I haven't pinned down yet, we get the error
message "invalid date format: @@2592000 +0000": $ git rebase --committer-date-is-author-date --onto branch_K branch_L~1 branch_L $ git checkout --theirs file $ git add file $ git rebase --continue fatal: invalid date format: @@2592000 +0000 error: could not commit staged changes.Quoted 8 lines
Quoted 2 lines
This isn't reproducible without --committer-date-is-author-date. More context (the test where it happens) is in [2].
Interesting. Do you happen to know if this started happening with ra/rebase-i-more-options, or did it just become an issue with en/rebase-backend? I looked around at the link you provided and feel a bit confused; I'm not sure which test does this or how I'd reproduce.
I'm confused by the test as well. As ra/rebase-i-more-options only touched the sequencer then any bugs would only show up in this test (which runs a non-interactive rebase) once en/rbease-backend switched to that backend. It seems likely that ra/rebase-i-more-options is to blame.
Quoted 4 lines
Jonathan - do you happen to know if your users create empty commits at all? and if so what do they expect rebase to do with them (and any that become empty when they are rebased) - cf
https://lore.kernel.org/git/<CABPp-BEH=9qejeqysHYE+AJ+JPaBympZizq-bx_OjArYFa4xUQ@mail.gmail.com>
The double @ looks very funny. I would be interested in seeing an MCVE.
Quoted 11 lines
Quoted 11 lines
Quoted 6 lines
4. I suspect the exit status in the "you need to resolve conflicts" case has changed. With rebase --am, [3] would automatically invoke rebase --abort when conflicts are present, but with rebase --merge it does not.
Known?
Nope, but I would certainly hope that "you need to resolve conflicts" would result in a non-zero exit status. If it doesn't, that sounds like a bug in the interactive backend that we need to fix. I'll dig in.
Yes, exiting with status 0 would be a major bug, and I think it might even be a bug that was introduced by me when I re-implemented the core loop of the interactive rebase in C.
But to me it sounds as if 4. is not so about the exit code but about aborting immediately. I do not recall seeing --am rebases to abort, though, but to exit with error (and I saw the same behavior in interactive rebases).
We will need to see a reduced concrete example (preferably as a new test case) of the described behavior.
Ciao,
Dscho
Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
Hi,
Quoted 48 lines · Johannes Schindelin
Hi,
Quoted 28 lines · Phillip Wood
Quoted 11 lines · Elijah Newren
Quoted 9 lines · Jonathan Nieder
Elijah Newren via GitGitGadget wrote:
1. "git rebase --am" does not invoke the post-commit hook, but "git rebase --merge" does. Is this behavior change intended?
Noticed because jiri[1] installs a post-commit hook that warns about commits on detached HEAD, so this change makes rebases more noisy in repositories that were set up using jiri.
Perhaps that hook could learn not to warn if a branch is being rebased? git could be more helpful there by having a porcelain option to status that prints the branch name if we're rebasing (
git worktree --listshows the branch correctly when it's being rebased but does not (yet - I have a patch to do it) mark the current worktree so isn't very helpful.)Quoted 8 lines
I've never used a post-commit hook or seen one in the wild. Certainly wasn't intentional, but it's not clear to me if it's wrong or right either. I don't see why it would make sense to distinguish between any of git rebase --am/--merge/--interactive, but it isn't too surprising that by historical accident the two rebase backends which happened to call git-commit behind the scenes would call a post-commit hook and the other rebase backend that didn't call git-commit wouldn't.
Quoted 8 lines
Looking through the history the am based rebase has never run the post-commit hook as am has its own set of hooks and the scripted version used commit-tree. The merge based rebase ran
git commitwhich ran the post commit hook. The interactive rebase ran the hook until and I broke it ina356ee4659b("sequencer: try to commit without forking 'git commit'", 2017-11-24) and after I fixed it in4627bc777e("sequencer: run post-commit hook", 2019-10-15). As it was broken for two years with no one noticing it can't be that popular.Maybe a crazy idea, but maybe not: how about running the
post-commithook _only_ if--mergewas specified explicitly, and in that case (and guarded behind a check verifying that thepost-commithook _actually_ exists _and_ is executable) warn the user that this hook won't be run in future versions?
Quoted 7 lines
To make things better for users who actually want to run that hook during rebases, we could introduce a config option, say,
rebase.runPostCommitHookthat is a tri-state (true,false,onlyForDashDashMerge, at first defaulting to the last, eventually tofalse).Crazy? Or helpful?
Seems crazy. Why would you want it just for --merge? If anything, I would think --merge should be most like --am; if some mode of rebase were to be considered special, I'd think it'd only be the _explicitly_ interactive case. But I don't see the justification for treating any of the rebase modes differently. I think the hook should be on for all of them, or off for all of them, and I could go either way.
(Honestly, it's tempting to just fix the fact that the interactive backend needlessly forks a "git commit" process by having it commit on its own much like builtin/merge.c does. Then omit calling the post-commit hook and it behaves the same as the am backend and no one in the world notices because no one in the world uses or cares about that hook except a few people at Google who happen to be used to the am-backend and even then aren't convinced whether invoking the hook or not is right. And if that's wrong and someone has a solid argument about why and the harm that not calling it does, then big deal, we add calling the hook sometime in the future...)
Quoted 11 lines
Quoted 10 lines
Quoted 3 lines
But the big question here, is what is correct behavior? Should rebase call the post-commit hook, or should it skip it? I haven't any clue what the answer to that is.
It's creating a new commit so I lean towards thinking it should run the post-commit hook. As an example I have a post-commit hook that prints a warning if a commit is created on a branch that is being rewritten by one of my scripts in another worktree. There are pre-commit and pre-rebase hooks to try and prevent that, but the warning is there as a last resort if those hooks are by-passed.
Quoted 2 lines
I guess you're right, it is quite surprising that the
post-commithook is _not_ run for--amrebases even though commits are created.
I guess part of the problem is whether people think of it as "new commits" or just any commits. "New commits" are created by "git commit" and "git merge". rebase and cherry-pick just create derivatives of existing commits. Given the existence of the post-rewrite hook, one could argue that the distinction has merit. I'm not sure either way, so I'm glad Emily started investigating. I'd rather not deal with that can of worms... :-)
Quoted 64 lines
Quoted 7 lines
Quoted 2 lines
Quoted 2 lines
2.
GIT_REFLOG_ACTIONcontains "rebase -i" even though the rebase is not interactive.If this is important to people I think it should be easy enough to set
GIT_REFLOG_ACTIONto the appropriate string inbuiltin/rebase.c(so long as it hasn't already been set by the user) rather than relying onsequencer.cto do it.I agree (but won't have time to implement it, so maybe I should shut up already...)
Quoted 39 lines
Quoted 25 lines
Yep, as does --keep, --exec, --rebase-merges, etc. There are lots of rebases which use the interactive machinery even if they aren't explicitly interactive. I've never seen the "-i" in the reflog message defined, but clearly it has always been used whenever the interactive machinery was in play regardless of whether the rebase was interactive. In that regard, I figured that --merge fit in rather nicely. (And I noted the fact that reflog messages were different between the backends among the "BEHAVIORAL DIFFERENCES" section of git-rebase.txt). But if others think we should just drop the -i (much as we did for the bash prompt), I'd be happy with that too. If we go that route, I think I'd rather drop the -i in the reflog for all rebases, not just the using-the-interactive-machinery-but-not-explicitly-interactive ones.
Quoted 11 lines
3. In circumstances I haven't pinned down yet, we get the error
message "invalid date format: @@2592000 +0000": $ git rebase --committer-date-is-author-date --onto branch_K branch_L~1 branch_L $ git checkout --theirs file $ git add file $ git rebase --continue fatal: invalid date format: @@2592000 +0000 error: could not commit staged changes.Quoted 8 lines
Quoted 2 lines
This isn't reproducible without --committer-date-is-author-date. More context (the test where it happens) is in [2].
Interesting. Do you happen to know if this started happening with ra/rebase-i-more-options, or did it just become an issue with en/rebase-backend? I looked around at the link you provided and feel a bit confused; I'm not sure which test does this or how I'd reproduce.
I'm confused by the test as well. As ra/rebase-i-more-options only touched the sequencer then any bugs would only show up in this test (which runs a non-interactive rebase) once en/rbease-backend switched to that backend. It seems likely that ra/rebase-i-more-options is to blame.
Quoted 4 lines
Jonathan - do you happen to know if your users create empty commits at all? and if so what do they expect rebase to do with them (and any that become empty when they are rebased) - cf
https://lore.kernel.org/git/<CABPp-BEH=9qejeqysHYE+AJ+JPaBympZizq-bx_OjArYFa4xUQ@mail.gmail.com>The double
@looks very funny. I would be interested in seeing an MCVE.Quoted 7 lines
Quoted 7 lines
Quoted 6 lines
4. I suspect the exit status in the "you need to resolve conflicts" case has changed. With rebase --am, [3] would automatically invoke rebase --abort when conflicts are present, but with rebase --merge it does not.
Known?
Quoted 8 lines
Quoted 4 lines
Quoted 4 lines
Nope, but I would certainly hope that "you need to resolve conflicts" would result in a non-zero exit status. If it doesn't, that sounds like a bug in the interactive backend that we need to fix. I'll dig in.
Yes, exiting with status 0 would be a major bug, and I think it might even be a bug that was introduced by me when I re-implemented the core loop of the interactive rebase in C.
Ooh, that sounds interesting. Do you have any more details? My simple testing here shows that we exit with status 1, so we shouldn't have that problem unless perhaps there was something else in next (ra/rebase-i-more-options??) or some other special conditions that was causing it.
Quoted 4 lines
But to me it sounds as if 4. is not so about the exit code but about aborting immediately. I do not recall seeing --am rebases to abort, though, but to exit with error (and I saw the same behavior in interactive rebases).
The aborting "immediately" wasn't done by git but by the code calling git; following Jonathan's link you'll see the code they are complaining about is:
func tryRebase(jirix *jiri.X, project Project, branch string) (bool, error) {
scm := gitutil.New(jirix, gitutil.RootDirOpt(project.Path)) if err := scm.Rebase(branch); err != nil {
err := scm.RebaseAbort()
return false, err
}
return true, nil
}
but of course, the scm.Rebase() and scm.RebaseAbort() definitions aren't within that file so I have no idea what is really being run.
Quoted 2 lines
We will need to see a reduced concrete example (preferably as a new test case) of the described behavior.
Yes, please.
Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
Quoted 5 lines · Elijah Newren
Then omit calling the
post-commit hook and it behaves the same as the am backend and no one in the world notices because no one in the world uses or cares about that hook except a few people at Google who happen to be used to the am-backend
Just responding to this part: I know this was a bit of thinking out loud, but the "just a few people at Google" bit is counter-productive. The search Emily ran
<https://github.com/search?l=&q=filename%3A%2Apost-commit%2A&type=Code>
shows that it's fairly common to use a post-commit hook for deployment, with scripts like
#!/bin/bash unset GIT_INDEX_FILE git --work-tree=/var/www/html --git-dir=/home/daniel/proj/.git checkout -f
or
#!/bin/bash
# Sync gh-pages branch with master
#########################################
git checkout gh-pages
git rm -rf -q .
git checkout master -- .
git add .
git commit -am "Syncing gh-pages with master" git checkout master
And I'm not saying that selfishly --- obviously, from a selfish perspective, what you're proposing would change behavior the least and I'd end up with happy users. :) I'm just trying to help with updating the list's collective model of user behavior.
(Actually, I want to remove jiri's post-commit hook --- so it is only the example that revealed this behavior change and is not my motivation for continuing to chime in in this thread.)
The deployment examples above seem like examples where the user would want the script to run on "git am" (and on "git merge --ff-only", for that matter) but not on the intermediate commits in "git rebase", since when rebasing a multi-commit series, deploying earlier rebased commits would cause the deployment to lose the benefit of later fixes.
[...]
Quoted 5 lines
Ooh, that sounds interesting. Do you have any more details? My simple testing here shows that we exit with status 1, so we shouldn't have that problem unless perhaps there was something else in next (ra/rebase-i-more-options??) or some other special conditions that was causing it.
I can set aside some more time to investigate that one early next week.
Thanks for the quick answers --- it's been very helpful.
Sincerely,
Jonathan
Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
Quoted 9 lines · Jonathan Nieder
#!/bin/bash
# Sync gh-pages branch with master
#########################################
git checkout gh-pages
git rm -rf -q .
git checkout master -- .
git add .
git commit -am "Syncing gh-pages with master" git checkout master
Correction: I misread this one. It was guarded with
if [ `git rev-parse --abbrev-ref HEAD` == "master" ]; then ... fi
which makes all the difference. Another example is to compute ctags:
#!/bin/sh ~/bin/hook_ctags.sh >/dev/null 2>&1 &
Here's a fun one that updates a timesheet:
https://github.com/GokhanArik/git-hooks-timesheet
Sorry for the noise,
Jonathan
Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
Hi,
Quoted 6 lines · Jonathan Nieder
which makes all the difference. Another example is to compute ctags:
#!/bin/sh ~/bin/hook_ctags.sh >/dev/null 2>&1 &
Thought about this ctags case a bit more. In contrast to the deployment cases you brought up (which appear to be protected by a branch check anyway), doesn't this one argue more that post-commit should be run for all rebase types? Sure, it'd take more computation time, but if a user runs "git pull --rebase" and then is frustrated that all their tags are extremely out of date then they may be kind of frustrated.
Of course, arguing against myself, we could always tell them to just implement a post-rewrite hook.
Quoted 2 lines
Here's a fun one that updates a timesheet:
https://github.com/GokhanArik/git-hooks-timesheet
So...cherry-picking commits will pad your timesheet with duplicate work on the same topic (because cherry-pick calls sequencer which calls "git commit"), and so will interactive-based rebases, but rebase-am (the default rebase in the past) wouldn't. Fun indeed...
Wouldn't such users view removing the post-commit hook from sequencer as "harmful to their productivity"? And shouldn't we add the post-commit hook to rebase-am in order to "increase their productivity"? ;-)
Quoted 1 line
Sorry for the noise,
No, these were great examples; thanks for pointing them out. My commentary about the timesheet is the only thing that is noise here. :-)
Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
Quoted 3 lines · Elijah Newren
Thought about this ctags case a bit more. In contrast to the deployment cases you brought up (which appear to be protected by a branch check anyway),
To be clear, one of the two deployment cases I mentioned (gh-pages) is protected by a branch check, but the other (git --work-tree=srvroot checkout) is not.
Quoted 5 lines
doesn't this one argue more that post-commit
should be run for all rebase types? Sure, it'd take more computation time, but if a user runs "git pull --rebase" and then is frustrated that all their tags are extremely out of date then they may be kind of frustrated.
Yes. In fact I think all of these examples argue for having the post-commit hook run once on rebase completion (and on "git am" completion, "git merge --ff-only" completion, etc).
Of course it's not hard to find other examples that argue against that. We provided a hook "here's a notification that runs after git commit runs" without tailoring it to a particular need, and on one hand people came up with various creative ways to use it (yay!) but on the other hand it's hard to maintain as Git's ways of making new commits evolve.
Quoted 2 lines
Of course, arguing against myself, we could always tell them to just implement a post-rewrite hook.
That's an interesting facet I hadn't thought about before.
It's tempting since it kind of minimizes the blast surface while providing people a way to keep doing what they had wanted.
Historically "git cherry-pick" ran "git commit", causing it to run the post-commit hook. There's some related discussion at
https://lore.kernel.org/git/[email protected]/
of other side effects of having run "git commit".
... oh! git cherry-pick doesn't run post-rewrite. Interesting.
To summarize what we've discussed so far:
A. run post-commit hook consistently in rebase --am and --merge modes
Advantages:
* consistent with historic "rebase -i" behavior
* supports hooks like ctags generation that want to update state to match what has been committed in the worktree (especially when preparing for the rebase to stop due to conflicts)
* consistent with other commands like "git cherry-pick" that run the post-commit hook
Disadvantages:
* invokes hooks more often in a setting they are not used to being invoked in
* slows down rebase
* when post-commit hooks are used for deployment, exposes intermediate states in the middle of a rebase to the deployment environment
* inconsistent with "git am"
Mitigations:
* could go through the normal warn about change, opt-in config, opt-out config cycle to make the change smoother
* could provide new hooks (e.g., one run after a batch of objects is created for applications similar to "git gc --auto", one run when HEAD is updated for applications like the ctags one, one run on ref update for applications like the deployment case) and encourage authors to migrate to them
* could run the post-commit hook in "git am", too
B. stop running the post-commit hook in rebase
Advantages:
* makes --am and --merge consistent with minimal user impact
Disadvantages:
* meaning of the post-commit hook remains a muddle
* in particular, "git am" and "git rebase" would not be consistent with "git cherry-pick"
Mitigations:
* could provide new hooks for people to migrate to to replace that muddle
C. stop running the post-commit hook in rebase --merge or commit --amend
Advantages:
* produces a consistent definition of post-commit ("it's about new commits only")
* hook authors get the same expressiveness using the post-rewrite hook
Disadvantages:
* the change to historical "git commit --amend" behavior is likely to be surprising
Mitigations:
* could go through the warn, opt-in config, opt-out config cycle
Any bits I've missed?
Thanks,
Jonathan
Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
Quoted 10 lines · Jonathan Nieder
Quoted 3 lines
Of course, arguing against myself, we could always tell them to just implement a post-rewrite hook.
That's an interesting facet I hadn't thought about before.
It's tempting since it kind of minimizes the blast surface while providing people a way to keep doing what they had wanted.
Historically "git cherry-pick" ran "git commit", causing it to run the
What do you mean "historically"? I'm pretty sure this piece of code is shared in the sequencer between rebase -i, rebase --merge, and cherry-pick, so all three currently call post-commit unless I'm mistaken.
Quoted 9 lines
post-commit hook. There's some related discussion at
https://lore.kernel.org/git/[email protected]/
of other side effects of having run "git commit".... oh! git cherry-pick doesn't run post-rewrite. Interesting.
To summarize what we've discussed so far:
A. run post-commit hook consistently in rebase --am and --merge modes
Could you spell these out a bit more? I think by this option you mean "Always run post-commit when commits are created", which includes whether those commits were created by git-commit, git-rebase (any backend), git-merge, git-cherry-pick, git-revert, git-am, etc. (but with a glaring exception for fast-import and things that call it, much like the exception post-rewrite already carves out for it).
Much as I don't see a reason to make rebase --merge different than rebase --am, I don't see a reason to differentiate between git-merge and git-commit. I also don't see why rebase --am should be different than am.
Quoted 8 lines
Advantages:
* consistent with historic "rebase -i" behavior
* supports hooks like ctags generation that want to update state to match what has been committed in the worktree (especially when preparing for the rebase to stop due to conflicts)
* consistent with other commands like "git cherry-pick" that run the post-commit hook
Quoted 8 lines
Disadvantages:
* invokes hooks more often in a setting they are not used to being invoked in
* slows down rebase
* when post-commit hooks are used for deployment, exposes intermediate states in the middle of a rebase to the deployment environment
* inconsistent with "git am"
I don't want to introduce additional inconsistencies. git-am and git merge should be changed at the same time if we go this route.
Quoted 12 lines
Mitigations:
* could go through the normal warn about change, opt-in config, opt-out config cycle to make the change smoother
* could provide new hooks (e.g., one run after a batch of objects is created for applications similar to "git gc --auto", one run when HEAD is updated for applications like the ctags one, one run on ref update for applications like the deployment case) and encourage authors to migrate to them
* could run the post-commit hook in "git am", too
Quoted 1 line
B. stop running the post-commit hook in rebase
AND sometimes in both cherry-pick and git-revert, depending on the number of commit(s) picked/reverted (see below).
Also, even if we go this route, I think the post-commit hook should be added to git-merge whenever it creates a merge commit.
Quoted 2 lines
Advantages:
* makes --am and --merge consistent with minimal user impact
...and also makes --interactive and --exec and other cases consistent too.
Quoted 2 lines
Disadvantages:
* meaning of the post-commit hook remains a muddle
Why? "Commands which create no more than one commit (git-commit, git-merge, maybe single-commit git-revert or git-cherry-pick) call post-commit, commands which create several commits derived from others (git-am, git-rebase, git-cherry-pick, sometimes git-revert ) call post-rewrite instead for performance reasons" (with the standard exception for fast-import; also, git-merge could probably call post-rewrite in fast-forward cases since no new commit is created to call post-commit on).
Or we could just say that commands which can create multiple commits (cherry-pick & revert) only call post-rewrite even in cases where they are only called with one commit (since am and rebase can also be called on just one commit).
Quoted 2 lines
* in particular, "git am" and "git rebase" would not be consistent with "git cherry-pick"
Yeah, I'd rather not introduce more inconsistencies; we should pick a rule and enforce it universally. Thus, I'd say option B would probably be thought of as the "always creates at most one commit" vs. "creates multiple commits" split for post-commit vs. post-rewrite.
Quoted 9 lines
Mitigations:
* could provide new hooks for people to migrate to to replace that muddleC. stop running the post-commit hook in rebase --merge or commit --amend
Advantages:
* produces a consistent definition of post-commit ("it's about new commits only")
But cherry-pick never creates "new" commits if commit --amend doesn't, so we'd also want to turn the post-commit hook off for cherry-pick, right? And we'd want to turn post-commit on for git-merge, at least for non-fast-forward cases since those create new commits.
Quoted 1 line
* hook authors get the same expressiveness using the post-rewrite hook
Isn't this also an advantage for case B? Any reason it was left off up there?
Quoted 3 lines
Disadvantages:
* the change to historical "git commit --amend" behavior is likely to be surprising
That is certainly one issue, but I'd say this case is riddled with problems and is the most muddled of all. Once you change git commit --amend behavior because it's about *new* commits only then you've got a huge can of worms from rebase -i:
For interactive rebases, we have to avoid the post-commit hook for anything that is a pick, but if they break or edit and insert new commits then we have to call the post-commit hook for those. If the rebase stops due to a merge conflict, then the next commit created needs to avoid the post-commit hook. If they pick to squash or fixup commits, then we have to deploy rename/break detection to determine whether the commit is "new" or not and thus whether the post-commit hook should be triggered for it. Actually, maybe we need the rename/break-detection logic when the rebase stops at a conflict too due to user edits. And then we start getting into questions about whether the normal heuristics for rename/break-detection apply or if "new-ness" should have its own set of heuristics...
Also, I can't tell if a reverted commit count as "new" or not. Even if it does, what about the case of a reverted revert, or higher order iterations of reverts (which I see occasionally)?
Does this definition mean we should use the git-cherry machinery on any new commits to search for "newness" (maybe even with an extension to search across all branches) before we decide whether to call the post-commit hook?
I think case C is opening up a pandora's box of craziness.
Quoted 7 lines
Mitigations:
* could go through the warn, opt-in config, opt-out config cycleAny bits I've missed?
Thanks,
Jonathan
I think it was useful to phrase it this way and list the various advantages, disadvantages, and mitigations; thanks.
Is there anything I've missed in my additional details? Anything sound crazy (beyond what I said I think case C leads to)?
Elijah
Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
Quoted 5 lines · Elijah Newren
Quoted 1 line · Jonathan Nieder
Historically "git cherry-pick" ran "git commit", causing it to run the
What do you mean "historically"?
Before 356ee4659bb (sequencer: try to commit without forking 'git commit', 2017-11-24). See also 4627bc777e9 (sequencer: run post-commit hook, 2019-10-15).
Quoted 4 lines
I'm pretty sure this piece of code
is shared in the sequencer between rebase -i, rebase --merge, and cherry-pick, so all three currently call post-commit unless I'm mistaken.
Yep, that's right.
[...]
Quoted 9 lines
Quoted 1 line
A. run post-commit hook consistently in rebase --am and --merge modes
Could you spell these out a bit more? I think by this option you mean "Always run post-commit when commits are created", which includes whether those commits were created by git-commit, git-rebase (any backend), git-merge, git-cherry-pick, git-revert, git-am, etc. (but with a glaring exception for fast-import and things that call it, much like the exception post-rewrite already carves out for it).
Quoted 4 lines
Much as I don't see a reason to make rebase --merge different than rebase --am, I don't see a reason to differentiate between git-merge and git-commit. I also don't see why rebase --am should be different than am.
Sure, that matches the general gist behind (A).
[...]
Quoted 4 lines
Quoted 1 line
B. stop running the post-commit hook in rebase
AND sometimes in both cherry-pick and git-revert, depending on the number of commit(s) picked/reverted (see below).
Interesting. Doesn't "git cherry-pick A..B" run post-commit after each commit today? It would be possible to do (B) and retain that behavior.
Quoted 2 lines
Also, even if we go this route, I think the post-commit hook should be added to git-merge whenever it creates a merge commit.
Agreed.
Quoted 4 lines
Quoted 2 lines
Advantages:
* makes --am and --merge consistent with minimal user impact...and also makes --interactive and --exec and other cases consistent too.
Hm. My initial thought with (B) was to stop running post-commit in non-interactive rebase but keep running it in rebase --interactive.
I don't have a strong opinion about this, though.
Quoted 8 lines
Quoted 2 lines
Disadvantages:
* meaning of the post-commit hook remains a muddleWhy? "Commands which create no more than one commit (git-commit, git-merge, maybe single-commit git-revert or git-cherry-pick) call post-commit, commands which create several commits derived from others (git-am, git-rebase, git-cherry-pick, sometimes git-revert ) call post-rewrite instead for performance reasons"
Example: if I run "git revert A..B", following that rule we'd want to call post-rewrite instead of post-commit. But post-rewrite gets input
<old-sha1> SP <new-sha1> [ SP <extra-info> ] LF
so that it can be used to copy data such as notes. What do I pass in as <old-sha1>?
Relatedly, if I remember correctly, we don't call post-rewrite in "git cherry-pick" because cherry-pick is considered to represent a *new* commit instead of a modified version of the old one. In C++ terms, it's a copy constructor instead of a move constructor. ;-) See notes.rewrite.* in "git help notes" for more on that subject.
[...]
Quoted 9 lines
Quoted 5 lines
C. stop running the post-commit hook in rebase --merge or commit --amend
Advantages:
* produces a consistent definition of post-commit ("it's about new commits only")But cherry-pick never creates "new" commits if commit --amend doesn't, so we'd also want to turn the post-commit hook off for cherry-pick, right?
With the copy/modify distinction described above, "git commit --amend" acts as a "modify" operation in a user's workflow, while "git cherry-pick" acts as a "copy" operation.
That means that with this definition, post-commit is appropriate to run in cherry-pick but not in commit --amend.
This matches the distinction used by note rewriting and the post-rewrite hook. Looking back at the discussion in
https://lore.kernel.org/git/[email protected]/
I don't see this covered, so it's possible I'm remembering wrong (a pointer to the motivating discussion that inspired that patch series would be welcome). In the context of note copying, this distinction ended up being a good decision in practice in most workflows I've seen.
[...]
Quoted 6 lines
Quoted 3 lines
Disadvantages:
* the change to historical "git commit --amend" behavior is likely to be surprisingThat is certainly one issue, but I'd say this case is riddled with problems and is the most muddled of all.
I'm inclined to agree, based on the "git commit --amend" example.
[...]
Quoted 5 lines
I think it was useful to phrase it this way and list the various advantages, disadvantages, and mitigations; thanks.
Is there anything I've missed in my additional details? Anything sound crazy (beyond what I said I think case C leads to)?
To sum it up, I lean toward (B), but (A) is not so bad, either (especially with the "I need some time to adapt to the change" opt-out config described under mitigations).
Thanks for patiently working it through with me.
My two cents,
Jonathan
Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
Quoted 9 lines · Jonathan Nieder
[...]
Quoted 4 lines
Quoted 1 line
B. stop running the post-commit hook in rebase
AND sometimes in both cherry-pick and git-revert, depending on the number of commit(s) picked/reverted (see below).
Interesting. Doesn't "git cherry-pick A..B" run post-commit after each commit today? It would be possible to do (B) and retain that behavior.
Sure, it'd be possible to retain the post-commit calls for cherry-pick but should we? Isn't the primary reason the issue came up that having batch-commit operations calling post-commit for each intermediate commit is ugly/unexpected overhead? If we're ripping it out of rebase for that reason, it seems that cherry-pick should get the same treatment. I don't want to just move the inconsistencies around, I want to fix them.
Quoted 4 lines
Quoted 2 lines
Also, even if we go this route, I think the post-commit hook should be added to git-merge whenever it creates a merge commit.
Agreed.
Yay!
Quoted 9 lines
Quoted 4 lines
Quoted 2 lines
Advantages:
* makes --am and --merge consistent with minimal user impact...and also makes --interactive and --exec and other cases consistent too.
Hm. My initial thought with (B) was to stop running post-commit in non-interactive rebase but keep running it in rebase --interactive.
I don't have a strong opinion about this, though.
I'm okay with treating them differently if there's a defining reason why, but otherwise I think they should all behave the same. The only reasons I've seen for differences all fall in category C so far, which gives me a strong opinion against treating them differently.
Quoted 14 lines
Quoted 8 lines
Quoted 2 lines
Disadvantages:
* meaning of the post-commit hook remains a muddleWhy? "Commands which create no more than one commit (git-commit, git-merge, maybe single-commit git-revert or git-cherry-pick) call post-commit, commands which create several commits derived from others (git-am, git-rebase, git-cherry-pick, sometimes git-revert ) call post-rewrite instead for performance reasons"
Example: if I run "git revert A..B", following that rule we'd want to call post-rewrite instead of post-commit. But post-rewrite gets input
<old-sha1> SP <new-sha1> [ SP <extra-info> ] LF
Quoted 8 lines
so that it can be used to copy data such as notes. What do I pass in as <old-sha1>?
Relatedly, if I remember correctly, we don't call post-rewrite in "git cherry-pick" because cherry-pick is considered to represent a *new* commit instead of a modified version of the old one. In C++ terms, it's a copy constructor instead of a move constructor. ;-) See notes.rewrite.* in "git help notes" for more on that subject.
Ah, I see. Yeah, there's some impedance mismatch between the purpose/definition of the post-rewrite hook and what I think is needed for B. Perhaps create a new post-batch-commit hook ("batch-post-commit"?) and have git-rebase, git-am, git-cherry-pick, git-revert, etc. call it while git-commit and git-merge call post-commit. (Or maybe even make git-commit and git-merge also call post-batch-commit/batch-post-commit but do so with just one commit -- and just note that post-commit's design of not taking any parameters was a mistake and any place that happens to call it beyond "git-commit/git-merge" is solely by accident of past implementation and is not guaranteed to continue.)
I think the realm of "copy constructor vs. move constructor" may make sense for post-rewrite-hook, but I'm worried that trying to apply that logic to notification of new commits may lead us into category C craziness.
Quoted 11 lines
[...]
Quoted 9 lines
Quoted 5 lines
C. stop running the post-commit hook in rebase --merge or commit --amend
Advantages:
* produces a consistent definition of post-commit ("it's about new commits only")But cherry-pick never creates "new" commits if commit --amend doesn't, so we'd also want to turn the post-commit hook off for cherry-pick, right?
Quoted 6 lines
With the copy/modify distinction described above, "git commit --amend" acts as a "modify" operation in a user's workflow, while "git cherry-pick" acts as a "copy" operation.
That means that with this definition, post-commit is appropriate to run in cherry-pick but not in commit --amend.
What if there are conflicts? Because with conflicts, aren't cherry-picks more like the 'commit --amend' case since they behave as a "modify"? Conflict-dependent invoking of the post-commit hook?
Quoted 8 lines
This matches the distinction used by note rewriting and the post-rewrite hook. Looking back at the discussion in
https://lore.kernel.org/git/[email protected]/
I don't see this covered, so it's possible I'm remembering wrong (a pointer to the motivating discussion that inspired that patch series would be welcome). In the context of note copying, this distinction ended up being a good decision in practice in most workflows I've seen.
Yeah, for note copying that distinction makes a lot of sense to me. For "notification of new commits", it doesn't seem to jive at all for me.
Quoted 14 lines
[...]
Quoted 6 lines
Quoted 3 lines
Disadvantages:
* the change to historical "git commit --amend" behavior is likely to be surprisingThat is certainly one issue, but I'd say this case is riddled with problems and is the most muddled of all.
I'm inclined to agree, based on the "git commit --amend" example.
[...]
Quoted 3 lines
I think it was useful to phrase it this way and list the various advantages, disadvantages, and mitigations; thanks.
Quoted 6 lines
Quoted 2 lines
Is there anything I've missed in my additional details? Anything sound crazy (beyond what I said I think case C leads to)?
To sum it up, I lean toward (B), but (A) is not so bad, either (especially with the "I need some time to adapt to the change" opt-out config described under mitigations).
Sounds like we might be starting to converge towards a similar opinion. I roughly feel the same with leaning towards something like
(B) _if_ we can address the inconsistencies rather than just shuffle them around (meaning we also turn off post-commit for interactive rebase and cherry-pick) and move all of them over to some kind of batch-commit-notification mechanism.
Quoted 4 lines
Thanks for patiently working it through with me.
My two cents,
Jonathan
Likewise. :-)
Elijah
Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
In reply to Elijah NewrenQuoted 5 lines · Elijah Newren
Why? "Commands which create no more than one commit (git-commit, git-merge, maybe single-commit git-revert or git-cherry-pick) call post-commit, commands which create several commits derived from others (git-am, git-rebase, git-cherry-pick, sometimes git-revert ) call post-rewrite instead for performance reasons"
Sounds totally wrong. post-rewrite is about carrying forward data that used to be associated with commit X to new commit Y when Y was created by "rewriting" X (i.e. after Y gets created X no longer has any purpose other than as a historical curiosity). There is nothing "for performance reasons" here. There should be nothing for "git revert" to do with post-rewrite (because the resulting commit is not even a rewrite of the commit that was reverted), and "git cherry-pick" is not even a rewrite operation (it is to duplicate an existing commit into another unrelated context, without discarding the original commit).
I am a bit confused...
Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
Quoted 9 lines · Junio C Hamano
Quoted 5 lines · Elijah Newren
Why? "Commands which create no more than one commit (git-commit, git-merge, maybe single-commit git-revert or git-cherry-pick) call post-commit, commands which create several commits derived from others (git-am, git-rebase, git-cherry-pick, sometimes git-revert ) call post-rewrite instead for performance reasons"
Quoted 10 lines
Sounds totally wrong. post-rewrite is about carrying forward data that used to be associated with commit X to new commit Y when Y was created by "rewriting" X (i.e. after Y gets created X no longer has any purpose other than as a historical curiosity). There is nothing "for performance reasons" here. There should be nothing for "git revert" to do with post-rewrite (because the resulting commit is not even a rewrite of the commit that was reverted), and "git cherry-pick" is not even a rewrite operation (it is to duplicate an existing commit into another unrelated context, without discarding the original commit).
Yep, it was totally wrong; Jonathan clued me in later in the thread.
Quoted 1 line
I am a bit confused...
No, it was I who was confused.
Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
In reply to Jonathan NiederQuoted 11 lines · Jonathan Nieder
Quoted 5 lines · Elijah Newren
Then omit calling the
post-commit hook and it behaves the same as the am backend and no one in the world notices because no one in the world uses or cares about that hook except a few people at Google who happen to be used to the am-backendJust responding to this part: I know this was a bit of thinking out loud, but the "just a few people at Google" bit is counter-productive.
Yes, you're right, I should do better. Let me try again to express my thoughts in a more constructive way...
Quoted 4 lines
The search Emily ran
<https://github.com/search?l=&q=filename%3A%2Apost-commit%2A&type=Code>
shows that it's fairly common to use a post-commit hook for deployment,
* I actually looked through a dozen or two of those yesterday. I came away assuming that many of those might be svn hooks
(http://svnbook.red-bean.com/en/1.7/svn.ref.reposhooks.post-commit.html)
or for other non-git version control systems. Those that were for git didn't seem like they'd matter whether or not they were called by rebase. Which led me down a thought experiment...
* sequencer.c has accumulated a bit of cruft. Partially this was because it's conversion to c was somewhat minimal, e.g. it'd still fork other processes. One of these other processes is "git commit". I think it's been part of the plan all along to remove any forked processes from sequencer.c and other things that were converted from shell.
* What if, before being aware of this post-commit hook, someone had removed the forking of "git commit" from sequencer.c? I think it nearly certain that whoever did that wouldn't realize that strict back-compat would involve adding a call to a post-commit hook when they did that, and thus all the non-am-based rebases and cherry-picks would suddenly stop calling that hook. Would anyone notice?
* People have been using the interactive and merge backends for years for lots of different rebase types with more and more joining. We've never had a report that users were annoyed by it calling the post-commit hook. But, on the other hand, rebase-am has run for years without ever calling the post-commit hook and no one has complained. I don't believe interactivity is the deciding factor here, as we have several non-interactive types of rebases implemented by the interactive backend (--keep-empty, --exec, --rebase-merges, --preserve-merges, --strategy, --merge) all of which are more similar in usecase to the am-backend than to the explicitly interactive usecase from a user perspective (i.e. "Just run on all the commits" vs. "Let me edit and rearrange and drop and insert and whatnot") and thus for which I'd assume similar user expectations for what hooks if any to call.
* When the issue came up, Junio argued that "post-commit" can easily be argued to mean "whenever a commit is created". That seemed eminently reasonable to me. On the flip side, we do have both "post-commit" and "post-rewrite" hooks, which could argue that you'd want to handle brand new commits ("git commit", "git merge") differently than tweaking existing ones ("git rebase", "git cherry-pick"), though explicitly interactive rebases are a monkey-wrench here since they can be used to insert brand new commits in the middle of rewriting a big pile of commits (suggesting maybe that only the explicitly interactive rebases should call "post-commit"?).
* Five different people on this list have chimed in about this "post-commit" behavior, but I haven't seen a single argument for or against calling "post-commit" based on actual user expectation for this kind of usecase (e.g. if done from a clean design). All I've seen is folks talking about whether there are backward compatibility concerns and/or what could be construed by the meaning of words like "post-commit". In other words, I don't think any of us have a clue what is correct behavior if we were designing from scratch.
* Since we don't seem to understand what "correct" behavior is...how much does or would "incorrect" behavior hurt? What if we made a random judgement call about the post-commit hook and got it wrong? Well...
* If we changed am to also call the post-commit hook, we have some
early signal that there might be some folks who could comment, though I haven't yet seen a case where people would be hurt other than a possible slow-down which the user could probably fix by adjusting their scripts. And in such cases it's easy to say "post-commit" means we call the hook when a new commit is created; if you're doing something weird where you don't want to do work during the middle of a rebase your script should check for that case -- it has always been called by other rebase backends anyway.
* If we changed sequencer.c to stop forking a git-commit process
and by proxy missed the post-commit hook, I strongly suspect no one would notice or care. But if they did, we could just point out how we have both "post-commit" and "post-rewrite" hooks and that means we "fixed" our bug of calling the "post-commit" hook from other rebase/cherry-pick backends in the past by just calling the post-rewrite hook. We can tell users they should add a post-rewrite hook, and, if they want, have it call their post-commit hook one or more times.
* If we changed sequencer.c in a way that it stopped calling the
post-commit hook, and someone did notice/care and did have a good solid argument about correct behavior and that it should involve calling "post-commit"...then we simply add it and the world is fixed (though we have to do the explaining that post-commit means whenever a commit is created mentioned above). And we provide users with a workaround for older git versions where we tell them to have the post-rewrite hook invoke their own post-commit hook for them.
In short, while I don't know what "correct" behavior is other than that the rebase backends ought to behave the same (or at least all the am-based rebases and the non-explicitly-interactive rebases implemented by the interactive backend should all behave the same), I'm not yet seeing much damage from just picking an answer and implementing it.
All that said, I'm really, really glad Emily decided to look into this so I don't have to argue or justify it from anything more than a "backseat driver" type of perspective. :-)
Quoted 27 lines
with scripts like
#!/bin/bash unset GIT_INDEX_FILE git --work-tree=/var/www/html --git-dir=/home/daniel/proj/.git checkout -for
#!/bin/bash
# Sync gh-pages branch with master
#########################################
git checkout gh-pages
git rm -rf -q .
git checkout master -- .
git add .
git commit -am "Syncing gh-pages with master" git checkout masterAnd I'm not saying that selfishly --- obviously, from a selfish perspective, what you're proposing would change behavior the least and I'd end up with happy users. :) I'm just trying to help with updating the list's collective model of user behavior.
(Actually, I want to remove jiri's post-commit hook --- so it is only the example that revealed this behavior change and is not my motivation for continuing to chime in in this thread.)
Quoted 5 lines
The deployment examples above seem like examples where the user would want the script to run on "git am" (and on "git merge --ff-only", for that matter) but not on the intermediate commits in "git rebase", since when rebasing a multi-commit series, deploying earlier rebased commits would cause the deployment to lose the benefit of later fixes.
With your correction later in the thread that it's protected by current branch == "master", this example doesn't concern me as much. The ctags case is more interesting, but it's currently running on every commit of rebase using the interactive backend already, and while it might slow things down to run on every commit it wouldn't break anything. Plus, users can easily add a detached-head or in-the-middle-of-rebase check to their hook to avoid such a slow down if it is enough for them to notice so I'm not seeing much harm yet.
So, these are definitely interesting cases, but I'm still not seeing arguments about whether the hook should be called from a user perspective, just whether users might in the short term be adversely affected due to assuming they previously built up scripts assuming only certain types of rebases would ever be run. (Not that I'm trying to discount the short term, because it obviously matters, but that I'm worried we're letting it be the sole deciding factor rather than thinking of correct behavior and possibly introducing transition plans and whatnot if needed.)
Quoted 9 lines
[...]
Quoted 5 lines
Ooh, that sounds interesting. Do you have any more details? My simple testing here shows that we exit with status 1, so we shouldn't have that problem unless perhaps there was something else in next (ra/rebase-i-more-options??) or some other special conditions that was causing it.
I can set aside some more time to investigate that one early next week.
That'd be great, thanks.
Quoted 4 lines
Thanks for the quick answers --- it's been very helpful.
Sincerely,
Jonathan
Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
In reply to Jonathan NiederQuoted 5 lines · Jonathan Nieder
The deployment examples above seem like examples where the user would want the script to run on "git am" (and on "git merge --ff-only", for that matter) but not on the intermediate commits in "git rebase", since when rebasing a multi-commit series, deploying earlier rebased commits would cause the deployment to lose the benefit of later fixes.
Agree. The same thing can be said to "git am" accepting a series of multiple patches, though.
Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
In reply to Elijah NewrenHi Elijah,
Quoted 26 lines · Elijah Newren
Quoted 15 lines · Johannes Schindelin
Quoted 12 lines · Phillip Wood
Quoted 11 lines · Elijah Newren
Quoted 6 lines · Jonathan Nieder
4. I suspect the exit status in the "you need to resolve conflicts" case has changed. With rebase --am, [3] would automatically invoke rebase --abort when conflicts are present, but with rebase --merge it does not.
Known?
Quoted 8 lines
Quoted 4 lines
Quoted 4 lines
Nope, but I would certainly hope that "you need to resolve conflicts" would result in a non-zero exit status. If it doesn't, that sounds like a bug in the interactive backend that we need to fix. I'll dig in.
Yes, exiting with status 0 would be a major bug, and I think it might even be a bug that was introduced by me when I re-implemented the core loop of the interactive rebase in C.
Quoted 5 lines
Ooh, that sounds interesting. Do you have any more details? My simple testing here shows that we exit with status 1, so we shouldn't have that problem unless perhaps there was something else in next (ra/rebase-i-more-options??) or some other special conditions that was causing it.
Oh, I did not mean to say that it exits with status 0. I meant to say that _if_ it does, it would be terrible, and that my suspicion would be that the re-implementation in C introduced that bug. But as you say, there is no bug, so for once, the re-implementation in C is not at fault! ;-)
Ciao,
Dscho
Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
In reply to Phillip WoodQuoted 10 lines · Phillip Wood
Looking through the history the am based rebase has never run the post-commit hook as am has its own set of hooks and the scripted version used commit-tree.
Quoted 3 lines
But the big question here, is what is correct behavior? Should rebase call the post-commit hook, or should it skip it? I haven't any clue what the answer to that is.
It's creating a new commit so I lean towards thinking it should run the post-commit hook. As an example I have a post-commit hook that
The reason why "am" did not run "post-commit" is because "post-commit" was (originally) meant to be for cases where the end user types "git commit", IOW, the notion of "since we are creating a new commit object, let's run the post-commit hook" used to be totally misguided way of thinking. The hook was "the user _ran_ 'commit', so post-commit is run immediately afterwards".
These days, I think most of our tools subscribe to the newer "a commit got created--have hook the chance to report it" idea, simply because the original position is untenable---"git merge" may not want to run the post-commit hook according to the initial design, and it is fine to make it not to run it when clean automerge was recorded, but sometimes the end users have to type "git commit" to conclude a conflicted merge.
So from that point of view, I do not think it is too bad to bring "am" into the new ear and start running post-commit from it.
The "pre-commit" hook is a different story, though. From purely practical point of view, starting to run extra verification that may veto new commits from getting created only because we changed the implementation detail for some reason is a disservice to the users.
Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
In reply to Phillip WoodHi,
Quoted 6 lines · Phillip Wood
Quoted 3 lines · Elijah Newren
But the big question here, is what is correct behavior? Should rebase call the post-commit hook, or should it skip it? I haven't any clue what the answer to that is.
Quoted 6 lines
It's creating a new commit so I lean towards thinking it should run the post-commit hook. As an example I have a post-commit hook that prints a warning if a commit is created on a branch that is being rewritten by one of my scripts in another worktree. There are pre-commit and pre-rebase hooks to try and prevent that, but the warning is there as a last resort if those hooks are by-passed.
The initial design for the post-commit hook was
commit 89e2c5f17b901edf28a8bb778ece3f6c18bbb721
Author: Junio C Hamano <[email protected]>
Date: Thu Aug 18 17:20:08 2005 -0700
Add commit hook and make the verification customizable.
There are three hooks:
- 'pre-commit' is given an opportunity to inspect what is being committed, before we invoke the EDITOR for the commit message;
- 'commit-msg' is invoked on the commit log message after the user prepares it;
- 'post-commit' is run after a successful commit is made.
The first two can interfere to stop the commit. The last one is for after-the-fact notification.
The initial implementation was only in "git commit" and didn't affect other commands, but that's an artifact of implementation, not intention. The intention is more murky: certainly "creating a new commit" is not the event we want to notify about (for example, "git commit-tree" should not invoke the hook), but if I were designing it today then all operations that create a new commit and update the current branch with it might qualify.
Even that definition is a bit fuzzy: when I run "git rebase <upstream> <branch>", am I updating <branch> or the current branch? Are cherry-picks that carry over changes that were committed previously new commits?
Worse, we have the body of existing post-commit hooks to contend with, and each one may have made different assumptions about the semantics. We do not have the luxury of designing the hook without regard to that body of existing hooks today.
The hook that jiri installs is very simple. It wants to check when a user has (interactively) made a commit on a detached HEAD, to let them know that they might want to use "git checkout -b" afterward. With this particular hook, the behavior is better when git rebase does not invoke the hook, since in the context of a rebase, the user has no need to use "git checkout -b" afterward. This is a regression, and I think we need to take that seriously.
It doesn't seem like jiri is doing anything weird here, so there's a reasonably high probability that other hooks would be affected in the same way. How can we help authors of such hooks to handle this change?
I don't think that starting to invoke the 'post-commit' hook even more without going through that thought process is an acceptable fix. Since the "right" semantics aren't obvious here, the first step is probably to collect some typical examples of post-commit hooks, like the example from the message I'm replying to (thank you!). Ideas for where we can find more?
Thanks,
Jonathan
Re: [PATCH v3 15/15] rebase: change the default backend from "am" to "merge"
Quoted 51 lines · Jonathan Nieder
Hi,
Quoted 6 lines · Phillip Wood
Quoted 3 lines · Elijah Newren
But the big question here, is what is correct behavior? Should rebase call the post-commit hook, or should it skip it? I haven't any clue what the answer to that is.
Quoted 6 lines
It's creating a new commit so I lean towards thinking it should run the post-commit hook. As an example I have a post-commit hook that prints a warning if a commit is created on a branch that is being rewritten by one of my scripts in another worktree. There are pre-commit and pre-rebase hooks to try and prevent that, but the warning is there as a last resort if those hooks are by-passed.
The initial design for the post-commit hook was
commit
89e2c5f17b901edf28a8bb778ece3f6c18bbb721
Author: Junio C Hamano <[email protected]>
Date: Thu Aug 18 17:20:08 2005 -0700Add commit hook and make the verification customizable.
There are three hooks:
- 'pre-commit' is given an opportunity to inspect what is being committed, before we invoke the EDITOR for the commit message;
- 'commit-msg' is invoked on the commit log message after the user prepares it;
- 'post-commit' is run after a successful commit is made.
The first two can interfere to stop the commit. The last one is for after-the-fact notification.
The initial implementation was only in "git commit" and didn't affect other commands, but that's an artifact of implementation, not intention. The intention is more murky: certainly "creating a new commit" is not the event we want to notify about (for example, "git commit-tree" should not invoke the hook), but if I were designing it today then all operations that create a new commit and update the current branch with it might qualify.
Even that definition is a bit fuzzy: when I run "git rebase <upstream> <branch>", am I updating <branch> or the current branch? Are cherry-picks that carry over changes that were committed previously new commits?
Quoted 4 lines
Worse, we have the body of existing post-commit hooks to contend with, and each one may have made different assumptions about the semantics. We do not have the luxury of designing the hook without regard to that body of existing hooks today.
This seems like the most important point to me. I took a very cursory peek at some search results on Github
(https://github.com/search?l=&q=filename%3A%2Apost-commit%2A&type=Code)
and I see mostly shims - make sure certain files were included in the commit, bring a worktree up to date, stage stuff that wasn't committed - but again, that's a very brief glance at over 19000 probable post-commit hooks. Of course it's not possible for us to examine all of them, but having these 19k scripts being run lots of times when they didn't used to during an operation as common as rebase seems a little scary.
Quoted 8 lines
The hook that jiri installs is very simple. It wants to check when a user has (interactively) made a commit on a detached HEAD, to let them know that they might want to use "git checkout -b" afterward. With this particular hook, the behavior is better when git rebase does not invoke the hook, since in the context of a rebase, the user has no need to use "git checkout -b" afterward. This is a regression, and I think we need to take that seriously.
Quoted 4 lines
It doesn't seem like jiri is doing anything weird here, so there's a reasonably high probability that other hooks would be affected in the same way. How can we help authors of such hooks to handle this change?
This part I do disagree with. Git warns (advice-optional) when I enter detached HEAD state. When I leave my detached HEAD with some orphaned commits within (via checkout), I get a warning unconditionally, and an advice-optional tip to use 'git branch <name> <commit-id>'. The Jiri hook is warning that these commits I'm making could be lost, but in fact Git itself is telling me that (and how to save them) at the first moment when they are actually in danger. So I think this particular example is not a good one.
I'm more worried about ones like this, although in the rebase case it may well be a no-op as I can't start a rebase with a dirty index:
https://github.com/alphagov/digitalmarketplace-runner/blob/f8524d9ee7465caef0571d8beb8dff4b25d10fe7/hooks/post-commit
Perhaps a good question is "what could be very catastrophic if it happened during the middle of a rebase, but not such a bad thing if it happened when I run 'git commit'?"
Quoted 6 lines
I don't think that starting to invoke the 'post-commit' hook even more without going through that thought process is an acceptable fix. Since the "right" semantics aren't obvious here, the first step is probably to collect some typical examples of post-commit hooks, like the example from the message I'm replying to (thank you!). Ideas for where we can find more?
That Github search query I posted above is a pretty big haystack. I suppose plenty of folks who contribute to Git have a userbase and might be familiar with some workflows in use there, right?
- Emily
v4 cover rebase: make the default backend configurable
In reply to Elijah Newren via GitGitGadgetThis 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'
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.
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 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"
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.sh
base-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/679
Range-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: b50a1741e0 git-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-base in 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 --git a/t/t3433-rebase-options-compatibility.sh b/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"
- export GIT_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-whitespace
diff --git a/builtin/rebase.c b/builtin/rebase.c --- a/builtin/rebase.c
11: 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.sh b/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_fail git rebase main side &&
-+ test_must_fail git rebase --am main side &&
- git rebase --abort &&
-- git rebase --ignore-whitespace main side &&
-+ git rebase --am --ignore-whitespace main side &&
- test_cmp expect 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_cmp authortime 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 --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh
--- a/t/t5407-post-rewrite-hook.sh
+++ b/t/t5407-post-rewrite-hook.sh
13: 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"
Signature · 1 line
gitgitgadget
PATCH v4 1/19 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
Re: [PATCH v4 01/19] git-rebase.txt: update description of --allow-empty-message
Hi Elijah
Quoted 19 lines · Elijah Newren via GitGitGadget
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.Signed-off-by Elijah Newren <[email protected]>--- Documentation/git-rebase.txt | 7 ++++--- builtin/rebase.c | 12 +++++++----- 2 files changed, 11 insertions(+), 8 deletions(-)Documentation/git-rebase.txt +0
-0
Documentation/git-rebase.txt See also INCOMPATIBLE OPTIONS below. · line 271
Quoted 10 lines
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.
Do we want to mention that the user can pass --no-allow-empty-message to override this?
Best Wishes
Phillip
Quoted 34 lines
Documentation/git-rebase.txt +0
-0+ 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"),
Re: [PATCH v4 01/19] git-rebase.txt: update description of --allow-empty-message
Hi Phillip,
Quoted 3 lines · Phillip Wood
Hi Elijah
...
Quoted 7 lines
Quoted 7 lines
Documentation/git-rebase.txt +0
-0@@ -265,9 +265,10 @@ See also INCOMPATIBLE OPTIONS below. See also INCOMPATIBLE OPTIONS below.
Quoted 11 lines
Documentation/git-rebase.txt See also INCOMPATIBLE OPTIONS below. · line 271
Quoted 8 lines
Documentation/git-rebase.txt +4
-3--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.Do we want to mention that the user can pass --no-allow-empty-message to override this?
Interesting point. I could be persuaded towards that, but I'm leaning against it based on the logic in b00bf1c9a8dd which this commit was based upon.
However, even if we do want to mention --no-allow-empty-message, I'd rather do that in a different series. This patch is already slightly tangential to the series, but it at least fixes the documentation for a previously-documented option -- an option that happens to be similar in spirit to the ones being touched by the series. Adding documentation for a previously undocumented option, while possibly laudable, feels like taking a tangent to a tangent to me. (And this series is already long enough and is still hanging after two months, so...)
Elijah
PATCH v4 2/19 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 v4 3/19 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 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.
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 | 72 +++++++++++++++++++++++++++++++ t/t3427-rebase-subtree.sh | 12 +++--- 9 files changed, 128 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 +72 -0
72 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
Re: [PATCH v4 03/19] rebase (interactive-backend): make --keep-empty the default
Hi Elijah
Quoted 6 lines · Elijah Newren via GitGitGadget
From: 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 for the interactive backend.
--keep-empty was introduced by 90e1818f9a ("git-rebase: add keep_empty flag", 2012-04-20) and was respected for am based rebases as well (although the implementation was broken if there were any conflicts)
Quoted 7 lines
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.
Quoted 14 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 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.
The detailed commit message is great, however I think it would be good to point out that this changes things so that --no-keep-empty is parsed but does nothing - it is no longer possible to get that behavior. I'm not entirely sure about making --keep-empty hidden given that the am backend has a different behavior without it but as we're changing the default backend it probably does not matter and it looks like --keep-empty combined with an am specific option will still error out which is good.
Overall I like this change and it simplifies the implementation which is nice. Hopefully no one was relying on --no-keep-empty
Quoted 34 lines
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]>--- 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 | 72 +++++++++++++++++++++++++++++++ t/t3427-rebase-subtree.sh | 12 +++--- 9 files changed, 128 insertions(+), 55 deletions(-)create mode 100755
t/t3424-rebase-empty.shDocumentation/git-rebase.txt +0
-0
Documentation/git-rebase.txt See also INCOMPATIBLE OPTIONS below. · line 266
Quoted 10 lines
Documentation/git-rebase.txt +5
-2@@ -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.
Maybe mention that --no-keep-empty is no longer respected?
Quoted 52 lines
Documentation/git-rebase.txt +8
-9-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 +0
-2@@ -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; }
builtin/rebase.c static int run_rebase_interactive(struct rebase_options *opts, · line 449
Quoted 10 lines
builtin/rebase.c +10
-0+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 ; +}
This doesn't seem to handle --no-keep-empty (but looking through the patch that seems to be intentional)
Quoted 274 lines
builtin/rebase.c +8
-7static 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 # mt/t3424-rebase-empty.sh new file +49
-0@@ -0,0 +1,72 @@ +#!/bin/sh + +test_description='git rebase of commits that start or become empty' + +. ./test-lib.sh + +test_expect_success 'setup test repository' ' + test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers && + test_write_lines A B C D E F G H I J >letters && + git add numbers letters && + git commit -m A && + + git branch upstream && + git branch localmods && + + git checkout upstream && + test_write_lines A B C D E >letters && + git add letters && + git commit -m B && + + test_write_lines 1 2 3 4 five 6 7 8 9 ten >numbers && + git add numbers && + git commit -m C && + + git checkout localmods && + test_write_lines 1 2 3 4 five 6 7 8 9 10 >numbers && + git add numbers && + git commit -m C2 && + + git commit --allow-empty -m D && + + test_write_lines A B C D E >letters && + git add letters && + git commit -m "Five letters ought to be enough for anybody" +' + +test_expect_failure 'rebase (am-backend) with a variety of empty commits' ' + test_when_finished "git rebase --abort" && + git checkout -B testing localmods && + # rebase (--am) should not drop commits that start empty + git rebase upstream && + + test_write_lines D C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + +test_expect_failure 'rebase --merge with a variety of empty commits' ' + test_when_finished "git rebase --abort" &&
t/t3424-rebase-empty.sh line 59
Quoted 10 lines
t/t3424-rebase-empty.sh new file +10
-0+ 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 && + git log --format=%s >actual && + test_cmp expect actual +' + +GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITOR
This doesn't do any harm but I'm not sure it's needed
Best Wishes
Phillip
Quoted 47 lines
t/t3424-rebase-empty.sh new file +12
-0+test_expect_success 'rebase --interactive with a variety of empty commits' ' + git checkout -B testing localmods && + test_must_fail git rebase --interactive upstream && + + git rebase --skip && + + test_write_lines D C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + +test_donet/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"
Re: [PATCH v4 03/19] rebase (interactive-backend): make --keep-empty the default
Hi Phillip,
Quoted 6 lines · Phillip Wood
Hi Elijah
Quoted 2 lines · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
Quoted 8 lines
Quoted 4 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.
--keep-empty was introduced by
90e1818f9a("git-rebase: addkeep_emptyflag", 2012-04-20) and was respected for am based rebases as well (although the implementation was broken if there were any conflicts)
Interesting. If you look closely at commit 90e1818f9a, however, you'll note that they mentioned that format-patch and am were not capable of handling the --keep-empty behavior, and so they instead bypassed those commands and re-implemented a non-error-handling version of the sequencer backend (simply cherry-picking all the commits). Further, that re-implementation ignored other options that were passed and meant to be interpreted by format-patch or am due to the fact that format-patch and am were bypassed. Thus, by a strict reading, it looks like --keep-empty cannot be considered to have ever been respected by the am backend.
This was made more concrete by commit 002ee2fe6825 (builtin rebase:
support keep-empty option, 2018-09-04) when the --keep-empty flag was explicitly coded to imply the sequencer backend, thus making it clear that this option was incompatible with am-based rebases.
This was cemented further by commit 5dacd4abdd3c (git-rebase.txt:
document incompatible options, 2018-06-25), which made it clear to users that the flags were incompatible.
So, yeah, my commit message isn't quite accurate, but it's not quite right to claim that --keep-empty worked with the am backend either; the reality is a bit messier to explain.
Quoted 22 lines
Quoted 7 lines
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.Quoted 14 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 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.
Quoted 8 lines
The detailed commit message is great, however I think it would be good to point out that this changes things so that --no-keep-empty is parsed but does nothing - it is no longer possible to get that behavior. I'm not entirely sure about making --keep-empty hidden given that the am backend has a different behavior without it but as we're changing the default backend it probably does not matter and it looks like --keep-empty combined with an am specific option will still error out which is good.
As noted above, the --keep-empty option has always been orthogonal to the am backend, even if commit messages early on might have implied otherwise. Since it was only relevant for sequencer backend(s) and it's no longer relevant for the (now only) sequencer backend, I think hiding the option is a good idea.
Quoted 2 lines
Overall I like this change and it simplifies the implementation which is nice. Hopefully no one was relying on --no-keep-empty
:-)
Quoted 36 lines
Quoted 36 lines
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]>--- 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 | 72 +++++++++++++++++++++++++++++++ t/t3427-rebase-subtree.sh | 12 +++--- 9 files changed, 128 insertions(+), 55 deletions(-)create mode 100755
t/t3424-rebase-empty.shDocumentation/git-rebase.txt +0
-0@@ -259,10 +259,13 @@ See also INCOMPATIBLE OPTIONS below. unchanged as a result.
Quoted 10 lines
Documentation/git-rebase.txt See also INCOMPATIBLE OPTIONS below. · line 266
Quoted 8 lines
Documentation/git-rebase.txt +5
-2--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.Maybe mention that --no-keep-empty is no longer respected?
Possibly, but I'm somewhat of the mind that If this option wasn't worth documenting before (as evidenced by the fact that it wasn't) and wasn't all that interesting in the first place (the only thing it could do was override a previous --keep-empty), why add the documentation now --especially since it still only overrides a previous --keep-empty and overriding a no-op just amounts to a different-flavored no-op?
Quoted 344 lines
Quoted 52 lines
Documentation/git-rebase.txt +8
-9-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 +0
-2@@ -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; }builtin/rebase.c static int run_rebase_interactive(struct rebase_options *opts, · line 449
Quoted 10 lines
builtin/rebase.c +10
-0+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 ; +}This doesn't seem to handle --no-keep-empty (but looking through the patch that seems to be intentional)
Quoted 278 lines
builtin/rebase.c +8
-7static 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 # mt/t3424-rebase-empty.sh new file +53
-0@@ -0,0 +1,72 @@ +#!/bin/sh + +test_description='git rebase of commits that start or become empty' + +. ./test-lib.sh + +test_expect_success 'setup test repository' ' + test_write_lines 1 2 3 4 5 6 7 8 9 10 >numbers && + test_write_lines A B C D E F G H I J >letters && + git add numbers letters && + git commit -m A && + + git branch upstream && + git branch localmods && + + git checkout upstream && + test_write_lines A B C D E >letters && + git add letters && + git commit -m B && + + test_write_lines 1 2 3 4 five 6 7 8 9 ten >numbers && + git add numbers && + git commit -m C && + + git checkout localmods && + test_write_lines 1 2 3 4 five 6 7 8 9 10 >numbers && + git add numbers && + git commit -m C2 && + + git commit --allow-empty -m D && + + test_write_lines A B C D E >letters && + git add letters && + git commit -m "Five letters ought to be enough for anybody" +' + +test_expect_failure 'rebase (am-backend) with a variety of empty commits' ' + test_when_finished "git rebase --abort" && + git checkout -B testing localmods && + # rebase (--am) should not drop commits that start empty + git rebase upstream && + + test_write_lines D C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + +test_expect_failure 'rebase --merge with a variety of empty commits' ' + test_when_finished "git rebase --abort" && + git checkout -B testing localmods && + # rebase --merge should not halt on the commit that becomes empty + git rebase --merge upstream && +
Quoted 8 lines
t/t3424-rebase-empty.sh line 59
Quoted 6 lines
t/t3424-rebase-empty.sh new file +6
-0+ test_write_lines D C B A >expect && + git log --format=%s >actual && + test_cmp expect actual +' + +GIT_SEQUENCE_EDITOR=: && export GIT_SEQUENCE_EDITORThis doesn't do any harm but I'm not sure it's needed
Indeed, good catch. I'll remove it.
Thanks for digging in and providing feedback!
Elijah
PATCH v4 4/19 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 | 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 +24 -3
@@ -258,6 +258,22 @@ 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. ++ +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 +52 -0
102 lines not on this page · raw message
sequencer.c +37 -11
106 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 +43 -7
70 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