* [PATCH 0/3] rebase: learn --keep-base @ 2019-03-23 15:25 Denton Liu 2019-03-23 15:25 ` [PATCH 1/3] rebase: teach rebase --keep-base Denton Liu ` (5 more replies) 0 siblings, 6 replies; 123+ messages in thread From: Denton Liu @ 2019-03-23 15:25 UTC (permalink / raw) To: Git Mailing List This series teaches rebase the --keep-base option. 'git rebase --keep-base <upstream>' is equivalent to 'git rebase --onto <upstream>... <upstream>' or 'git rebase --onto $(git merge-base <upstream> HEAD) <upstream>' . This seems to be a common case that people (including myself!) run into; I was able to find these StackOverflow posts about this use case: * https://stackoverflow.com/questions/53234798/can-i-rebase-on-a-branchs-fork-point-without-explicitly-specifying-the-parent * https://stackoverflow.com/questions/41529128/how-do-you-rebase-only-changes-between-two-branches-into-another-branch * https://stackoverflow.com/a/4207357 Denton Liu (3): rebase: teach rebase --keep-base t3416: test rebase --keep-base git-rebase.txt: document --keep-base option Documentation/git-rebase.txt | 12 +++++-- builtin/rebase.c | 25 ++++++++++++-- t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 6 deletions(-) -- 2.21.0.512.g57bf1b23e1 ^ permalink raw reply [flat|nested] 123+ messages in thread
* [PATCH 1/3] rebase: teach rebase --keep-base 2019-03-23 15:25 [PATCH 0/3] rebase: learn --keep-base Denton Liu @ 2019-03-23 15:25 ` Denton Liu 2019-03-24 3:46 ` Eric Sunshine ` (4 more replies) 2019-03-23 15:25 ` [PATCH 2/3] t3416: test " Denton Liu ` (4 subsequent siblings) 5 siblings, 5 replies; 123+ messages in thread From: Denton Liu @ 2019-03-23 15:25 UTC (permalink / raw) To: Git Mailing List A common scenario is if a user is working on a topic branch and they wish to make some changes to intermediate commits or autosquashing, they would run something such as git rebase -i --onto master... master in order to preserve the merge base. This prevents unnecessary commit churning. Alternatively, a user wishing to test individual commits in a topic branch without changing anything may run git rebase -x ./test.sh master... master Since rebasing onto the merge base of the branch and the upstream is such a common case, introduce the --keep-base option as a shortcut. This allows us to rewrite the above as git rebase -i --keep-base master and git rebase -x ./test.sh --keep-base master respectively. Signed-off-by: Denton Liu <liu.denton@gmail.com> --- builtin/rebase.c | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 77deebc65c..fffee89064 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -27,8 +27,8 @@ #include "branch.h" static char const * const builtin_rebase_usage[] = { - N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " - "[<upstream>] [<branch>]"), + N_("git rebase [-i] [options] [--exec <cmd>] " + "[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"), N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " "--root [<branch>]"), N_("git rebase --continue | --abort | --skip | --edit-todo"), @@ -1018,6 +1018,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) }; const char *branch_name; int ret, flags, total_argc, in_progress = 0; + int keep_base = 0; int ok_to_skip_pre_rebase = 0; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; @@ -1051,6 +1052,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_STRING(0, "onto", &options.onto_name, N_("revision"), N_("rebase onto given branch instead of upstream")), + OPT_BOOL(0, "keep-base", &keep_base, + N_("use the merge-base of upstream and branch as the current base")), OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase, N_("allow pre-rebase hook to run")), OPT_NEGBIT('q', "quiet", &options.flags, @@ -1217,6 +1220,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) usage_with_options(builtin_rebase_usage, builtin_rebase_options); + if (keep_base) { + if (options.onto_name) + die(_("cannot combine '--keep-base' with '--onto'")); + if (options.root) + die(_("cannot combine '--keep-base' with '--root'")); + } + if (action != NO_ACTION && !in_progress) die(_("No rebase in progress?")); setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0); @@ -1541,10 +1551,19 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } /* Make sure the branch to rebase onto is valid. */ - if (!options.onto_name) + if (keep_base) { + strbuf_reset(&buf); + strbuf_addstr(&buf, options.upstream_name); + strbuf_addstr(&buf, "..."); + options.onto_name = xstrdup(buf.buf); + } else if (!options.onto_name) options.onto_name = options.upstream_name; if (strstr(options.onto_name, "...")) { if (get_oid_mb(options.onto_name, &merge_base) < 0) + if (keep_base) + die(_("'%s': need exactly one merge base with branch"), + options.upstream_name); + else die(_("'%s': need exactly one merge base"), options.onto_name); options.onto = lookup_commit_or_die(&merge_base, -- 2.21.0.512.g57bf1b23e1 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* Re: [PATCH 1/3] rebase: teach rebase --keep-base 2019-03-23 15:25 ` [PATCH 1/3] rebase: teach rebase --keep-base Denton Liu @ 2019-03-24 3:46 ` Eric Sunshine 2019-03-24 13:20 ` Junio C Hamano ` (3 subsequent siblings) 4 siblings, 0 replies; 123+ messages in thread From: Eric Sunshine @ 2019-03-24 3:46 UTC (permalink / raw) To: Denton Liu; +Cc: Git Mailing List On Sat, Mar 23, 2019 at 11:25 AM Denton Liu <liu.denton@gmail.com> wrote:> > [...] > Since rebasing onto the merge base of the branch and the upstream is > such a common case, introduce the --keep-base option as a shortcut. > [...] > Signed-off-by: Denton Liu <liu.denton@gmail.com> > --- > diff --git a/builtin/rebase.c b/builtin/rebase.c > @@ -1541,10 +1551,19 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > /* Make sure the branch to rebase onto is valid. */ > - if (!options.onto_name) > + if (keep_base) { > + strbuf_reset(&buf); > + strbuf_addstr(&buf, options.upstream_name); > + strbuf_addstr(&buf, "..."); > + options.onto_name = xstrdup(buf.buf); > + } else if (!options.onto_name) > options.onto_name = options.upstream_name; > if (strstr(options.onto_name, "...")) { > if (get_oid_mb(options.onto_name, &merge_base) < 0) > + if (keep_base) > + die(_("'%s': need exactly one merge base with branch"), > + options.upstream_name); > + else Style: Indent with tabs, not spaces. ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH 1/3] rebase: teach rebase --keep-base 2019-03-23 15:25 ` [PATCH 1/3] rebase: teach rebase --keep-base Denton Liu 2019-03-24 3:46 ` Eric Sunshine @ 2019-03-24 13:20 ` Junio C Hamano 2019-03-25 0:06 ` Denton Liu 2019-03-24 13:37 ` Junio C Hamano ` (2 subsequent siblings) 4 siblings, 1 reply; 123+ messages in thread From: Junio C Hamano @ 2019-03-24 13:20 UTC (permalink / raw) To: Denton Liu; +Cc: Git Mailing List Denton Liu <liu.denton@gmail.com> writes: > A common scenario is if a user is working on a topic branch and they > wish to make some changes to intermediate commits or autosquashing, they > would run something such as > > git rebase -i --onto master... master > > in order to preserve the merge base. This prevents unnecessary commit > churning. > > Alternatively, a user wishing to test individual commits in a topic > branch without changing anything may run > > git rebase -x ./test.sh master... master > > Since rebasing onto the merge base of the branch and the upstream is > such a common case, introduce the --keep-base option as a shortcut. > > This allows us to rewrite the above as > > git rebase -i --keep-base master > > and > > git rebase -x ./test.sh --keep-base master > > respectively. I never use the "feature" myself, but I recall that when "git rebase" is run on a branch appropriately prepared, you do not even have to say <upstream> (iow, you type "git rebase<RET>" and rebase on top of @{upstream}). Can this new "--keep-base" feature mesh well with it? When the current branch has forked from origin/master, for example, it would be good if $ git rebase -i --same-base becomes a usable short-hand for $ git rebase -i --same-base origin/master aka $ git rebase -i --onto $(git merge-base HEAD origin/master) origin/master ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH 1/3] rebase: teach rebase --keep-base 2019-03-24 13:20 ` Junio C Hamano @ 2019-03-25 0:06 ` Denton Liu 2019-03-25 5:41 ` Denton Liu 0 siblings, 1 reply; 123+ messages in thread From: Denton Liu @ 2019-03-25 0:06 UTC (permalink / raw) To: Junio C Hamano; +Cc: Git Mailing List Hi Junio, On Sun, Mar 24, 2019 at 10:20:28PM +0900, Junio C Hamano wrote: > Denton Liu <liu.denton@gmail.com> writes: > > > A common scenario is if a user is working on a topic branch and they > > wish to make some changes to intermediate commits or autosquashing, they > > would run something such as > > > > git rebase -i --onto master... master > > > > in order to preserve the merge base. This prevents unnecessary commit > > churning. > > > > Alternatively, a user wishing to test individual commits in a topic > > branch without changing anything may run > > > > git rebase -x ./test.sh master... master > > > > Since rebasing onto the merge base of the branch and the upstream is > > such a common case, introduce the --keep-base option as a shortcut. > > > > This allows us to rewrite the above as > > > > git rebase -i --keep-base master > > > > and > > > > git rebase -x ./test.sh --keep-base master > > > > respectively. > > I never use the "feature" myself, but I recall that when "git > rebase" is run on a branch appropriately prepared, you do not even > have to say <upstream> (iow, you type "git rebase<RET>" and rebase > on top of @{upstream}). > > Can this new "--keep-base" feature mesh well with it? When the > current branch has forked from origin/master, for example, it would > be good if > > $ git rebase -i --same-base > > becomes a usable short-hand for > > $ git rebase -i --same-base origin/master By "--same-base", I am assuming you mistyped and meant to write "--keep-base"? If that's the case, I can make it a shorthand. Thanks, Denton > > aka > > $ git rebase -i --onto $(git merge-base HEAD origin/master) origin/master > ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH 1/3] rebase: teach rebase --keep-base 2019-03-25 0:06 ` Denton Liu @ 2019-03-25 5:41 ` Denton Liu 2019-04-01 10:45 ` Junio C Hamano 0 siblings, 1 reply; 123+ messages in thread From: Denton Liu @ 2019-03-25 5:41 UTC (permalink / raw) To: Junio C Hamano; +Cc: Git Mailing List On Sun, Mar 24, 2019 at 05:06:18PM -0700, Denton Liu wrote: > Hi Junio, > > On Sun, Mar 24, 2019 at 10:20:28PM +0900, Junio C Hamano wrote: > > Denton Liu <liu.denton@gmail.com> writes: > > > > > A common scenario is if a user is working on a topic branch and they > > > wish to make some changes to intermediate commits or autosquashing, they > > > would run something such as > > > > > > git rebase -i --onto master... master > > > > > > in order to preserve the merge base. This prevents unnecessary commit > > > churning. > > > > > > Alternatively, a user wishing to test individual commits in a topic > > > branch without changing anything may run > > > > > > git rebase -x ./test.sh master... master > > > > > > Since rebasing onto the merge base of the branch and the upstream is > > > such a common case, introduce the --keep-base option as a shortcut. > > > > > > This allows us to rewrite the above as > > > > > > git rebase -i --keep-base master > > > > > > and > > > > > > git rebase -x ./test.sh --keep-base master > > > > > > respectively. > > > > I never use the "feature" myself, but I recall that when "git > > rebase" is run on a branch appropriately prepared, you do not even > > have to say <upstream> (iow, you type "git rebase<RET>" and rebase > > on top of @{upstream}). > > > > Can this new "--keep-base" feature mesh well with it? When the > > current branch has forked from origin/master, for example, it would > > be good if > > > > $ git rebase -i --same-base > > > > becomes a usable short-hand for > > > > $ git rebase -i --same-base origin/master > > By "--same-base", I am assuming you mistyped and meant to write > "--keep-base"? If that's the case, I can make it a shorthand. Sorry, I misunderstood your question. "--keep-base" already has the shorthand case handled by default. One caveat is that "--fork-point" is automatically implied if no upstream is supplied but this behaviour is the same for "--onto" or when no other options are supplied as well. I don't use the feature either so I'm not really sure if this behaviour would be the most sane thing to do. > Thanks, > > Denton > > > > > aka > > > > $ git rebase -i --onto $(git merge-base HEAD origin/master) origin/master > > ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH 1/3] rebase: teach rebase --keep-base 2019-03-25 5:41 ` Denton Liu @ 2019-04-01 10:45 ` Junio C Hamano 0 siblings, 0 replies; 123+ messages in thread From: Junio C Hamano @ 2019-04-01 10:45 UTC (permalink / raw) To: Denton Liu; +Cc: Git Mailing List Denton Liu <liu.denton@gmail.com> writes: >> > I never use the "feature" myself, but I recall that when "git >> > rebase" is run on a branch appropriately prepared, you do not even >> > have to say <upstream> (iow, you type "git rebase<RET>" and rebase >> > on top of @{upstream}). >> > >> > Can this new "--keep-base" feature mesh well with it? When the >> > current branch has forked from origin/master, for example, it would >> > be good if >> > >> > $ git rebase -i --same-base >> > >> > becomes a usable short-hand for >> > >> > $ git rebase -i --same-base origin/master >> >> By "--same-base", I am assuming you mistyped and meant to write >> "--keep-base"? If that's the case, I can make it a shorthand. > > Sorry, I misunderstood your question. "--keep-base" already has the > shorthand case handled by default. I actually think you understood _my_ question perfectly well, but misremembered what your implementation already supported ;-) If the new option works well with "the branch knows who its upstream is" feature already, so that the user does not have to type origin/master in the above example without doing anything special on your implementation's side, that is a great news. ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH 1/3] rebase: teach rebase --keep-base 2019-03-23 15:25 ` [PATCH 1/3] rebase: teach rebase --keep-base Denton Liu 2019-03-24 3:46 ` Eric Sunshine 2019-03-24 13:20 ` Junio C Hamano @ 2019-03-24 13:37 ` Junio C Hamano 2019-03-25 5:47 ` Denton Liu 2019-03-25 18:50 ` Johannes Schindelin 4 siblings, 0 replies; 123+ messages in thread From: Junio C Hamano @ 2019-03-24 13:37 UTC (permalink / raw) To: Denton Liu; +Cc: Git Mailing List Denton Liu <liu.denton@gmail.com> writes: > if (strstr(options.onto_name, "...")) { > if (get_oid_mb(options.onto_name, &merge_base) < 0) > + if (keep_base) > + die(_("'%s': need exactly one merge base with branch"), > + options.upstream_name); > + else > die(_("'%s': need exactly one merge base"), > options.onto_name); If you turn a single statement body into if/else, have {} around the body of the outer if, i.e. if (get_oid_mb(...) < 0) { if (keep_base) die(_("'%s': need exactly one merge base with branch"), options.upstream_name); else die(_("'%s': need exactly one merge base"), options.onto_name); } Otherwise -Werror=danglng-else will stop compilation. Thanks. ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH 1/3] rebase: teach rebase --keep-base 2019-03-23 15:25 ` [PATCH 1/3] rebase: teach rebase --keep-base Denton Liu ` (2 preceding siblings ...) 2019-03-24 13:37 ` Junio C Hamano @ 2019-03-25 5:47 ` Denton Liu 2019-03-25 18:50 ` Johannes Schindelin 4 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-03-25 5:47 UTC (permalink / raw) To: Git Mailing List On Sat, Mar 23, 2019 at 08:25:28AM -0700, Denton Liu wrote: > A common scenario is if a user is working on a topic branch and they > wish to make some changes to intermediate commits or autosquashing, they Sorry, small typo here: s/autosquashing/autosquash/ -Denton > would run something such as > > git rebase -i --onto master... master > > in order to preserve the merge base. This prevents unnecessary commit > churning. > > Alternatively, a user wishing to test individual commits in a topic > branch without changing anything may run > > git rebase -x ./test.sh master... master > > Since rebasing onto the merge base of the branch and the upstream is > such a common case, introduce the --keep-base option as a shortcut. > > This allows us to rewrite the above as > > git rebase -i --keep-base master > > and > > git rebase -x ./test.sh --keep-base master > > respectively. > > Signed-off-by: Denton Liu <liu.denton@gmail.com> > --- > builtin/rebase.c | 25 ++++++++++++++++++++++--- > 1 file changed, 22 insertions(+), 3 deletions(-) > > diff --git a/builtin/rebase.c b/builtin/rebase.c > index 77deebc65c..fffee89064 100644 > --- a/builtin/rebase.c > +++ b/builtin/rebase.c > @@ -27,8 +27,8 @@ > #include "branch.h" > > static char const * const builtin_rebase_usage[] = { > - N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " > - "[<upstream>] [<branch>]"), > + N_("git rebase [-i] [options] [--exec <cmd>] " > + "[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"), > N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " > "--root [<branch>]"), > N_("git rebase --continue | --abort | --skip | --edit-todo"), > @@ -1018,6 +1018,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > }; > const char *branch_name; > int ret, flags, total_argc, in_progress = 0; > + int keep_base = 0; > int ok_to_skip_pre_rebase = 0; > struct strbuf msg = STRBUF_INIT; > struct strbuf revisions = STRBUF_INIT; > @@ -1051,6 +1052,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > OPT_STRING(0, "onto", &options.onto_name, > N_("revision"), > N_("rebase onto given branch instead of upstream")), > + OPT_BOOL(0, "keep-base", &keep_base, > + N_("use the merge-base of upstream and branch as the current base")), > OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase, > N_("allow pre-rebase hook to run")), > OPT_NEGBIT('q', "quiet", &options.flags, > @@ -1217,6 +1220,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > usage_with_options(builtin_rebase_usage, > builtin_rebase_options); > > + if (keep_base) { > + if (options.onto_name) > + die(_("cannot combine '--keep-base' with '--onto'")); > + if (options.root) > + die(_("cannot combine '--keep-base' with '--root'")); > + } > + > if (action != NO_ACTION && !in_progress) > die(_("No rebase in progress?")); > setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0); > @@ -1541,10 +1551,19 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > } > > /* Make sure the branch to rebase onto is valid. */ > - if (!options.onto_name) > + if (keep_base) { > + strbuf_reset(&buf); > + strbuf_addstr(&buf, options.upstream_name); > + strbuf_addstr(&buf, "..."); > + options.onto_name = xstrdup(buf.buf); > + } else if (!options.onto_name) > options.onto_name = options.upstream_name; > if (strstr(options.onto_name, "...")) { > if (get_oid_mb(options.onto_name, &merge_base) < 0) > + if (keep_base) > + die(_("'%s': need exactly one merge base with branch"), > + options.upstream_name); > + else > die(_("'%s': need exactly one merge base"), > options.onto_name); > options.onto = lookup_commit_or_die(&merge_base, > -- > 2.21.0.512.g57bf1b23e1 > ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH 1/3] rebase: teach rebase --keep-base 2019-03-23 15:25 ` [PATCH 1/3] rebase: teach rebase --keep-base Denton Liu ` (3 preceding siblings ...) 2019-03-25 5:47 ` Denton Liu @ 2019-03-25 18:50 ` Johannes Schindelin 2019-03-25 19:29 ` Denton Liu 4 siblings, 1 reply; 123+ messages in thread From: Johannes Schindelin @ 2019-03-25 18:50 UTC (permalink / raw) To: Denton Liu; +Cc: Git Mailing List Hi Denton, On Sat, 23 Mar 2019, Denton Liu wrote: > [...] > > This allows us to rewrite the above as > > git rebase -i --keep-base master > > and > > git rebase -x ./test.sh --keep-base master > > respectively. Just a quick note: this breaks t5407 because that test uses `git rebase --keep` and expects that abbreviated option to be expanded to `--keep-empty`, which is now no longer the only possible expansion. I just submitted a patch series to fix that, and other uses of abbreviated options in the test suite, in https://public-inbox.org/git/pull.167.git.gitgitgadget@gmail.com/T/#t Ciao, Johannes P.S.: Did you run the test suite before submitting your patches? ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH 1/3] rebase: teach rebase --keep-base 2019-03-25 18:50 ` Johannes Schindelin @ 2019-03-25 19:29 ` Denton Liu 2019-03-26 13:27 ` Johannes Schindelin 0 siblings, 1 reply; 123+ messages in thread From: Denton Liu @ 2019-03-25 19:29 UTC (permalink / raw) To: Johannes Schindelin; +Cc: Git Mailing List Hi Johannes, On Mon, Mar 25, 2019 at 07:50:38PM +0100, Johannes Schindelin wrote: > Hi Denton, > > On Sat, 23 Mar 2019, Denton Liu wrote: > > > [...] > > > > This allows us to rewrite the above as > > > > git rebase -i --keep-base master > > > > and > > > > git rebase -x ./test.sh --keep-base master > > > > respectively. > > Just a quick note: this breaks t5407 because that test uses `git rebase > --keep` and expects that abbreviated option to be expanded to > `--keep-empty`, which is now no longer the only possible expansion. > > I just submitted a patch series to fix that, and other uses of abbreviated > options in the test suite, in > https://public-inbox.org/git/pull.167.git.gitgitgadget@gmail.com/T/#t Thanks for catching this. I replied with a (tiny) review. > > Ciao, > Johannes > > P.S.: Did you run the test suite before submitting your patches? Usually I'm more diligent about running tests but I wrote this patchset in the back of a car when I was running low on batteries. I only ran the rebase-related tests but I guess that wasn't enough. My mistake, though. I'll be sure to _always_ run tests in the future. Thanks, Denton ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH 1/3] rebase: teach rebase --keep-base 2019-03-25 19:29 ` Denton Liu @ 2019-03-26 13:27 ` Johannes Schindelin 0 siblings, 0 replies; 123+ messages in thread From: Johannes Schindelin @ 2019-03-26 13:27 UTC (permalink / raw) To: Denton Liu; +Cc: Git Mailing List Hi Denton, On Mon, 25 Mar 2019, Denton Liu wrote: > On Mon, Mar 25, 2019 at 07:50:38PM +0100, Johannes Schindelin wrote: > > > P.S.: Did you run the test suite before submitting your patches? > > Usually I'm more diligent about running tests but I wrote this patchset > in the back of a car when I was running low on batteries. I only ran the > rebase-related tests but I guess that wasn't enough. I totally understand that kind of scenario. > My mistake, though. I'll be sure to _always_ run tests in the future. Please do not feel bad. I did work pretty hard on making the Azure Pipelines support as fast as it is for the very purpose of helping exactly situations like yours: to hand off the testing to the cloud when batteries run low (or when you want to use your laptop for something else than running Git's test suite). And of course to verify that you don't break things on platforms other than the one you happen to develop on. And to check the documentation. And to run some static analyses. :-) Maybe you want to open PRs at https://github.com/git/git or at https://github.com/gitgitgadget/git to benefit from that kind of automatic testing, benefitting from my work (which would make me feel real good about spending that amount of time on it). Ciao, Johannes ^ permalink raw reply [flat|nested] 123+ messages in thread
* [PATCH 2/3] t3416: test rebase --keep-base 2019-03-23 15:25 [PATCH 0/3] rebase: learn --keep-base Denton Liu 2019-03-23 15:25 ` [PATCH 1/3] rebase: teach rebase --keep-base Denton Liu @ 2019-03-23 15:25 ` Denton Liu 2019-03-23 15:25 ` [PATCH 3/3] git-rebase.txt: document --keep-base option Denton Liu ` (3 subsequent siblings) 5 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-03-23 15:25 UTC (permalink / raw) To: Git Mailing List Test rebase --keep-base to ensure it works correctly in the normal case and fails when there are multiple merge-bases, both in regular and interactive mode. Also, test to make sure conflicting options causes rebase to fail. While we're at it, add a missing set_fake_editor call to 'rebase -i --onto master...side'. Signed-off-by: Denton Liu <liu.denton@gmail.com> --- t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh index ddf2f64853..9c2548423b 100755 --- a/t/t3416-rebase-onto-threedots.sh +++ b/t/t3416-rebase-onto-threedots.sh @@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' ' git checkout side && git reset --hard K && + set_fake_editor && test_must_fail git rebase -i --onto master...side J ' +test_expect_success 'rebase --keep-base --onto incompatible' ' + test_must_fail git rebase --keep-base --onto master... +' + +test_expect_success 'rebase --keep-base --root incompatible' ' + test_must_fail git rebase --keep-base --root +' + +test_expect_success 'rebase --keep-base master from topic' ' + git reset --hard && + git checkout topic && + git reset --hard G && + + git rebase --keep-base master && + git rev-parse C >base.expect && + git merge-base master HEAD >base.actual && + test_cmp base.expect base.actual && + + git rev-parse HEAD~2 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase --keep-base master from side' ' + git reset --hard && + git checkout side && + git reset --hard K && + + test_must_fail git rebase --keep-base master +' + +test_expect_success 'rebase -i --keep-base master from topic' ' + git reset --hard && + git checkout topic && + git reset --hard G && + + set_fake_editor && + EXPECT_COUNT=2 git rebase -i --keep-base master && + git rev-parse C >base.expect && + git merge-base master HEAD >base.actual && + test_cmp base.expect base.actual && + + git rev-parse HEAD~2 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase -i --keep-base master from side' ' + git reset --hard && + git checkout side && + git reset --hard K && + + set_fake_editor && + test_must_fail git rebase -i --keep-base master +' + test_done -- 2.21.0.512.g57bf1b23e1 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH 3/3] git-rebase.txt: document --keep-base option 2019-03-23 15:25 [PATCH 0/3] rebase: learn --keep-base Denton Liu 2019-03-23 15:25 ` [PATCH 1/3] rebase: teach rebase --keep-base Denton Liu 2019-03-23 15:25 ` [PATCH 2/3] t3416: test " Denton Liu @ 2019-03-23 15:25 ` Denton Liu 2019-03-24 13:15 ` [PATCH 0/3] rebase: learn --keep-base Junio C Hamano ` (2 subsequent siblings) 5 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-03-23 15:25 UTC (permalink / raw) To: Git Mailing List Document the --keep-base rebase option. While we're at it, change an instance of "merge-base" to "merge base", which is the consistent spelling. Signed-off-by: Denton Liu <liu.denton@gmail.com> --- Documentation/git-rebase.txt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 6363d674b7..780d381667 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip SYNOPSIS -------- [verse] -'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] - [<upstream> [<branch>]] +'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] + [--onto <newbase> | --keep-base] [<upstream> [<branch>]] 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] --root [<branch>] 'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch @@ -217,6 +217,12 @@ As a special case, you may use "A\...B" as a shortcut for the merge base of A and B if there is exactly one merge base. You can leave out at most one of A and B, in which case it defaults to HEAD. +--keep-base:: + Set the starting point at which to create the new commits to the + merge base of <upstream> <branch>. Running + 'git rebase --keep-base <upstream> <branch>' is equivalent to + running 'git rebase --onto <upstream>... <upstream>'. + <upstream>:: Upstream branch to compare against. May be any valid commit, not just an existing branch name. Defaults to the configured @@ -863,7 +869,7 @@ NOTE: While an "easy case recovery" sometimes appears to be successful --interactive` will be **resurrected**! The idea is to manually tell 'git rebase' "where the old 'subsystem' -ended and your 'topic' began", that is, what the old merge-base +ended and your 'topic' began", that is, what the old merge base between them was. You will have to find a way to name the last commit of the old 'subsystem', for example: -- 2.21.0.512.g57bf1b23e1 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* Re: [PATCH 0/3] rebase: learn --keep-base 2019-03-23 15:25 [PATCH 0/3] rebase: learn --keep-base Denton Liu ` (2 preceding siblings ...) 2019-03-23 15:25 ` [PATCH 3/3] git-rebase.txt: document --keep-base option Denton Liu @ 2019-03-24 13:15 ` Junio C Hamano 2019-03-25 0:04 ` Denton Liu 2019-03-26 14:35 ` Ævar Arnfjörð Bjarmason 2019-03-28 22:17 ` [PATCH v2] rebase: teach rebase --keep-base Denton Liu 5 siblings, 1 reply; 123+ messages in thread From: Junio C Hamano @ 2019-03-24 13:15 UTC (permalink / raw) To: Denton Liu; +Cc: Git Mailing List Denton Liu <liu.denton@gmail.com> writes: > This series teaches rebase the --keep-base option. > > 'git rebase --keep-base <upstream>' is equivalent to > 'git rebase --onto <upstream>... <upstream>' or > 'git rebase --onto $(git merge-base <upstream> HEAD) <upstream>' . > > This seems to be a common case that people (including myself!) run > into; I was able to find these StackOverflow posts about this use > case: Since this is 0/3 I won't complain too loudly, but read the above again while pretending that you didn't know what your initial motivating example was. The last three lines does not explain anything useful to such a reader, and the reader needs to decipher the two sample commands to guess what you wanted to achieve. Before "teaches rebase the --keep-base option", you must tell what you wanted to do with that new feature to attract readers---convince them your new contribution is worth their time to read over. If I understand correctly, what "--onto $(git merge-base @{u} HEAD) @{u}" lets you express is: no matter how much progress the upstream has made while I was away from the keyboard, I want to rebase the current topic on top of the same commit from the upstream, on which I based the current iteration of the topic. I suspect that such a rebase will become no-op without "-i". Am I mistaken? I am not sure if "--keep-base" is useful without "-i". But of course, it would be useful with "-i", i.e. when you want to further polish the topic. You need to give <upstream> to the command to let it know where their work stops and your work begins, i.e. letting the command figure out what commits to replay. But in such a workflow, you do not want <upstream> to affect on top of what commit the replayed history is built. And "keep base" would be a very direct way to achieve that. ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH 0/3] rebase: learn --keep-base 2019-03-24 13:15 ` [PATCH 0/3] rebase: learn --keep-base Junio C Hamano @ 2019-03-25 0:04 ` Denton Liu 2019-04-01 10:45 ` Junio C Hamano 0 siblings, 1 reply; 123+ messages in thread From: Denton Liu @ 2019-03-25 0:04 UTC (permalink / raw) To: Junio C Hamano; +Cc: Git Mailing List Hi Junio, On Sun, Mar 24, 2019 at 10:15:31PM +0900, Junio C Hamano wrote: > Denton Liu <liu.denton@gmail.com> writes: > > > This series teaches rebase the --keep-base option. > > > > 'git rebase --keep-base <upstream>' is equivalent to > > 'git rebase --onto <upstream>... <upstream>' or > > 'git rebase --onto $(git merge-base <upstream> HEAD) <upstream>' . > > > > This seems to be a common case that people (including myself!) run > > into; I was able to find these StackOverflow posts about this use > > case: > > Since this is 0/3 I won't complain too loudly, but read the above > again while pretending that you didn't know what your initial > motivating example was. The last three lines does not explain > anything useful to such a reader, and the reader needs to decipher > the two sample commands to guess what you wanted to achieve. > > Before "teaches rebase the --keep-base option", you must tell what > you wanted to do with that new feature to attract readers---convince > them your new contribution is worth their time to read over. > > If I understand correctly, what "--onto $(git merge-base @{u} HEAD) @{u}" > lets you express is: > > no matter how much progress the upstream has made while I > was away from the keyboard, I want to rebase the current > topic on top of the same commit from the upstream, on which > I based the current iteration of the topic. Thanks for the clarification. I'll try my best to write future cover letters more clearly. > > I suspect that such a rebase will become no-op without "-i". Am I > mistaken? I am not sure if "--keep-base" is useful without "-i". It's useful in the case of "-x", although that is a grey area since "-x" uses interactive machinery internally. Aside from "-x", I can't really think of a situation where we would use "--keep-base" without "-i". > > But of course, it would be useful with "-i", i.e. when you want to > further polish the topic. You need to give <upstream> to the command > to let it know where their work stops and your work begins, > i.e. letting the command figure out what commits to replay. But in > such a workflow, you do not want <upstream> to affect on top of what > commit the replayed history is built. And "keep base" would be a > very direct way to achieve that. > > > Thanks, Denton ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH 0/3] rebase: learn --keep-base 2019-03-25 0:04 ` Denton Liu @ 2019-04-01 10:45 ` Junio C Hamano 0 siblings, 0 replies; 123+ messages in thread From: Junio C Hamano @ 2019-04-01 10:45 UTC (permalink / raw) To: Denton Liu; +Cc: Git Mailing List Denton Liu <liu.denton@gmail.com> writes: >> I suspect that such a rebase will become no-op without "-i". Am I >> mistaken? I am not sure if "--keep-base" is useful without "-i". > > It's useful in the case of "-x", although that is a grey area since "-x" > uses interactive machinery internally. Aside from "-x", I can't really > think of a situation where we would use "--keep-base" without "-i". I consider "-x" is a mere sugar-coat around "-i". What I was getting at was if we need some mention of the fact in the documentation (e.g. "the option is accepted but fairly useless unless you are doing the rebase '-i/-x'"). ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH 0/3] rebase: learn --keep-base 2019-03-23 15:25 [PATCH 0/3] rebase: learn --keep-base Denton Liu ` (3 preceding siblings ...) 2019-03-24 13:15 ` [PATCH 0/3] rebase: learn --keep-base Junio C Hamano @ 2019-03-26 14:35 ` Ævar Arnfjörð Bjarmason 2019-03-26 17:50 ` Denton Liu 2019-03-28 22:17 ` [PATCH v2] rebase: teach rebase --keep-base Denton Liu 5 siblings, 1 reply; 123+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2019-03-26 14:35 UTC (permalink / raw) To: Denton Liu; +Cc: Git Mailing List On Sat, Mar 23 2019, Denton Liu wrote: > This series teaches rebase the --keep-base option. > > 'git rebase --keep-base <upstream>' is equivalent to > 'git rebase --onto <upstream>... <upstream>' or > 'git rebase --onto $(git merge-base <upstream> HEAD) <upstream>' . > > This seems to be a common case that people (including myself!) run into; I was > able to find these StackOverflow posts about this use case: > > * https://stackoverflow.com/questions/53234798/can-i-rebase-on-a-branchs-fork-point-without-explicitly-specifying-the-parent > * https://stackoverflow.com/questions/41529128/how-do-you-rebase-only-changes-between-two-branches-into-another-branch > * https://stackoverflow.com/a/4207357 Like with another series of yours I think this would be best squashed into one patch. Maybe I've misunderstood this but isn't this like --fork-point except with just plain "git merge-base" instead of "git merge-base --fork-point", but then again 2/3 shows multiple base aren't supported, but merge-base supports that. I'd find something like the "DISCUSSION ON FORK-POINT MODE" in git-merge-base helpful with examples of what we'd pick in the various scenarios, and also if whatever commit this picks was something you could have "git merge-base" spew out, so you could get what rebase would do here from other tooling (which maybe is possible, but I'm confused by the "no multiple bases"...). ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH 0/3] rebase: learn --keep-base 2019-03-26 14:35 ` Ævar Arnfjörð Bjarmason @ 2019-03-26 17:50 ` Denton Liu 2019-03-26 20:35 ` Ævar Arnfjörð Bjarmason 0 siblings, 1 reply; 123+ messages in thread From: Denton Liu @ 2019-03-26 17:50 UTC (permalink / raw) To: Ævar Arnfjörð Bjarmason; +Cc: Git Mailing List Hi Ævar, On Tue, Mar 26, 2019 at 03:35:34PM +0100, Ævar Arnfjörð Bjarmason wrote: > > On Sat, Mar 23 2019, Denton Liu wrote: > > > This series teaches rebase the --keep-base option. > > > > 'git rebase --keep-base <upstream>' is equivalent to > > 'git rebase --onto <upstream>... <upstream>' or > > 'git rebase --onto $(git merge-base <upstream> HEAD) <upstream>' . > > > > This seems to be a common case that people (including myself!) run into; I was > > able to find these StackOverflow posts about this use case: > > > > * https://stackoverflow.com/questions/53234798/can-i-rebase-on-a-branchs-fork-point-without-explicitly-specifying-the-parent > > * https://stackoverflow.com/questions/41529128/how-do-you-rebase-only-changes-between-two-branches-into-another-branch > > * https://stackoverflow.com/a/4207357 > > Like with another series of yours I think this would be best squashed > into one patch. Will do. > > Maybe I've misunderstood this but isn't this like --fork-point except > with just plain "git merge-base" instead of "git merge-base > --fork-point", but then again 2/3 shows multiple base aren't supported, > but merge-base supports that. > --fork-point gets used to determine the _set of_ commits which are to be rebased, whereas --keep-base (and --onto) determine the base where that set of commits will be spliced. As a result, these two options cover orthogonal use-cases. The reason why --keep-base doesn't support multiple bases for the same reason that --onto already disallows multiple bases. If we have multiple bases, how do we determine which one is the "true base" to use? It makes more sense to error out and let the user manually specify it. > I'd find something like the "DISCUSSION ON FORK-POINT MODE" in > git-merge-base helpful with examples of what we'd pick in the various > scenarios, and also if whatever commit this picks was something you > could have "git merge-base" spew out, so you could get what rebase would > do here from other tooling (which maybe is possible, but I'm confused by > the "no multiple bases"...). If I'm understanding you correctly then yes, this could be done with other tooling. See the 0/3 for equivalent commands. Perhaps I should update the rebase documentation to mention that --fork-point and --keep-base are orthogonal because it's unclear for you, it's probably unclear for other users as well. Thanks, Denton ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH 0/3] rebase: learn --keep-base 2019-03-26 17:50 ` Denton Liu @ 2019-03-26 20:35 ` Ævar Arnfjörð Bjarmason 2019-03-26 21:30 ` Denton Liu 0 siblings, 1 reply; 123+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2019-03-26 20:35 UTC (permalink / raw) To: Denton Liu; +Cc: Git Mailing List, Jeff King On Tue, Mar 26 2019, Denton Liu wrote: > Hi Ævar, > > On Tue, Mar 26, 2019 at 03:35:34PM +0100, Ævar Arnfjörð Bjarmason wrote: >> >> On Sat, Mar 23 2019, Denton Liu wrote: >> >> > This series teaches rebase the --keep-base option. >> > >> > 'git rebase --keep-base <upstream>' is equivalent to >> > 'git rebase --onto <upstream>... <upstream>' or >> > 'git rebase --onto $(git merge-base <upstream> HEAD) <upstream>' . >> > >> > This seems to be a common case that people (including myself!) run into; I was >> > able to find these StackOverflow posts about this use case: >> > >> > * https://stackoverflow.com/questions/53234798/can-i-rebase-on-a-branchs-fork-point-without-explicitly-specifying-the-parent >> > * https://stackoverflow.com/questions/41529128/how-do-you-rebase-only-changes-between-two-branches-into-another-branch >> > * https://stackoverflow.com/a/4207357 >> >> Like with another series of yours I think this would be best squashed >> into one patch. > > Will do. > >> >> Maybe I've misunderstood this but isn't this like --fork-point except >> with just plain "git merge-base" instead of "git merge-base >> --fork-point", but then again 2/3 shows multiple base aren't supported, >> but merge-base supports that. >> > > --fork-point gets used to determine the _set of_ commits which are to be > rebased, whereas --keep-base (and --onto) determine the base where that > set of commits will be spliced. As a result, these two options cover > orthogonal use-cases. Right. After playing with this a bit more though --fork-point is mostly there, it it does find the same fork point, as evidenced all your tests (that aren't asserting incompatibility with other options) passing with this: diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh index 9c2548423b..ab2d50e69a 100755 --- a/t/t3416-rebase-onto-threedots.sh +++ b/t/t3416-rebase-onto-threedots.sh @@ -116,7 +116,7 @@ test_expect_success 'rebase --keep-base master from topic' ' git checkout topic && git reset --hard G && - git rebase --keep-base master && + git rebase $(git merge-base --fork-point master HEAD) && git rev-parse C >base.expect && git merge-base master HEAD >base.actual && test_cmp base.expect base.actual && @@ -140,7 +140,7 @@ test_expect_success 'rebase -i --keep-base master from topic' ' git reset --hard G && set_fake_editor && - EXPECT_COUNT=2 git rebase -i --keep-base master && + EXPECT_COUNT=2 git rebase -i $(git merge-base --fork-point master HEAD) && git rev-parse C >base.expect && git merge-base master HEAD >base.actual && test_cmp base.expect base.actual && I've poked at some of this recently in https://public-inbox.org/git/20190221214059.9195-3-avarab@gmail.com/ as noted in the feedback there (I haven't gotten around to v2 yet) it's entirely possible that I haven't understood this at all :) But it seems to me that this patch/implementation conflates two unrelated things. Once is that we use --fork-point to mean that we're going to find the divergence point with "merge-base --fork-point". This gets you halfway to where you want to be, i.e. AFAICT the --keep-base and --fork-point will always find the same commit for "git rebase" and "git rebase --keep-base". See the "options.restrict_revision = get_fork_point(...)" part of the code. The other, which you want to disable, is that --fork-point *also* says "OK, once we've found the divergence point, let's then rebase it on the latest upstream. Or in the example above the "master" part of "git merge-base --fork-point master HEAD". Shouldn't --keep-base just be implemented in terms of skipping *that* part, i.e. we find the fork point using the upstream info, but then don't rebase *on* upstream. The reason the distinction matters is because with your patch these two act differently: git rebase --keep-base git rebase $(git merge-base @{u} HEAD) The latter will skip work ("Current branch master is up to date"), but --keep-base will always re-rebase things. There's some cases where --fork-point does that, which I was trying to address with my linked WIP patch above. Whereas the thing you actually want to work is: git rebase -i --keep-base git rebase -i $(git merge-base @{u} HEAD) I.e. to have both of those allow you to re-arrange/fixup whatever and still rebase on the same divergence point with @{u}, and won't run rebase when there's no work to do unless you give it --force-rebase. > reason that --onto already disallows multiple bases. If we have multiple > bases, how do we determine which one is the "true base" to use? It makes > more sense to error out and let the user manually specify it. Ah, makes sense. >> I'd find something like the "DISCUSSION ON FORK-POINT MODE" in >> git-merge-base helpful with examples of what we'd pick in the various >> scenarios, and also if whatever commit this picks was something you >> could have "git merge-base" spew out, so you could get what rebase would >> do here from other tooling (which maybe is possible, but I'm confused by >> the "no multiple bases"...). > > If I'm understanding you correctly then yes, this could be done with > other tooling. See the 0/3 for equivalent commands. > > Perhaps I should update the rebase documentation to mention that > --fork-point and --keep-base are orthogonal because it's unclear for > you, it's probably unclear for other users as well. > > Thanks, > > Denton ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH 0/3] rebase: learn --keep-base 2019-03-26 20:35 ` Ævar Arnfjörð Bjarmason @ 2019-03-26 21:30 ` Denton Liu 2019-03-27 15:39 ` Ævar Arnfjörð Bjarmason 0 siblings, 1 reply; 123+ messages in thread From: Denton Liu @ 2019-03-26 21:30 UTC (permalink / raw) To: Ævar Arnfjörð Bjarmason; +Cc: Git Mailing List, Jeff King Hi Ævar, On Tue, Mar 26, 2019 at 09:35:48PM +0100, Ævar Arnfjörð Bjarmason wrote: > > On Tue, Mar 26 2019, Denton Liu wrote: > > > Hi Ævar, > > > > On Tue, Mar 26, 2019 at 03:35:34PM +0100, Ævar Arnfjörð Bjarmason wrote: > >> > >> On Sat, Mar 23 2019, Denton Liu wrote: > >> > >> > This series teaches rebase the --keep-base option. > >> > > >> > 'git rebase --keep-base <upstream>' is equivalent to > >> > 'git rebase --onto <upstream>... <upstream>' or > >> > 'git rebase --onto $(git merge-base <upstream> HEAD) <upstream>' . > >> > > >> > This seems to be a common case that people (including myself!) run into; I was > >> > able to find these StackOverflow posts about this use case: > >> > > >> > * https://stackoverflow.com/questions/53234798/can-i-rebase-on-a-branchs-fork-point-without-explicitly-specifying-the-parent > >> > * https://stackoverflow.com/questions/41529128/how-do-you-rebase-only-changes-between-two-branches-into-another-branch > >> > * https://stackoverflow.com/a/4207357 > >> > >> Like with another series of yours I think this would be best squashed > >> into one patch. > > > > Will do. > > > >> > >> Maybe I've misunderstood this but isn't this like --fork-point except > >> with just plain "git merge-base" instead of "git merge-base > >> --fork-point", but then again 2/3 shows multiple base aren't supported, > >> but merge-base supports that. > >> > > > > --fork-point gets used to determine the _set of_ commits which are to be > > rebased, whereas --keep-base (and --onto) determine the base where that > > set of commits will be spliced. As a result, these two options cover > > orthogonal use-cases. > > Right. After playing with this a bit more though --fork-point is mostly > there, it it does find the same fork point, as evidenced all your tests > (that aren't asserting incompatibility with other options) passing with > this: > > diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh > index 9c2548423b..ab2d50e69a 100755 > --- a/t/t3416-rebase-onto-threedots.sh > +++ b/t/t3416-rebase-onto-threedots.sh > @@ -116,7 +116,7 @@ test_expect_success 'rebase --keep-base master from topic' ' > git checkout topic && > git reset --hard G && > > - git rebase --keep-base master && > + git rebase $(git merge-base --fork-point master HEAD) && > git rev-parse C >base.expect && > git merge-base master HEAD >base.actual && > test_cmp base.expect base.actual && > @@ -140,7 +140,7 @@ test_expect_success 'rebase -i --keep-base master from topic' ' > git reset --hard G && > > set_fake_editor && > - EXPECT_COUNT=2 git rebase -i --keep-base master && > + EXPECT_COUNT=2 git rebase -i $(git merge-base --fork-point master HEAD) && > git rev-parse C >base.expect && > git merge-base master HEAD >base.actual && > test_cmp base.expect base.actual && > > I've poked at some of this recently in > https://public-inbox.org/git/20190221214059.9195-3-avarab@gmail.com/ as > noted in the feedback there (I haven't gotten around to v2 yet) it's > entirely possible that I haven't understood this at all :) > > But it seems to me that this patch/implementation conflates two > unrelated things. > > Once is that we use --fork-point to mean that we're going to find the > divergence point with "merge-base --fork-point". This gets you halfway > to where you want to be, i.e. AFAICT the --keep-base and --fork-point > will always find the same commit for "git rebase" and "git rebase > --keep-base". See the "options.restrict_revision = get_fork_point(...)" > part of the code. I don't think this is true. The code that --keep-base uses to find the merge base is get_oid_mb, see the relevant snippet if (strstr(options.onto_name, "...")) { if (get_oid_mb(options.onto_name, &merge_base) < 0) whereas the --fork-point code uses get_fork_point, as you mentioned above. As a result, they don't necessarily refer to the same commit in the case where upstream is rewound. > > The other, which you want to disable, is that --fork-point *also* says > "OK, once we've found the divergence point, let's then rebase it on the > latest upstream. Or in the example above the "master" part of "git > merge-base --fork-point master HEAD". Correct, I guess in essence this is what I'm doing. > > Shouldn't --keep-base just be implemented in terms of skipping *that* > part, i.e. we find the fork point using the upstream info, but then > don't rebase *on* upstream. > > The reason the distinction matters is because with your patch these two > act differently: > > git rebase --keep-base > git rebase $(git merge-base @{u} HEAD) > > The latter will skip work ("Current branch master is up to date"), but > --keep-base will always re-rebase things. There's some cases where > --fork-point does that, which I was trying to address with my linked WIP > patch above. I believe this is desired behaviour. Suppose we have this (modified) graph from the git-merge-base docs, where B3 was formerly part of origin/master but it was then rewound: ---o---o---B2--o---o---o---B (origin/master) \ B3 \ Derived (local master) If we run "git rebase --keep-base", we'll get the following graph: ---o---o---B2--o---o---o---B (origin/master) \ Derived (local master) which I believe is the desired behaviour (we're abandoning B3 since upstream abandoned it). I hope I'm understanding you correctly. Please let me know if I've misinterpreted anything you've said or if anything I've said is unclear. Thanks, Denton > > Whereas the thing you actually want to work is: > > git rebase -i --keep-base > git rebase -i $(git merge-base @{u} HEAD) > > I.e. to have both of those allow you to re-arrange/fixup whatever and > still rebase on the same divergence point with @{u}, and won't run > rebase when there's no work to do unless you give it --force-rebase. > > > reason that --onto already disallows multiple bases. If we have multiple > > bases, how do we determine which one is the "true base" to use? It makes > > more sense to error out and let the user manually specify it. > > Ah, makes sense. > > >> I'd find something like the "DISCUSSION ON FORK-POINT MODE" in > >> git-merge-base helpful with examples of what we'd pick in the various > >> scenarios, and also if whatever commit this picks was something you > >> could have "git merge-base" spew out, so you could get what rebase would > >> do here from other tooling (which maybe is possible, but I'm confused by > >> the "no multiple bases"...). > > > > If I'm understanding you correctly then yes, this could be done with > > other tooling. See the 0/3 for equivalent commands. > > > > Perhaps I should update the rebase documentation to mention that > > --fork-point and --keep-base are orthogonal because it's unclear for > > you, it's probably unclear for other users as well. > > > > Thanks, > > > > Denton ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH 0/3] rebase: learn --keep-base 2019-03-26 21:30 ` Denton Liu @ 2019-03-27 15:39 ` Ævar Arnfjörð Bjarmason 0 siblings, 0 replies; 123+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2019-03-27 15:39 UTC (permalink / raw) To: Denton Liu; +Cc: Git Mailing List, Jeff King On Tue, Mar 26 2019, Denton Liu wrote: > Hi Ævar, > > On Tue, Mar 26, 2019 at 09:35:48PM +0100, Ævar Arnfjörð Bjarmason wrote: >> >> On Tue, Mar 26 2019, Denton Liu wrote: >> >> > Hi Ævar, >> > >> > On Tue, Mar 26, 2019 at 03:35:34PM +0100, Ævar Arnfjörð Bjarmason wrote: >> >> >> >> On Sat, Mar 23 2019, Denton Liu wrote: >> >> >> >> > This series teaches rebase the --keep-base option. >> >> > >> >> > 'git rebase --keep-base <upstream>' is equivalent to >> >> > 'git rebase --onto <upstream>... <upstream>' or >> >> > 'git rebase --onto $(git merge-base <upstream> HEAD) <upstream>' . >> >> > >> >> > This seems to be a common case that people (including myself!) run into; I was >> >> > able to find these StackOverflow posts about this use case: >> >> > >> >> > * https://stackoverflow.com/questions/53234798/can-i-rebase-on-a-branchs-fork-point-without-explicitly-specifying-the-parent >> >> > * https://stackoverflow.com/questions/41529128/how-do-you-rebase-only-changes-between-two-branches-into-another-branch >> >> > * https://stackoverflow.com/a/4207357 >> >> >> >> Like with another series of yours I think this would be best squashed >> >> into one patch. >> > >> > Will do. >> > >> >> >> >> Maybe I've misunderstood this but isn't this like --fork-point except >> >> with just plain "git merge-base" instead of "git merge-base >> >> --fork-point", but then again 2/3 shows multiple base aren't supported, >> >> but merge-base supports that. >> >> >> > >> > --fork-point gets used to determine the _set of_ commits which are to be >> > rebased, whereas --keep-base (and --onto) determine the base where that >> > set of commits will be spliced. As a result, these two options cover >> > orthogonal use-cases. >> >> Right. After playing with this a bit more though --fork-point is mostly >> there, it it does find the same fork point, as evidenced all your tests >> (that aren't asserting incompatibility with other options) passing with >> this: >> >> diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh >> index 9c2548423b..ab2d50e69a 100755 >> --- a/t/t3416-rebase-onto-threedots.sh >> +++ b/t/t3416-rebase-onto-threedots.sh >> @@ -116,7 +116,7 @@ test_expect_success 'rebase --keep-base master from topic' ' >> git checkout topic && >> git reset --hard G && >> >> - git rebase --keep-base master && >> + git rebase $(git merge-base --fork-point master HEAD) && >> git rev-parse C >base.expect && >> git merge-base master HEAD >base.actual && >> test_cmp base.expect base.actual && >> @@ -140,7 +140,7 @@ test_expect_success 'rebase -i --keep-base master from topic' ' >> git reset --hard G && >> >> set_fake_editor && >> - EXPECT_COUNT=2 git rebase -i --keep-base master && >> + EXPECT_COUNT=2 git rebase -i $(git merge-base --fork-point master HEAD) && >> git rev-parse C >base.expect && >> git merge-base master HEAD >base.actual && >> test_cmp base.expect base.actual && >> >> I've poked at some of this recently in >> https://public-inbox.org/git/20190221214059.9195-3-avarab@gmail.com/ as >> noted in the feedback there (I haven't gotten around to v2 yet) it's >> entirely possible that I haven't understood this at all :) >> >> But it seems to me that this patch/implementation conflates two >> unrelated things. >> >> Once is that we use --fork-point to mean that we're going to find the >> divergence point with "merge-base --fork-point". This gets you halfway >> to where you want to be, i.e. AFAICT the --keep-base and --fork-point >> will always find the same commit for "git rebase" and "git rebase >> --keep-base". See the "options.restrict_revision = get_fork_point(...)" >> part of the code. > > I don't think this is true. The code that --keep-base uses to find the > merge base is get_oid_mb, see the relevant snippet > > if (strstr(options.onto_name, "...")) { > if (get_oid_mb(options.onto_name, &merge_base) < 0) > > whereas the --fork-point code uses get_fork_point, as you mentioned > above. As a result, they don't necessarily refer to the same commit in > the case where upstream is rewound. > >> >> The other, which you want to disable, is that --fork-point *also* says >> "OK, once we've found the divergence point, let's then rebase it on the >> latest upstream. Or in the example above the "master" part of "git >> merge-base --fork-point master HEAD". > > Correct, I guess in essence this is what I'm doing. > >> >> Shouldn't --keep-base just be implemented in terms of skipping *that* >> part, i.e. we find the fork point using the upstream info, but then >> don't rebase *on* upstream. >> >> The reason the distinction matters is because with your patch these two >> act differently: >> >> git rebase --keep-base >> git rebase $(git merge-base @{u} HEAD) >> >> The latter will skip work ("Current branch master is up to date"), but >> --keep-base will always re-rebase things. There's some cases where >> --fork-point does that, which I was trying to address with my linked WIP >> patch above. > > I believe this is desired behaviour. Suppose we have this (modified) > graph from the git-merge-base docs, where B3 was formerly part of > origin/master but it was then rewound: > > ---o---o---B2--o---o---o---B (origin/master) > \ > B3 > \ > Derived (local master) > > If we run "git rebase --keep-base", we'll get the following graph: > > ---o---o---B2--o---o---o---B (origin/master) > \ > Derived (local master) > > which I believe is the desired behaviour (we're abandoning B3 since > upstream abandoned it). > > I hope I'm understanding you correctly. Please let me know if I've > misinterpreted anything you've said or if anything I've said is unclear. Yeah. I'm still confused, but mainly because I haven't allocated enough brainpower to try to understand it :) So yeah, I can believe it's subtly different, would be great to have a v2 whose docs/tests cover those subtleties, right now (as seen in my discussion upthread) the tests that are there can identically use the fork point. I also wonder if we can holistically think about this UI / how we can expose different things. E.g. for the times I've needed this and have manually dug up the fork point I haven't wanted to handle the case of upstream rewinding, just re-rebase-i on some old base, while still having upstream tracking info, and for rebase to exit early if there's nothing to do (similar to if I feed it the fork point as a rev). >> >> Whereas the thing you actually want to work is: >> >> git rebase -i --keep-base >> git rebase -i $(git merge-base @{u} HEAD) >> >> I.e. to have both of those allow you to re-arrange/fixup whatever and >> still rebase on the same divergence point with @{u}, and won't run >> rebase when there's no work to do unless you give it --force-rebase. >> >> > reason that --onto already disallows multiple bases. If we have multiple >> > bases, how do we determine which one is the "true base" to use? It makes >> > more sense to error out and let the user manually specify it. >> >> Ah, makes sense. >> >> >> I'd find something like the "DISCUSSION ON FORK-POINT MODE" in >> >> git-merge-base helpful with examples of what we'd pick in the various >> >> scenarios, and also if whatever commit this picks was something you >> >> could have "git merge-base" spew out, so you could get what rebase would >> >> do here from other tooling (which maybe is possible, but I'm confused by >> >> the "no multiple bases"...). >> > >> > If I'm understanding you correctly then yes, this could be done with >> > other tooling. See the 0/3 for equivalent commands. >> > >> > Perhaps I should update the rebase documentation to mention that >> > --fork-point and --keep-base are orthogonal because it's unclear for >> > you, it's probably unclear for other users as well. >> > >> > Thanks, >> > >> > Denton ^ permalink raw reply [flat|nested] 123+ messages in thread
* [PATCH v2] rebase: teach rebase --keep-base 2019-03-23 15:25 [PATCH 0/3] rebase: learn --keep-base Denton Liu ` (4 preceding siblings ...) 2019-03-26 14:35 ` Ævar Arnfjörð Bjarmason @ 2019-03-28 22:17 ` Denton Liu 2019-03-28 23:13 ` Ævar Arnfjörð Bjarmason ` (2 more replies) 5 siblings, 3 replies; 123+ messages in thread From: Denton Liu @ 2019-03-28 22:17 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Johannes Schindelin, Ævar Arnfjörð Bjarmason A common scenario is if a user is working on a topic branch and they wish to make some changes to intermediate commits or autosquash, they would run something such as git rebase -i --onto master... master in order to preserve the merge base. This prevents unnecessary commit churning. Alternatively, a user wishing to test individual commits in a topic branch without changing anything may run git rebase -x ./test.sh master... master Since rebasing onto the merge base of the branch and the upstream is such a common case, introduce the --keep-base option as a shortcut. This allows us to rewrite the above as git rebase -i --keep-base master and git rebase -x ./test.sh --keep-base master respectively. Add tests to ensure --keep-base works correctly in the normal case and fails when there are multiple merge bases, both in regular and interactive mode. Also, test to make sure conflicting options cause rebase to fail. While we're adding test cases, add a missing set_fake_editor call to 'rebase -i --onto master...side'. While we're documenting the --keep-base option, change an instance of "merge-base" to "merge base", which is the consistent spelling. Helped-by: Eric Sunshine <sunshine@sunshineco.com> Helped-by: Junio C Hamano <gitster@pobox.com> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- Ævar, I have a feeling that we're still miscommunicating and we don't fully understand each other. I'm putting up v2 to hopefully clear things up a little but I welcome more feedback. This patch now depends "[PATCH 1/8] tests (rebase): spell out the `--keep-empty` option" which is the first patch of Johannes's "Do not use abbreviated options in tests" patchset[1]. (Thanks for catching that, Johannes!) Changes since v1: * Squashed old set into one patch * Fixed indentation style and dangling else * Added more documentation after discussion with Ævar [1]: https://public-inbox.org/git/a1b4b74b9167e279dae4cd8c58fb28d8a714a66a.1553537656.git.gitgitgadget@gmail.com/ Documentation/git-rebase.txt | 25 ++++++++++++-- builtin/rebase.c | 32 ++++++++++++++---- t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 9 deletions(-) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 6363d674b7..27be1f48ff 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip SYNOPSIS -------- [verse] -'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] - [<upstream> [<branch>]] +'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] + [--onto <newbase> | --keep-base] [<upstream> [<branch>]] 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] --root [<branch>] 'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch @@ -217,6 +217,19 @@ As a special case, you may use "A\...B" as a shortcut for the merge base of A and B if there is exactly one merge base. You can leave out at most one of A and B, in which case it defaults to HEAD. +--keep-base:: + Set the starting point at which to create the new commits to the + merge base of <upstream> <branch>. Running + 'git rebase --keep-base <upstream> <branch>' is equivalent to + running 'git rebase --onto <upstream>... <upstream>'. ++ +Although both this option and --fork-point find the merge base between +<upstream> and <branch>, this option uses the merge base as the _starting +point_ on which new commits will be created, whereas --fork-point uses +the merge base to determine the _set of commits_ which will be rebased. ++ +See also INCOMPATIBLE OPTIONS below. + <upstream>:: Upstream branch to compare against. May be any valid commit, not just an existing branch name. Defaults to the configured @@ -364,6 +377,10 @@ ends up being empty, the <upstream> will be used as a fallback. + If either <upstream> or --root is given on the command line, then the default is `--no-fork-point`, otherwise the default is `--fork-point`. ++ +If your branch was based on <upstream> but <upstream> was rewound and +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:: --whitespace=<option>:: @@ -539,6 +556,8 @@ In addition, the following pairs of options are incompatible: * --preserve-merges and --rebase-merges * --rebase-merges and --strategy * --rebase-merges and --strategy-option + * --keep-base and --onto + * --keep-base and --root BEHAVIORAL DIFFERENCES ----------------------- @@ -863,7 +882,7 @@ NOTE: While an "easy case recovery" sometimes appears to be successful --interactive` will be **resurrected**! The idea is to manually tell 'git rebase' "where the old 'subsystem' -ended and your 'topic' began", that is, what the old merge-base +ended and your 'topic' began", that is, what the old merge base between them was. You will have to find a way to name the last commit of the old 'subsystem', for example: diff --git a/builtin/rebase.c b/builtin/rebase.c index 77deebc65c..7c14a00460 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -27,8 +27,8 @@ #include "branch.h" static char const * const builtin_rebase_usage[] = { - N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " - "[<upstream>] [<branch>]"), + N_("git rebase [-i] [options] [--exec <cmd>] " + "[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"), N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " "--root [<branch>]"), N_("git rebase --continue | --abort | --skip | --edit-todo"), @@ -1018,6 +1018,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) }; const char *branch_name; int ret, flags, total_argc, in_progress = 0; + int keep_base = 0; int ok_to_skip_pre_rebase = 0; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; @@ -1051,6 +1052,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_STRING(0, "onto", &options.onto_name, N_("revision"), N_("rebase onto given branch instead of upstream")), + OPT_BOOL(0, "keep-base", &keep_base, + N_("use the merge-base of upstream and branch as the current base")), OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase, N_("allow pre-rebase hook to run")), OPT_NEGBIT('q', "quiet", &options.flags, @@ -1217,6 +1220,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) usage_with_options(builtin_rebase_usage, builtin_rebase_options); + if (keep_base) { + if (options.onto_name) + die(_("cannot combine '--keep-base' with '--onto'")); + if (options.root) + die(_("cannot combine '--keep-base' with '--root'")); + } + if (action != NO_ACTION && !in_progress) die(_("No rebase in progress?")); setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0); @@ -1541,12 +1551,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } /* Make sure the branch to rebase onto is valid. */ - if (!options.onto_name) + if (keep_base) { + strbuf_reset(&buf); + strbuf_addstr(&buf, options.upstream_name); + strbuf_addstr(&buf, "..."); + options.onto_name = xstrdup(buf.buf); + } else if (!options.onto_name) options.onto_name = options.upstream_name; if (strstr(options.onto_name, "...")) { - if (get_oid_mb(options.onto_name, &merge_base) < 0) - die(_("'%s': need exactly one merge base"), - options.onto_name); + if (get_oid_mb(options.onto_name, &merge_base) < 0) { + if (keep_base) + die(_("'%s': need exactly one merge base with branch"), + options.upstream_name); + else + die(_("'%s': need exactly one merge base"), + options.onto_name); + } options.onto = lookup_commit_or_die(&merge_base, options.onto_name); } else { diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh index ddf2f64853..9c2548423b 100755 --- a/t/t3416-rebase-onto-threedots.sh +++ b/t/t3416-rebase-onto-threedots.sh @@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' ' git checkout side && git reset --hard K && + set_fake_editor && test_must_fail git rebase -i --onto master...side J ' +test_expect_success 'rebase --keep-base --onto incompatible' ' + test_must_fail git rebase --keep-base --onto master... +' + +test_expect_success 'rebase --keep-base --root incompatible' ' + test_must_fail git rebase --keep-base --root +' + +test_expect_success 'rebase --keep-base master from topic' ' + git reset --hard && + git checkout topic && + git reset --hard G && + + git rebase --keep-base master && + git rev-parse C >base.expect && + git merge-base master HEAD >base.actual && + test_cmp base.expect base.actual && + + git rev-parse HEAD~2 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase --keep-base master from side' ' + git reset --hard && + git checkout side && + git reset --hard K && + + test_must_fail git rebase --keep-base master +' + +test_expect_success 'rebase -i --keep-base master from topic' ' + git reset --hard && + git checkout topic && + git reset --hard G && + + set_fake_editor && + EXPECT_COUNT=2 git rebase -i --keep-base master && + git rev-parse C >base.expect && + git merge-base master HEAD >base.actual && + test_cmp base.expect base.actual && + + git rev-parse HEAD~2 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase -i --keep-base master from side' ' + git reset --hard && + git checkout side && + git reset --hard K && + + set_fake_editor && + test_must_fail git rebase -i --keep-base master +' + test_done -- 2.21.0.695.gaf8658f249 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* Re: [PATCH v2] rebase: teach rebase --keep-base 2019-03-28 22:17 ` [PATCH v2] rebase: teach rebase --keep-base Denton Liu @ 2019-03-28 23:13 ` Ævar Arnfjörð Bjarmason 2019-03-29 15:47 ` Johannes Schindelin 2019-04-01 20:51 ` [PATCH v3 0/4] " Denton Liu 2 siblings, 0 replies; 123+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2019-03-28 23:13 UTC (permalink / raw) To: Denton Liu Cc: Git Mailing List, Eric Sunshine, Junio C Hamano, Johannes Schindelin, Jeff King On Thu, Mar 28 2019, Denton Liu wrote: > A common scenario is if a user is working on a topic branch and they > wish to make some changes to intermediate commits or autosquash, they > would run something such as > > git rebase -i --onto master... master > > in order to preserve the merge base. This prevents unnecessary commit > churning. > > Alternatively, a user wishing to test individual commits in a topic > branch without changing anything may run > > git rebase -x ./test.sh master... master > > Since rebasing onto the merge base of the branch and the upstream is > such a common case, introduce the --keep-base option as a shortcut. > > This allows us to rewrite the above as > > git rebase -i --keep-base master > > and > > git rebase -x ./test.sh --keep-base master > > respectively. > > Add tests to ensure --keep-base works correctly in the normal case and > fails when there are multiple merge bases, both in regular and > interactive mode. Also, test to make sure conflicting options cause > rebase to fail. While we're adding test cases, add a missing > set_fake_editor call to 'rebase -i --onto master...side'. > > While we're documenting the --keep-base option, change an instance of > "merge-base" to "merge base", which is the consistent spelling. > > Helped-by: Eric Sunshine <sunshine@sunshineco.com> > Helped-by: Junio C Hamano <gitster@pobox.com> > Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> > Signed-off-by: Denton Liu <liu.denton@gmail.com> > --- > > Ævar, I have a feeling that we're still miscommunicating and we don't > fully understand each other. I'm putting up v2 to hopefully clear things > up a little but I welcome more feedback. > > This patch now depends "[PATCH 1/8] tests (rebase): spell out the > `--keep-empty` option" which is the first patch of Johannes's "Do not > use abbreviated options in tests" patchset[1]. (Thanks for catching > that, Johannes!) Yeah I'm still confused. Gotta go now, but just some early poking I did. First, there's now docs saying "starting point" v.s. "fork point", but the tests are still the same, i.e. the ones I can just replace with either of `git merge-base [--fork-point] @{u} master`. It would really help if the tests actually stressed the parts where this is different, including argument-less versions. I.e. just "git rebase --keep-base". Speaking of that, even though you say this is different than "--fork-point" you may or may not have noticed that if "argc < 1" you *still* go through the whole fork_point codepath, which will set "options.restrict_revision" for you. This is part of what I mentioned upthread. I.e. with this on top of this patch all your tests still pass: diff --git a/builtin/rebase.c b/builtin/rebase.c index 3347dd8975..e38a5044eb 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1515,7 +1515,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) NULL); if (!options.upstream_name) error_on_missing_default_upstream(); - if (fork_point < 0) + if (fork_point < 0 && !keep_base) fork_point = 1; } else { options.upstream_name = argv[0]; @@ -1524,9 +1524,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (!strcmp(options.upstream_name, "-")) options.upstream_name = "@{-1}"; } - options.upstream = peel_committish(options.upstream_name); - if (!options.upstream) - die(_("invalid upstream '%s'"), options.upstream_name); + if (!keep_base) { + options.upstream = peel_committish(options.upstream_name); + if (!options.upstream) + /* I suppose we need to keep this die(...) somewhere still... */ + die(_("invalid upstream '%s'"), options.upstream_name); + } options.upstream_arg = options.upstream_name; } else { if (!options.onto_name) { @@ -1564,6 +1566,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } options.onto = lookup_commit_or_die(&merge_base, options.onto_name); + if (keep_base) + options.upstream = options.onto; } else { options.onto = peel_committish(options.onto_name); if (!options.onto) But now because (and bear with me, I don't really get all this) we are not implicitly setting options.restrict_revision later in the "fork_point > 0" case *and* our "options.upstream" SHA-1 is the base you find with --keep-base instead of the @{u} SHA-1 the can_fast_forward(...) "if" kicks in, so now: $ git status On branch master Your branch and 'origin/master' have diverged, and have 3 and 37 different commits each, respectively. # Here your version would always re-rebase it $ ~/g/git/git --exec-path=$PWD rebase --keep-base Current branch master is up to date. But "-i" still works as intended: $ ~/g/git/git --exec-path=$PWD rebase --keep-base -i hint: Waiting for your editor to close the file... Waiting for Emacs... Successfully rebased and updated refs/heads/master. $ git status On branch master Your branch and 'origin/master' have diverged, and have 3 and 37 different commits each, respectively. I don't know about the *other* use-cases you have in mind, but I can't see a reason for why *that* simple case shouldn't work like that. Why would we redundantly rebase every time in this case, just becase some mode of --onto does it? I think if anything we should teach it the same lazyness, or maybe that breaks stuff (what stuff?). Peff discussed some of these variables & their interaction in https://public-inbox.org/git/20190224101029.GA13438@sigill.intra.peff.net/ I just re-read that, but this whole thing has paged out of my brain in the meantime, and on my quick reading before sending this E-Mail I'm still not sure what all the subtleties involved are. But one thing's for sure, I think exhaustive testing of all the edge cases involved will make this a lot clearer. I.e.: * Here upstream has diverged, we rebase (and in the "noop case?) * Here upstream has diverged, *and* rewound (and in the "noop case?) * etc. etc. > Changes since v1: > > * Squashed old set into one patch > * Fixed indentation style and dangling else > * Added more documentation after discussion with Ævar > > [1]: https://public-inbox.org/git/a1b4b74b9167e279dae4cd8c58fb28d8a714a66a.1553537656.git.gitgitgadget@gmail.com/ > > Documentation/git-rebase.txt | 25 ++++++++++++-- > builtin/rebase.c | 32 ++++++++++++++---- > t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++++++++ > 3 files changed, 105 insertions(+), 9 deletions(-) > > diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt > index 6363d674b7..27be1f48ff 100644 > --- a/Documentation/git-rebase.txt > +++ b/Documentation/git-rebase.txt > @@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip > SYNOPSIS > -------- > [verse] > -'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] > - [<upstream> [<branch>]] > +'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] > + [--onto <newbase> | --keep-base] [<upstream> [<branch>]] > 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] > --root [<branch>] > 'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch > @@ -217,6 +217,19 @@ As a special case, you may use "A\...B" as a shortcut for the > merge base of A and B if there is exactly one merge base. You can > leave out at most one of A and B, in which case it defaults to HEAD. > > +--keep-base:: > + Set the starting point at which to create the new commits to the > + merge base of <upstream> <branch>. Running > + 'git rebase --keep-base <upstream> <branch>' is equivalent to > + running 'git rebase --onto <upstream>... <upstream>'. > ++ > +Although both this option and --fork-point find the merge base between > +<upstream> and <branch>, this option uses the merge base as the _starting > +point_ on which new commits will be created, whereas --fork-point uses > +the merge base to determine the _set of commits_ which will be rebased. > ++ > +See also INCOMPATIBLE OPTIONS below. > + > <upstream>:: > Upstream branch to compare against. May be any valid commit, > not just an existing branch name. Defaults to the configured > @@ -364,6 +377,10 @@ ends up being empty, the <upstream> will be used as a fallback. > + > If either <upstream> or --root is given on the command line, then the > default is `--no-fork-point`, otherwise the default is `--fork-point`. > ++ > +If your branch was based on <upstream> but <upstream> was rewound and > +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:: > --whitespace=<option>:: > @@ -539,6 +556,8 @@ In addition, the following pairs of options are incompatible: > * --preserve-merges and --rebase-merges > * --rebase-merges and --strategy > * --rebase-merges and --strategy-option > + * --keep-base and --onto > + * --keep-base and --root > > BEHAVIORAL DIFFERENCES > ----------------------- > @@ -863,7 +882,7 @@ NOTE: While an "easy case recovery" sometimes appears to be successful > --interactive` will be **resurrected**! > > The idea is to manually tell 'git rebase' "where the old 'subsystem' > -ended and your 'topic' began", that is, what the old merge-base > +ended and your 'topic' began", that is, what the old merge base > between them was. You will have to find a way to name the last commit > of the old 'subsystem', for example: > > diff --git a/builtin/rebase.c b/builtin/rebase.c > index 77deebc65c..7c14a00460 100644 > --- a/builtin/rebase.c > +++ b/builtin/rebase.c > @@ -27,8 +27,8 @@ > #include "branch.h" > > static char const * const builtin_rebase_usage[] = { > - N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " > - "[<upstream>] [<branch>]"), > + N_("git rebase [-i] [options] [--exec <cmd>] " > + "[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"), > N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " > "--root [<branch>]"), > N_("git rebase --continue | --abort | --skip | --edit-todo"), > @@ -1018,6 +1018,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > }; > const char *branch_name; > int ret, flags, total_argc, in_progress = 0; > + int keep_base = 0; > int ok_to_skip_pre_rebase = 0; > struct strbuf msg = STRBUF_INIT; > struct strbuf revisions = STRBUF_INIT; > @@ -1051,6 +1052,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > OPT_STRING(0, "onto", &options.onto_name, > N_("revision"), > N_("rebase onto given branch instead of upstream")), > + OPT_BOOL(0, "keep-base", &keep_base, > + N_("use the merge-base of upstream and branch as the current base")), > OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase, > N_("allow pre-rebase hook to run")), > OPT_NEGBIT('q', "quiet", &options.flags, > @@ -1217,6 +1220,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > usage_with_options(builtin_rebase_usage, > builtin_rebase_options); > > + if (keep_base) { > + if (options.onto_name) > + die(_("cannot combine '--keep-base' with '--onto'")); > + if (options.root) > + die(_("cannot combine '--keep-base' with '--root'")); > + } > + > if (action != NO_ACTION && !in_progress) > die(_("No rebase in progress?")); > setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0); > @@ -1541,12 +1551,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > } > > /* Make sure the branch to rebase onto is valid. */ > - if (!options.onto_name) > + if (keep_base) { > + strbuf_reset(&buf); > + strbuf_addstr(&buf, options.upstream_name); > + strbuf_addstr(&buf, "..."); > + options.onto_name = xstrdup(buf.buf); > + } else if (!options.onto_name) > options.onto_name = options.upstream_name; > if (strstr(options.onto_name, "...")) { > - if (get_oid_mb(options.onto_name, &merge_base) < 0) > - die(_("'%s': need exactly one merge base"), > - options.onto_name); > + if (get_oid_mb(options.onto_name, &merge_base) < 0) { > + if (keep_base) > + die(_("'%s': need exactly one merge base with branch"), > + options.upstream_name); > + else > + die(_("'%s': need exactly one merge base"), > + options.onto_name); > + } > options.onto = lookup_commit_or_die(&merge_base, > options.onto_name); > } else { > diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh > index ddf2f64853..9c2548423b 100755 > --- a/t/t3416-rebase-onto-threedots.sh > +++ b/t/t3416-rebase-onto-threedots.sh > @@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' ' > git checkout side && > git reset --hard K && > > + set_fake_editor && > test_must_fail git rebase -i --onto master...side J > ' > > +test_expect_success 'rebase --keep-base --onto incompatible' ' > + test_must_fail git rebase --keep-base --onto master... > +' > + > +test_expect_success 'rebase --keep-base --root incompatible' ' > + test_must_fail git rebase --keep-base --root > +' > + > +test_expect_success 'rebase --keep-base master from topic' ' > + git reset --hard && > + git checkout topic && > + git reset --hard G && > + > + git rebase --keep-base master && > + git rev-parse C >base.expect && > + git merge-base master HEAD >base.actual && > + test_cmp base.expect base.actual && > + > + git rev-parse HEAD~2 >actual && > + git rev-parse C^0 >expect && > + test_cmp expect actual > +' > + > +test_expect_success 'rebase --keep-base master from side' ' > + git reset --hard && > + git checkout side && > + git reset --hard K && > + > + test_must_fail git rebase --keep-base master > +' > + > +test_expect_success 'rebase -i --keep-base master from topic' ' > + git reset --hard && > + git checkout topic && > + git reset --hard G && > + > + set_fake_editor && > + EXPECT_COUNT=2 git rebase -i --keep-base master && > + git rev-parse C >base.expect && > + git merge-base master HEAD >base.actual && > + test_cmp base.expect base.actual && > + > + git rev-parse HEAD~2 >actual && > + git rev-parse C^0 >expect && > + test_cmp expect actual > +' > + > +test_expect_success 'rebase -i --keep-base master from side' ' > + git reset --hard && > + git checkout side && > + git reset --hard K && > + > + set_fake_editor && > + test_must_fail git rebase -i --keep-base master > +' > + > test_done ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH v2] rebase: teach rebase --keep-base 2019-03-28 22:17 ` [PATCH v2] rebase: teach rebase --keep-base Denton Liu 2019-03-28 23:13 ` Ævar Arnfjörð Bjarmason @ 2019-03-29 15:47 ` Johannes Schindelin 2019-03-29 17:52 ` Denton Liu 2019-04-01 10:46 ` Junio C Hamano 2019-04-01 20:51 ` [PATCH v3 0/4] " Denton Liu 2 siblings, 2 replies; 123+ messages in thread From: Johannes Schindelin @ 2019-03-29 15:47 UTC (permalink / raw) To: Denton Liu Cc: Git Mailing List, Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason [-- Attachment #1: Type: text/plain, Size: 11674 bytes --] Hi Denton, On Thu, 28 Mar 2019, Denton Liu wrote: > A common scenario is if a user is working on a topic branch and they > wish to make some changes to intermediate commits or autosquash, they > would run something such as > > git rebase -i --onto master... master > > in order to preserve the merge base. This prevents unnecessary commit > churning. Maybe an example would clarify what you try to do here? Something like: Example: When contributing a patch series to the Git mailing list, one often starts on top of the current `master`. However, while developing the patch series, `master` is also developed further, and it is sometimes not the best idea to keep rebasing on top of `master`, but to keep the base commit as-is. In such a case, the user can call git rebase -i --onto master... master as a shortcut to using the merge base between `master` and the current branch as base commit. I wonder, however, whether it makes sense to introduce a shorter, sweeter way to do this: git rebase -i master... I.e. if we detect that the `<upstream>` argument is not a valid ref, that it ends with three dots, and that stripping those dots off makes it a valid ref, then we internally convert that to the same as `--onto master... master`. What do you think? Ciao, Dscho > Alternatively, a user wishing to test individual commits in a topic > branch without changing anything may run > > git rebase -x ./test.sh master... master > > Since rebasing onto the merge base of the branch and the upstream is > such a common case, introduce the --keep-base option as a shortcut. > > This allows us to rewrite the above as > > git rebase -i --keep-base master > > and > > git rebase -x ./test.sh --keep-base master > > respectively. > > Add tests to ensure --keep-base works correctly in the normal case and > fails when there are multiple merge bases, both in regular and > interactive mode. Also, test to make sure conflicting options cause > rebase to fail. While we're adding test cases, add a missing > set_fake_editor call to 'rebase -i --onto master...side'. > > While we're documenting the --keep-base option, change an instance of > "merge-base" to "merge base", which is the consistent spelling. > > Helped-by: Eric Sunshine <sunshine@sunshineco.com> > Helped-by: Junio C Hamano <gitster@pobox.com> > Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> > Signed-off-by: Denton Liu <liu.denton@gmail.com> > --- > > Ævar, I have a feeling that we're still miscommunicating and we don't > fully understand each other. I'm putting up v2 to hopefully clear things > up a little but I welcome more feedback. > > This patch now depends "[PATCH 1/8] tests (rebase): spell out the > `--keep-empty` option" which is the first patch of Johannes's "Do not > use abbreviated options in tests" patchset[1]. (Thanks for catching > that, Johannes!) > > Changes since v1: > > * Squashed old set into one patch > * Fixed indentation style and dangling else > * Added more documentation after discussion with Ævar > > [1]: https://public-inbox.org/git/a1b4b74b9167e279dae4cd8c58fb28d8a714a66a.1553537656.git.gitgitgadget@gmail.com/ > > Documentation/git-rebase.txt | 25 ++++++++++++-- > builtin/rebase.c | 32 ++++++++++++++---- > t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++++++++ > 3 files changed, 105 insertions(+), 9 deletions(-) > > diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt > index 6363d674b7..27be1f48ff 100644 > --- a/Documentation/git-rebase.txt > +++ b/Documentation/git-rebase.txt > @@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip > SYNOPSIS > -------- > [verse] > -'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] > - [<upstream> [<branch>]] > +'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] > + [--onto <newbase> | --keep-base] [<upstream> [<branch>]] > 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] > --root [<branch>] > 'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch > @@ -217,6 +217,19 @@ As a special case, you may use "A\...B" as a shortcut for the > merge base of A and B if there is exactly one merge base. You can > leave out at most one of A and B, in which case it defaults to HEAD. > > +--keep-base:: > + Set the starting point at which to create the new commits to the > + merge base of <upstream> <branch>. Running > + 'git rebase --keep-base <upstream> <branch>' is equivalent to > + running 'git rebase --onto <upstream>... <upstream>'. > ++ > +Although both this option and --fork-point find the merge base between > +<upstream> and <branch>, this option uses the merge base as the _starting > +point_ on which new commits will be created, whereas --fork-point uses > +the merge base to determine the _set of commits_ which will be rebased. > ++ > +See also INCOMPATIBLE OPTIONS below. > + > <upstream>:: > Upstream branch to compare against. May be any valid commit, > not just an existing branch name. Defaults to the configured > @@ -364,6 +377,10 @@ ends up being empty, the <upstream> will be used as a fallback. > + > If either <upstream> or --root is given on the command line, then the > default is `--no-fork-point`, otherwise the default is `--fork-point`. > ++ > +If your branch was based on <upstream> but <upstream> was rewound and > +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:: > --whitespace=<option>:: > @@ -539,6 +556,8 @@ In addition, the following pairs of options are incompatible: > * --preserve-merges and --rebase-merges > * --rebase-merges and --strategy > * --rebase-merges and --strategy-option > + * --keep-base and --onto > + * --keep-base and --root > > BEHAVIORAL DIFFERENCES > ----------------------- > @@ -863,7 +882,7 @@ NOTE: While an "easy case recovery" sometimes appears to be successful > --interactive` will be **resurrected**! > > The idea is to manually tell 'git rebase' "where the old 'subsystem' > -ended and your 'topic' began", that is, what the old merge-base > +ended and your 'topic' began", that is, what the old merge base > between them was. You will have to find a way to name the last commit > of the old 'subsystem', for example: > > diff --git a/builtin/rebase.c b/builtin/rebase.c > index 77deebc65c..7c14a00460 100644 > --- a/builtin/rebase.c > +++ b/builtin/rebase.c > @@ -27,8 +27,8 @@ > #include "branch.h" > > static char const * const builtin_rebase_usage[] = { > - N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " > - "[<upstream>] [<branch>]"), > + N_("git rebase [-i] [options] [--exec <cmd>] " > + "[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"), > N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " > "--root [<branch>]"), > N_("git rebase --continue | --abort | --skip | --edit-todo"), > @@ -1018,6 +1018,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > }; > const char *branch_name; > int ret, flags, total_argc, in_progress = 0; > + int keep_base = 0; > int ok_to_skip_pre_rebase = 0; > struct strbuf msg = STRBUF_INIT; > struct strbuf revisions = STRBUF_INIT; > @@ -1051,6 +1052,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > OPT_STRING(0, "onto", &options.onto_name, > N_("revision"), > N_("rebase onto given branch instead of upstream")), > + OPT_BOOL(0, "keep-base", &keep_base, > + N_("use the merge-base of upstream and branch as the current base")), > OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase, > N_("allow pre-rebase hook to run")), > OPT_NEGBIT('q', "quiet", &options.flags, > @@ -1217,6 +1220,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > usage_with_options(builtin_rebase_usage, > builtin_rebase_options); > > + if (keep_base) { > + if (options.onto_name) > + die(_("cannot combine '--keep-base' with '--onto'")); > + if (options.root) > + die(_("cannot combine '--keep-base' with '--root'")); > + } > + > if (action != NO_ACTION && !in_progress) > die(_("No rebase in progress?")); > setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0); > @@ -1541,12 +1551,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > } > > /* Make sure the branch to rebase onto is valid. */ > - if (!options.onto_name) > + if (keep_base) { > + strbuf_reset(&buf); > + strbuf_addstr(&buf, options.upstream_name); > + strbuf_addstr(&buf, "..."); > + options.onto_name = xstrdup(buf.buf); > + } else if (!options.onto_name) > options.onto_name = options.upstream_name; > if (strstr(options.onto_name, "...")) { > - if (get_oid_mb(options.onto_name, &merge_base) < 0) > - die(_("'%s': need exactly one merge base"), > - options.onto_name); > + if (get_oid_mb(options.onto_name, &merge_base) < 0) { > + if (keep_base) > + die(_("'%s': need exactly one merge base with branch"), > + options.upstream_name); > + else > + die(_("'%s': need exactly one merge base"), > + options.onto_name); > + } > options.onto = lookup_commit_or_die(&merge_base, > options.onto_name); > } else { > diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh > index ddf2f64853..9c2548423b 100755 > --- a/t/t3416-rebase-onto-threedots.sh > +++ b/t/t3416-rebase-onto-threedots.sh > @@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' ' > git checkout side && > git reset --hard K && > > + set_fake_editor && > test_must_fail git rebase -i --onto master...side J > ' > > +test_expect_success 'rebase --keep-base --onto incompatible' ' > + test_must_fail git rebase --keep-base --onto master... > +' > + > +test_expect_success 'rebase --keep-base --root incompatible' ' > + test_must_fail git rebase --keep-base --root > +' > + > +test_expect_success 'rebase --keep-base master from topic' ' > + git reset --hard && > + git checkout topic && > + git reset --hard G && > + > + git rebase --keep-base master && > + git rev-parse C >base.expect && > + git merge-base master HEAD >base.actual && > + test_cmp base.expect base.actual && > + > + git rev-parse HEAD~2 >actual && > + git rev-parse C^0 >expect && > + test_cmp expect actual > +' > + > +test_expect_success 'rebase --keep-base master from side' ' > + git reset --hard && > + git checkout side && > + git reset --hard K && > + > + test_must_fail git rebase --keep-base master > +' > + > +test_expect_success 'rebase -i --keep-base master from topic' ' > + git reset --hard && > + git checkout topic && > + git reset --hard G && > + > + set_fake_editor && > + EXPECT_COUNT=2 git rebase -i --keep-base master && > + git rev-parse C >base.expect && > + git merge-base master HEAD >base.actual && > + test_cmp base.expect base.actual && > + > + git rev-parse HEAD~2 >actual && > + git rev-parse C^0 >expect && > + test_cmp expect actual > +' > + > +test_expect_success 'rebase -i --keep-base master from side' ' > + git reset --hard && > + git checkout side && > + git reset --hard K && > + > + set_fake_editor && > + test_must_fail git rebase -i --keep-base master > +' > + > test_done > -- > 2.21.0.695.gaf8658f249 > > ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH v2] rebase: teach rebase --keep-base 2019-03-29 15:47 ` Johannes Schindelin @ 2019-03-29 17:52 ` Denton Liu 2019-04-01 10:46 ` Junio C Hamano 1 sibling, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-03-29 17:52 UTC (permalink / raw) To: Johannes Schindelin Cc: Git Mailing List, Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason Hi Johannes, On Fri, Mar 29, 2019 at 04:47:42PM +0100, Johannes Schindelin wrote: > Hi Denton, > > On Thu, 28 Mar 2019, Denton Liu wrote: > > > A common scenario is if a user is working on a topic branch and they > > wish to make some changes to intermediate commits or autosquash, they > > would run something such as > > > > git rebase -i --onto master... master > > > > in order to preserve the merge base. This prevents unnecessary commit > > churning. > > Maybe an example would clarify what you try to do here? Something like: > > Example: When contributing a patch series to the Git mailing list, > one often starts on top of the current `master`. However, while > developing the patch series, `master` is also developed further, > and it is sometimes not the best idea to keep rebasing on top > of `master`, but to keep the base commit as-is. In such a case, > the user can call > > git rebase -i --onto master... master > > as a shortcut to using the merge base between `master` and the > current branch as base commit. Will do, I'll include an example in the next reroll (probably in the docs too). > > I wonder, however, whether it makes sense to introduce a shorter, sweeter > way to do this: > > git rebase -i master... > > I.e. if we detect that the `<upstream>` argument is not a valid ref, that > it ends with three dots, and that stripping those dots off makes it a > valid ref, then we internally convert that to the same as `--onto > master... master`. There's one use-case that this syntax wouldn't cover. Currently, if <upstream> isn't specified, rebase automatically uses 'git merge-base --fork-point @{u} HEAD' as <upstream>. This is even true for an invocation of 'git rebase --keep-base'. As a result, suppose we have the following graph o - f - o - U' origin/master \ U - A master where U used to be in origin/master (when we forked off) but upstream was rewound to drop it. If we run 'git rebase --keep-base', we'll get the following graph: o - f - o - U' origin/master \ A master i.e. we'll drop the commit dropped by upstream. I'm not entirely sure how useful this is, though, so if we decide to drop support for this use-case, then your proposed syntax will be fine. Thanks, Denton > > What do you think? > > Ciao, > Dscho > > > Alternatively, a user wishing to test individual commits in a topic > > branch without changing anything may run > > > > git rebase -x ./test.sh master... master > > > > Since rebasing onto the merge base of the branch and the upstream is > > such a common case, introduce the --keep-base option as a shortcut. > > > > This allows us to rewrite the above as > > > > git rebase -i --keep-base master > > > > and > > > > git rebase -x ./test.sh --keep-base master > > > > respectively. > > > > Add tests to ensure --keep-base works correctly in the normal case and > > fails when there are multiple merge bases, both in regular and > > interactive mode. Also, test to make sure conflicting options cause > > rebase to fail. While we're adding test cases, add a missing > > set_fake_editor call to 'rebase -i --onto master...side'. > > > > While we're documenting the --keep-base option, change an instance of > > "merge-base" to "merge base", which is the consistent spelling. > > > > Helped-by: Eric Sunshine <sunshine@sunshineco.com> > > Helped-by: Junio C Hamano <gitster@pobox.com> > > Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> > > Signed-off-by: Denton Liu <liu.denton@gmail.com> > > --- > > > > Ævar, I have a feeling that we're still miscommunicating and we don't > > fully understand each other. I'm putting up v2 to hopefully clear things > > up a little but I welcome more feedback. > > > > This patch now depends "[PATCH 1/8] tests (rebase): spell out the > > `--keep-empty` option" which is the first patch of Johannes's "Do not > > use abbreviated options in tests" patchset[1]. (Thanks for catching > > that, Johannes!) > > > > Changes since v1: > > > > * Squashed old set into one patch > > * Fixed indentation style and dangling else > > * Added more documentation after discussion with Ævar > > > > [1]: https://public-inbox.org/git/a1b4b74b9167e279dae4cd8c58fb28d8a714a66a.1553537656.git.gitgitgadget@gmail.com/ > > > > Documentation/git-rebase.txt | 25 ++++++++++++-- > > builtin/rebase.c | 32 ++++++++++++++---- > > t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++++++++ > > 3 files changed, 105 insertions(+), 9 deletions(-) > > > > diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt > > index 6363d674b7..27be1f48ff 100644 > > --- a/Documentation/git-rebase.txt > > +++ b/Documentation/git-rebase.txt > > @@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip > > SYNOPSIS > > -------- > > [verse] > > -'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] > > - [<upstream> [<branch>]] > > +'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] > > + [--onto <newbase> | --keep-base] [<upstream> [<branch>]] > > 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] > > --root [<branch>] > > 'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch > > @@ -217,6 +217,19 @@ As a special case, you may use "A\...B" as a shortcut for the > > merge base of A and B if there is exactly one merge base. You can > > leave out at most one of A and B, in which case it defaults to HEAD. > > > > +--keep-base:: > > + Set the starting point at which to create the new commits to the > > + merge base of <upstream> <branch>. Running > > + 'git rebase --keep-base <upstream> <branch>' is equivalent to > > + running 'git rebase --onto <upstream>... <upstream>'. > > ++ > > +Although both this option and --fork-point find the merge base between > > +<upstream> and <branch>, this option uses the merge base as the _starting > > +point_ on which new commits will be created, whereas --fork-point uses > > +the merge base to determine the _set of commits_ which will be rebased. > > ++ > > +See also INCOMPATIBLE OPTIONS below. > > + > > <upstream>:: > > Upstream branch to compare against. May be any valid commit, > > not just an existing branch name. Defaults to the configured > > @@ -364,6 +377,10 @@ ends up being empty, the <upstream> will be used as a fallback. > > + > > If either <upstream> or --root is given on the command line, then the > > default is `--no-fork-point`, otherwise the default is `--fork-point`. > > ++ > > +If your branch was based on <upstream> but <upstream> was rewound and > > +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:: > > --whitespace=<option>:: > > @@ -539,6 +556,8 @@ In addition, the following pairs of options are incompatible: > > * --preserve-merges and --rebase-merges > > * --rebase-merges and --strategy > > * --rebase-merges and --strategy-option > > + * --keep-base and --onto > > + * --keep-base and --root > > > > BEHAVIORAL DIFFERENCES > > ----------------------- > > @@ -863,7 +882,7 @@ NOTE: While an "easy case recovery" sometimes appears to be successful > > --interactive` will be **resurrected**! > > > > The idea is to manually tell 'git rebase' "where the old 'subsystem' > > -ended and your 'topic' began", that is, what the old merge-base > > +ended and your 'topic' began", that is, what the old merge base > > between them was. You will have to find a way to name the last commit > > of the old 'subsystem', for example: > > > > diff --git a/builtin/rebase.c b/builtin/rebase.c > > index 77deebc65c..7c14a00460 100644 > > --- a/builtin/rebase.c > > +++ b/builtin/rebase.c > > @@ -27,8 +27,8 @@ > > #include "branch.h" > > > > static char const * const builtin_rebase_usage[] = { > > - N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " > > - "[<upstream>] [<branch>]"), > > + N_("git rebase [-i] [options] [--exec <cmd>] " > > + "[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"), > > N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " > > "--root [<branch>]"), > > N_("git rebase --continue | --abort | --skip | --edit-todo"), > > @@ -1018,6 +1018,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > > }; > > const char *branch_name; > > int ret, flags, total_argc, in_progress = 0; > > + int keep_base = 0; > > int ok_to_skip_pre_rebase = 0; > > struct strbuf msg = STRBUF_INIT; > > struct strbuf revisions = STRBUF_INIT; > > @@ -1051,6 +1052,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > > OPT_STRING(0, "onto", &options.onto_name, > > N_("revision"), > > N_("rebase onto given branch instead of upstream")), > > + OPT_BOOL(0, "keep-base", &keep_base, > > + N_("use the merge-base of upstream and branch as the current base")), > > OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase, > > N_("allow pre-rebase hook to run")), > > OPT_NEGBIT('q', "quiet", &options.flags, > > @@ -1217,6 +1220,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > > usage_with_options(builtin_rebase_usage, > > builtin_rebase_options); > > > > + if (keep_base) { > > + if (options.onto_name) > > + die(_("cannot combine '--keep-base' with '--onto'")); > > + if (options.root) > > + die(_("cannot combine '--keep-base' with '--root'")); > > + } > > + > > if (action != NO_ACTION && !in_progress) > > die(_("No rebase in progress?")); > > setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0); > > @@ -1541,12 +1551,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > > } > > > > /* Make sure the branch to rebase onto is valid. */ > > - if (!options.onto_name) > > + if (keep_base) { > > + strbuf_reset(&buf); > > + strbuf_addstr(&buf, options.upstream_name); > > + strbuf_addstr(&buf, "..."); > > + options.onto_name = xstrdup(buf.buf); > > + } else if (!options.onto_name) > > options.onto_name = options.upstream_name; > > if (strstr(options.onto_name, "...")) { > > - if (get_oid_mb(options.onto_name, &merge_base) < 0) > > - die(_("'%s': need exactly one merge base"), > > - options.onto_name); > > + if (get_oid_mb(options.onto_name, &merge_base) < 0) { > > + if (keep_base) > > + die(_("'%s': need exactly one merge base with branch"), > > + options.upstream_name); > > + else > > + die(_("'%s': need exactly one merge base"), > > + options.onto_name); > > + } > > options.onto = lookup_commit_or_die(&merge_base, > > options.onto_name); > > } else { > > diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh > > index ddf2f64853..9c2548423b 100755 > > --- a/t/t3416-rebase-onto-threedots.sh > > +++ b/t/t3416-rebase-onto-threedots.sh > > @@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' ' > > git checkout side && > > git reset --hard K && > > > > + set_fake_editor && > > test_must_fail git rebase -i --onto master...side J > > ' > > > > +test_expect_success 'rebase --keep-base --onto incompatible' ' > > + test_must_fail git rebase --keep-base --onto master... > > +' > > + > > +test_expect_success 'rebase --keep-base --root incompatible' ' > > + test_must_fail git rebase --keep-base --root > > +' > > + > > +test_expect_success 'rebase --keep-base master from topic' ' > > + git reset --hard && > > + git checkout topic && > > + git reset --hard G && > > + > > + git rebase --keep-base master && > > + git rev-parse C >base.expect && > > + git merge-base master HEAD >base.actual && > > + test_cmp base.expect base.actual && > > + > > + git rev-parse HEAD~2 >actual && > > + git rev-parse C^0 >expect && > > + test_cmp expect actual > > +' > > + > > +test_expect_success 'rebase --keep-base master from side' ' > > + git reset --hard && > > + git checkout side && > > + git reset --hard K && > > + > > + test_must_fail git rebase --keep-base master > > +' > > + > > +test_expect_success 'rebase -i --keep-base master from topic' ' > > + git reset --hard && > > + git checkout topic && > > + git reset --hard G && > > + > > + set_fake_editor && > > + EXPECT_COUNT=2 git rebase -i --keep-base master && > > + git rev-parse C >base.expect && > > + git merge-base master HEAD >base.actual && > > + test_cmp base.expect base.actual && > > + > > + git rev-parse HEAD~2 >actual && > > + git rev-parse C^0 >expect && > > + test_cmp expect actual > > +' > > + > > +test_expect_success 'rebase -i --keep-base master from side' ' > > + git reset --hard && > > + git checkout side && > > + git reset --hard K && > > + > > + set_fake_editor && > > + test_must_fail git rebase -i --keep-base master > > +' > > + > > test_done > > -- > > 2.21.0.695.gaf8658f249 > > > > ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH v2] rebase: teach rebase --keep-base 2019-03-29 15:47 ` Johannes Schindelin 2019-03-29 17:52 ` Denton Liu @ 2019-04-01 10:46 ` Junio C Hamano 1 sibling, 0 replies; 123+ messages in thread From: Junio C Hamano @ 2019-04-01 10:46 UTC (permalink / raw) To: Johannes Schindelin Cc: Denton Liu, Git Mailing List, Eric Sunshine, Ævar Arnfjörð Bjarmason Johannes Schindelin <Johannes.Schindelin@gmx.de> writes: > Hi Denton, > > On Thu, 28 Mar 2019, Denton Liu wrote: > >> A common scenario is if a user is working on a topic branch and they >> wish to make some changes to intermediate commits or autosquash, they >> would run something such as >> >> git rebase -i --onto master... master > ... > I wonder, however, whether it makes sense to introduce a shorter, sweeter > way to do this: > > git rebase -i master... I agree that this is very tempting, as it mimicks "git checkout master...". But as Denton responds, it is not quite the same, so... ^ permalink raw reply [flat|nested] 123+ messages in thread
* [PATCH v3 0/4] rebase: teach rebase --keep-base 2019-03-28 22:17 ` [PATCH v2] rebase: teach rebase --keep-base Denton Liu 2019-03-28 23:13 ` Ævar Arnfjörð Bjarmason 2019-03-29 15:47 ` Johannes Schindelin @ 2019-04-01 20:51 ` Denton Liu 2019-04-01 20:51 ` [PATCH v3 1/4] t3431: add rebase --fork-point tests Denton Liu ` (5 more replies) 2 siblings, 6 replies; 123+ messages in thread From: Denton Liu @ 2019-04-01 20:51 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin Thanks again for your feedback, Ævar! I think we're both on the same page now. Hopefully I've addressed all of your high-level concerns with this patchset and we can move into a discussion on implementation detail. This patchset now depends "[PATCH 1/8] tests (rebase): spell out the `--keep-empty` option" which is the first patch of Johannes's "Do not use abbreviated options in tests" patchset[1]. (Thanks for catching that, Johannes!) Changes since v1: * Squashed old set into one patch * Fixed indentation style and dangling else * Added more documentation after discussion with Ævar Changes since v2: * Add testing for rebase --fork-point behaviour * Add testing for rebase fast-forward behaviour * Make rebase --onto fast-forward in more cases * Update documentation to include use-case [1]: https://public-inbox.org/git/a1b4b74b9167e279dae4cd8c58fb28d8a714a66a.1553537656.git.gitgitgadget@gmail.com/ Denton Liu (4): t3431: add rebase --fork-point tests t3432: test rebase fast-forward behavior rebase: fast-forward --onto in more cases rebase: teach rebase --keep-base Documentation/git-rebase.txt | 30 +++++++++++-- builtin/rebase.c | 72 +++++++++++++++++++++++--------- t/t3400-rebase.sh | 2 +- t/t3404-rebase-interactive.sh | 2 +- t/t3416-rebase-onto-threedots.sh | 57 +++++++++++++++++++++++++ t/t3431-rebase-fork-point.sh | 57 +++++++++++++++++++++++++ t/t3432-rebase-fast-forward.sh | 62 +++++++++++++++++++++++++++ 7 files changed, 258 insertions(+), 24 deletions(-) create mode 100755 t/t3431-rebase-fork-point.sh create mode 100755 t/t3432-rebase-fast-forward.sh -- 2.21.0.695.gaf8658f249 ^ permalink raw reply [flat|nested] 123+ messages in thread
* [PATCH v3 1/4] t3431: add rebase --fork-point tests 2019-04-01 20:51 ` [PATCH v3 0/4] " Denton Liu @ 2019-04-01 20:51 ` Denton Liu 2019-04-04 20:28 ` Denton Liu ` (2 more replies) 2019-04-01 20:51 ` [PATCH v3 2/4] t3432: test rebase fast-forward behavior Denton Liu ` (4 subsequent siblings) 5 siblings, 3 replies; 123+ messages in thread From: Denton Liu @ 2019-04-01 20:51 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin Signed-off-by: Denton Liu <liu.denton@gmail.com> --- t/t3431-rebase-fork-point.sh | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100755 t/t3431-rebase-fork-point.sh diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh new file mode 100755 index 0000000000..8e2483b73e --- /dev/null +++ b/t/t3431-rebase-fork-point.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# +# Copyright (c) 2019 Denton Liu +# + +test_description='git rebase --fork-point test' + +. ./test-lib.sh + +# A---B---D---E (master) +# \ +# C*---F---G (side) +# +# C was formerly part of master but is side out +# +test_expect_success setup ' + test_commit A && + test_commit B && + test_commit C && + git branch -t side && + git reset --hard HEAD^ && + test_commit D && + test_commit E && + git checkout side && + test_commit F && + test_commit G +' + +test_rebase() { + expected="$1" && + shift && + test_expect_success "git rebase $@" " + git checkout master && + git reset --hard E && + git checkout side && + git reset --hard G && + git rebase $@ && + test_write_lines $expected >expect && + git log --pretty=%s >actual && + test_cmp expect actual + " +} + +test_rebase 'G F E D B A' '' +test_rebase 'G F D B A' '--onto D' +test_rebase 'G F C E D B A' '--no-fork-point' +test_rebase 'G F C D B A' '--no-fork-point --onto D' +test_rebase 'G F E D B A' '--fork-point refs/heads/master' +test_rebase 'G F D B A' '--fork-point --onto D refs/heads/master' +test_rebase 'G F C E D B A' 'refs/heads/master' +test_rebase 'G F C D B A' '--onto D refs/heads/master' + +test_done -- 2.21.0.695.gaf8658f249 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* Re: [PATCH v3 1/4] t3431: add rebase --fork-point tests 2019-04-01 20:51 ` [PATCH v3 1/4] t3431: add rebase --fork-point tests Denton Liu @ 2019-04-04 20:28 ` Denton Liu 2019-04-05 11:15 ` SZEDER Gábor 2019-04-05 14:55 ` Johannes Schindelin 2 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-04 20:28 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin On Mon, Apr 01, 2019 at 01:51:57PM -0700, Denton Liu wrote: > Signed-off-by: Denton Liu <liu.denton@gmail.com> > --- > t/t3431-rebase-fork-point.sh | 53 ++++++++++++++++++++++++++++++++++++ > 1 file changed, 53 insertions(+) > create mode 100755 t/t3431-rebase-fork-point.sh > > diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh > new file mode 100755 > index 0000000000..8e2483b73e > --- /dev/null > +++ b/t/t3431-rebase-fork-point.sh > @@ -0,0 +1,53 @@ > +#!/bin/sh > +# > +# Copyright (c) 2019 Denton Liu > +# > + > +test_description='git rebase --fork-point test' > + > +. ./test-lib.sh > + > +# A---B---D---E (master) > +# \ > +# C*---F---G (side) > +# > +# C was formerly part of master but is side out Sorry, small typo. We should probably fix this before it gets merged into next. This should read "C was formerly part of master but master was rewound to remove C" Thanks, Denton > +# > +test_expect_success setup ' > + test_commit A && > + test_commit B && > + test_commit C && > + git branch -t side && > + git reset --hard HEAD^ && > + test_commit D && > + test_commit E && > + git checkout side && > + test_commit F && > + test_commit G > +' > + > +test_rebase() { > + expected="$1" && > + shift && > + test_expect_success "git rebase $@" " > + git checkout master && > + git reset --hard E && > + git checkout side && > + git reset --hard G && > + git rebase $@ && > + test_write_lines $expected >expect && > + git log --pretty=%s >actual && > + test_cmp expect actual > + " > +} > + > +test_rebase 'G F E D B A' '' > +test_rebase 'G F D B A' '--onto D' > +test_rebase 'G F C E D B A' '--no-fork-point' > +test_rebase 'G F C D B A' '--no-fork-point --onto D' > +test_rebase 'G F E D B A' '--fork-point refs/heads/master' > +test_rebase 'G F D B A' '--fork-point --onto D refs/heads/master' > +test_rebase 'G F C E D B A' 'refs/heads/master' > +test_rebase 'G F C D B A' '--onto D refs/heads/master' > + > +test_done > -- > 2.21.0.695.gaf8658f249 > ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH v3 1/4] t3431: add rebase --fork-point tests 2019-04-01 20:51 ` [PATCH v3 1/4] t3431: add rebase --fork-point tests Denton Liu 2019-04-04 20:28 ` Denton Liu @ 2019-04-05 11:15 ` SZEDER Gábor 2019-04-08 4:38 ` Junio C Hamano 2019-04-05 14:55 ` Johannes Schindelin 2 siblings, 1 reply; 123+ messages in thread From: SZEDER Gábor @ 2019-04-05 11:15 UTC (permalink / raw) To: Denton Liu Cc: Git Mailing List, Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin On Mon, Apr 01, 2019 at 01:51:57PM -0700, Denton Liu wrote: > Signed-off-by: Denton Liu <liu.denton@gmail.com> > --- > t/t3431-rebase-fork-point.sh | 53 ++++++++++++++++++++++++++++++++++++ > 1 file changed, 53 insertions(+) > create mode 100755 t/t3431-rebase-fork-point.sh > > diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh > new file mode 100755 > index 0000000000..8e2483b73e > --- /dev/null > +++ b/t/t3431-rebase-fork-point.sh > @@ -0,0 +1,53 @@ > +#!/bin/sh > +# > +# Copyright (c) 2019 Denton Liu > +# > + > +test_description='git rebase --fork-point test' > + > +. ./test-lib.sh > + > +# A---B---D---E (master) > +# \ > +# C*---F---G (side) > +# > +# C was formerly part of master but is side out > +# > +test_expect_success setup ' > + test_commit A && > + test_commit B && > + test_commit C && > + git branch -t side && > + git reset --hard HEAD^ && > + test_commit D && > + test_commit E && > + git checkout side && > + test_commit F && > + test_commit G > +' > + > +test_rebase() { > + expected="$1" && > + shift && > + test_expect_success "git rebase $@" " > + git checkout master && > + git reset --hard E && > + git checkout side && > + git reset --hard G && > + git rebase $@ && > + test_write_lines $expected >expect && > + git log --pretty=%s >actual && > + test_cmp expect actual > + " > +} > + > +test_rebase 'G F E D B A' '' It appears that this last empty argument triggers some bug in Bash v4.2 and older (and on macOS such an old Bash is the default /bin/sh), as it turns that empty argument into something else, which in turn fails the test with: <...> ++ git rebase $'\177' fatal: invalid upstream '?' error: last command exited with $?=128 not ok 2 - git rebase https://travis-ci.org/git/git/jobs/516070862#L2276 Omitting that empty argument avoids this issue, and the test still checks what it was supposed to. > +test_rebase 'G F D B A' '--onto D' > +test_rebase 'G F C E D B A' '--no-fork-point' > +test_rebase 'G F C D B A' '--no-fork-point --onto D' > +test_rebase 'G F E D B A' '--fork-point refs/heads/master' > +test_rebase 'G F D B A' '--fork-point --onto D refs/heads/master' > +test_rebase 'G F C E D B A' 'refs/heads/master' > +test_rebase 'G F C D B A' '--onto D refs/heads/master' > + > +test_done > -- > 2.21.0.695.gaf8658f249 > ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH v3 1/4] t3431: add rebase --fork-point tests 2019-04-05 11:15 ` SZEDER Gábor @ 2019-04-08 4:38 ` Junio C Hamano 0 siblings, 0 replies; 123+ messages in thread From: Junio C Hamano @ 2019-04-08 4:38 UTC (permalink / raw) To: SZEDER Gábor Cc: Denton Liu, Git Mailing List, Eric Sunshine, Ævar Arnfjörð Bjarmason, Johannes Schindelin SZEDER Gábor <szeder.dev@gmail.com> writes: >> + test_expect_success "git rebase $@" " >> +... >> + git rebase $@ && >> +... >> + " >> +} >> + >> +test_rebase 'G F E D B A' '' > > It appears that this last empty argument triggers some bug in Bash > v4.2 and older (and on macOS such an old Bash is the default /bin/sh), > as it turns that empty argument into something else, which in turn > fails the test with: > > <...> > ++ git rebase $'\177' > fatal: invalid upstream '?' > error: last command exited with $?=128 > not ok 2 - git rebase > > https://travis-ci.org/git/git/jobs/516070862#L2276 Yeah, every time I see $@ that appears in any form other than "$@" (i.e. within a pair of double-quotes without anything else in it), it makes me feel very uneasy. Shouldn't the argument to the above "rebase" be spelled $* instead? I somehow do not think use of $@ there is buying us anything. Of course, if we were really passing an arg with $IFS character in it, we could probably eval 'git rebase "$@"' it (with appropriate quoting to adjust for the surrounding quote pair). > Omitting that empty argument avoids this issue, and the test still > checks what it was supposed to. > >> +test_rebase 'G F D B A' '--onto D' >> +test_rebase 'G F C E D B A' '--no-fork-point' >> +test_rebase 'G F C D B A' '--no-fork-point --onto D' >> +test_rebase 'G F E D B A' '--fork-point refs/heads/master' >> +test_rebase 'G F D B A' '--fork-point --onto D refs/heads/master' >> +test_rebase 'G F C E D B A' 'refs/heads/master' >> +test_rebase 'G F C D B A' '--onto D refs/heads/master' >> + >> +test_done >> -- >> 2.21.0.695.gaf8658f249 >> ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH v3 1/4] t3431: add rebase --fork-point tests 2019-04-01 20:51 ` [PATCH v3 1/4] t3431: add rebase --fork-point tests Denton Liu 2019-04-04 20:28 ` Denton Liu 2019-04-05 11:15 ` SZEDER Gábor @ 2019-04-05 14:55 ` Johannes Schindelin 2019-04-05 17:25 ` Denton Liu 2 siblings, 1 reply; 123+ messages in thread From: Johannes Schindelin @ 2019-04-05 14:55 UTC (permalink / raw) To: Denton Liu Cc: Git Mailing List, Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason Hi Denton, On Mon, 1 Apr 2019, Denton Liu wrote: > diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh > new file mode 100755 > index 0000000000..8e2483b73e > --- /dev/null > +++ b/t/t3431-rebase-fork-point.sh > @@ -0,0 +1,53 @@ > +#!/bin/sh > +# > +# Copyright (c) 2019 Denton Liu > +# > + > +test_description='git rebase --fork-point test' > + > +. ./test-lib.sh > + > +# A---B---D---E (master) > +# \ > +# C*---F---G (side) > +# > +# C was formerly part of master but is side out > +# > +test_expect_success setup ' > + test_commit A && > + test_commit B && > + test_commit C && > + git branch -t side && > + git reset --hard HEAD^ && > + test_commit D && > + test_commit E && > + git checkout side && > + test_commit F && > + test_commit G > +' > + > +test_rebase() { > + expected="$1" && > + shift && > + test_expect_success "git rebase $@" " > + git checkout master && > + git reset --hard E && > + git checkout side && > + git reset --hard G && > + git rebase $@ && I think we need this patch, to make the macOS build happy: -- snip -- Subject: fixup??? t3431: add rebase --fork-point tests Try to fix the Mac build, which currently fails thusly: ++ git reset --hard G HEAD is now at d8775ba G ++ git rebase $'\177' fatal: invalid upstream '?' error: last command exited with $?=128 Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh index 4607e65de6..b41a0c0b68 100755 --- a/t/t3431-rebase-fork-point.sh +++ b/t/t3431-rebase-fork-point.sh @@ -34,7 +34,7 @@ test_rebase() { git reset --hard E && git checkout side && git reset --hard G && - git rebase $@ && + eval git rebase \"$@\" && test_write_lines $expected >expect && git log --pretty=%s >actual && test_cmp expect actual -- snap -- Ciao, Dscho > + test_write_lines $expected >expect && > + git log --pretty=%s >actual && > + test_cmp expect actual > + " > +} > + > +test_rebase 'G F E D B A' '' > +test_rebase 'G F D B A' '--onto D' > +test_rebase 'G F C E D B A' '--no-fork-point' > +test_rebase 'G F C D B A' '--no-fork-point --onto D' > +test_rebase 'G F E D B A' '--fork-point refs/heads/master' > +test_rebase 'G F D B A' '--fork-point --onto D refs/heads/master' > +test_rebase 'G F C E D B A' 'refs/heads/master' > +test_rebase 'G F C D B A' '--onto D refs/heads/master' > + > +test_done > -- > 2.21.0.695.gaf8658f249 > > ^ permalink raw reply related [flat|nested] 123+ messages in thread
* Re: [PATCH v3 1/4] t3431: add rebase --fork-point tests 2019-04-05 14:55 ` Johannes Schindelin @ 2019-04-05 17:25 ` Denton Liu 2019-04-05 17:51 ` Johannes Sixt 0 siblings, 1 reply; 123+ messages in thread From: Denton Liu @ 2019-04-05 17:25 UTC (permalink / raw) To: Johannes Schindelin Cc: Git Mailing List, Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason On Fri, Apr 05, 2019 at 04:55:37PM +0200, Johannes Schindelin wrote: > Hi Denton, > > On Mon, 1 Apr 2019, Denton Liu wrote: > > > diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh > > new file mode 100755 > > index 0000000000..8e2483b73e > > --- /dev/null > > +++ b/t/t3431-rebase-fork-point.sh > > @@ -0,0 +1,53 @@ > > +#!/bin/sh > > +# > > +# Copyright (c) 2019 Denton Liu > > +# > > + > > +test_description='git rebase --fork-point test' > > + > > +. ./test-lib.sh > > + > > +# A---B---D---E (master) > > +# \ > > +# C*---F---G (side) > > +# > > +# C was formerly part of master but is side out > > +# > > +test_expect_success setup ' > > + test_commit A && > > + test_commit B && > > + test_commit C && > > + git branch -t side && > > + git reset --hard HEAD^ && > > + test_commit D && > > + test_commit E && > > + git checkout side && > > + test_commit F && > > + test_commit G > > +' > > + > > +test_rebase() { > > + expected="$1" && > > + shift && > > + test_expect_success "git rebase $@" " > > + git checkout master && > > + git reset --hard E && > > + git checkout side && > > + git reset --hard G && > > + git rebase $@ && > > I think we need this patch, to make the macOS build happy: Thanks for digging into this, both. Out of curiosity, t3432 is written similarly: test_rebase_same_head() { status="$1" && shift && test_expect_$status "git rebase $@ with $changes is no-op" " oldhead=\$(git rev-parse HEAD) && test_when_finished 'git reset --hard \$oldhead' && git rebase $@ && newhead=\$(git rev-parse HEAD) && test_cmp_rev \$oldhead \$newhead " } and is also invoked similarly test_rebase_same_head success '' but it doesn't seem to fail. Any ideas on why? Thanks, Denton > > -- snip -- > Subject: fixup??? t3431: add rebase --fork-point tests > > Try to fix the Mac build, which currently fails thusly: > > ++ git reset --hard G > HEAD is now at d8775ba G > ++ git rebase $'\177' > fatal: invalid upstream '?' > error: last command exited with $?=128 > > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> > > diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh > index 4607e65de6..b41a0c0b68 100755 > --- a/t/t3431-rebase-fork-point.sh > +++ b/t/t3431-rebase-fork-point.sh > @@ -34,7 +34,7 @@ test_rebase() { > git reset --hard E && > git checkout side && > git reset --hard G && > - git rebase $@ && > + eval git rebase \"$@\" && > test_write_lines $expected >expect && > git log --pretty=%s >actual && > test_cmp expect actual > -- snap -- > > Ciao, > Dscho > > > + test_write_lines $expected >expect && > > + git log --pretty=%s >actual && > > + test_cmp expect actual > > + " > > +} > > + > > +test_rebase 'G F E D B A' '' > > +test_rebase 'G F D B A' '--onto D' > > +test_rebase 'G F C E D B A' '--no-fork-point' > > +test_rebase 'G F C D B A' '--no-fork-point --onto D' > > +test_rebase 'G F E D B A' '--fork-point refs/heads/master' > > +test_rebase 'G F D B A' '--fork-point --onto D refs/heads/master' > > +test_rebase 'G F C E D B A' 'refs/heads/master' > > +test_rebase 'G F C D B A' '--onto D refs/heads/master' > > + > > +test_done > > -- > > 2.21.0.695.gaf8658f249 > > > > ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH v3 1/4] t3431: add rebase --fork-point tests 2019-04-05 17:25 ` Denton Liu @ 2019-04-05 17:51 ` Johannes Sixt 2019-04-05 18:51 ` Johannes Schindelin 0 siblings, 1 reply; 123+ messages in thread From: Johannes Sixt @ 2019-04-05 17:51 UTC (permalink / raw) To: Denton Liu Cc: Johannes Schindelin, Git Mailing List, Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason Am 05.04.19 um 19:25 schrieb Denton Liu: > On Fri, Apr 05, 2019 at 04:55:37PM +0200, Johannes Schindelin wrote: >> On Mon, 1 Apr 2019, Denton Liu wrote: >>> +test_rebase() { >>> + expected="$1" && >>> + shift && >>> + test_expect_success "git rebase $@" " >>> + git checkout master && >>> + git reset --hard E && >>> + git checkout side && >>> + git reset --hard G && >>> + git rebase $@ && >> >> I think we need this patch, to make the macOS build happy: > > Thanks for digging into this, both. > > Out of curiosity, t3432 is written similarly: > > test_rebase_same_head() { > status="$1" && > shift && > test_expect_$status "git rebase $@ with $changes is no-op" " > oldhead=\$(git rev-parse HEAD) && > test_when_finished 'git reset --hard \$oldhead' && > git rebase $@ && > newhead=\$(git rev-parse HEAD) && > test_cmp_rev \$oldhead \$newhead > " > } Using $@ in these expansions is wrong. You do not want to forward an argument list, but you want to construct a command line. $* is correct here. Then you can remove the single-quotes at the invocation, like so: test_rebase_same_head success test_rebase_same_head success --onto B B Function test_rebase() could be done in the same way, but the first argument, expected, still needs quotes at the call site, of course. -- Hannes ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH v3 1/4] t3431: add rebase --fork-point tests 2019-04-05 17:51 ` Johannes Sixt @ 2019-04-05 18:51 ` Johannes Schindelin 2019-04-05 20:19 ` Johannes Schindelin 0 siblings, 1 reply; 123+ messages in thread From: Johannes Schindelin @ 2019-04-05 18:51 UTC (permalink / raw) To: Johannes Sixt Cc: Denton Liu, Git Mailing List, Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason Hi Hannes & Denton, On Fri, 5 Apr 2019, Johannes Sixt wrote: > Am 05.04.19 um 19:25 schrieb Denton Liu: > > On Fri, Apr 05, 2019 at 04:55:37PM +0200, Johannes Schindelin wrote: > >> On Mon, 1 Apr 2019, Denton Liu wrote: > >>> +test_rebase() { > >>> + expected="$1" && > >>> + shift && > >>> + test_expect_success "git rebase $@" " > >>> + git checkout master && > >>> + git reset --hard E && > >>> + git checkout side && > >>> + git reset --hard G && > >>> + git rebase $@ && > >> > >> I think we need this patch, to make the macOS build happy: Actually, my patch did not even fix the build, I looked at the wrong (succeeding) build, sorry for the noise. > > Thanks for digging into this, both. > > > > Out of curiosity, t3432 is written similarly: > > > > test_rebase_same_head() { > > status="$1" && > > shift && > > test_expect_$status "git rebase $@ with $changes is no-op" " > > oldhead=\$(git rev-parse HEAD) && > > test_when_finished 'git reset --hard \$oldhead' && > > git rebase $@ && > > newhead=\$(git rev-parse HEAD) && > > test_cmp_rev \$oldhead \$newhead > > " > > } That is curious, indeed! > Using $@ in these expansions is wrong. You do not want to forward an > argument list, but you want to construct a command line. $* is correct > here. Then you can remove the single-quotes at the invocation, like so: > > test_rebase_same_head success > test_rebase_same_head success --onto B B > > Function test_rebase() could be done in the same way, but the first > argument, expected, still needs quotes at the call site, of course. That's a good idea, let me run with it. Ciao, Dscho ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH v3 1/4] t3431: add rebase --fork-point tests 2019-04-05 18:51 ` Johannes Schindelin @ 2019-04-05 20:19 ` Johannes Schindelin 2019-04-05 21:10 ` SZEDER Gábor 0 siblings, 1 reply; 123+ messages in thread From: Johannes Schindelin @ 2019-04-05 20:19 UTC (permalink / raw) To: Johannes Sixt Cc: Denton Liu, Git Mailing List, Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason Hi, On Fri, 5 Apr 2019, Johannes Schindelin wrote: > On Fri, 5 Apr 2019, Johannes Sixt wrote: > > > Am 05.04.19 um 19:25 schrieb Denton Liu: > > > On Fri, Apr 05, 2019 at 04:55:37PM +0200, Johannes Schindelin wrote: > > >> On Mon, 1 Apr 2019, Denton Liu wrote: > > >>> +test_rebase() { > > >>> + expected="$1" && > > >>> + shift && > > >>> + test_expect_success "git rebase $@" " > > >>> + git checkout master && > > >>> + git reset --hard E && > > >>> + git checkout side && > > >>> + git reset --hard G && > > >>> + git rebase $@ && > > > Using $@ in these expansions is wrong. You do not want to forward an > > argument list, but you want to construct a command line. $* is correct > > here. Then you can remove the single-quotes at the invocation, like so: > > > > test_rebase_same_head success > > test_rebase_same_head success --onto B B > > > > Function test_rebase() could be done in the same way, but the first > > argument, expected, still needs quotes at the call site, of course. > > That's a good idea, let me run with it. Indeed, this patch fixes it (see e.g. https://dev.azure.com/Git-for-Windows/git/_build/results?buildId=34370): -- snipsnap -- Subject: [PATCH] fixup??? t3431: add rebase --fork-point tests Try to fix the Mac build, which currently fails thusly: ++ git reset --hard G HEAD is now at d8775ba G ++ git rebase $'\177' fatal: invalid upstream '?' error: last command exited with $?=128 Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> --- t/t3431-rebase-fork-point.sh | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh index 4607e65de6..daa0c77467 100755 --- a/t/t3431-rebase-fork-point.sh +++ b/t/t3431-rebase-fork-point.sh @@ -29,29 +29,29 @@ test_expect_success setup ' test_rebase() { expected="$1" && shift && - test_expect_success "git rebase $@" " + test_expect_success "git rebase $*" " git checkout master && git reset --hard E && git checkout side && git reset --hard G && - git rebase $@ && + eval git rebase $* && test_write_lines $expected >expect && git log --pretty=%s >actual && test_cmp expect actual " } -test_rebase 'G F E D B A' '' -test_rebase 'G F D B A' '--onto D' -test_rebase 'G F B A' '--keep-base' -test_rebase 'G F C E D B A' '--no-fork-point' -test_rebase 'G F C D B A' '--no-fork-point --onto D' -test_rebase 'G F C B A' '--no-fork-point --keep-base' -test_rebase 'G F E D B A' '--fork-point refs/heads/master' -test_rebase 'G F D B A' '--fork-point --onto D refs/heads/master' -test_rebase 'G F B A' '--fork-point --keep-base refs/heads/master' -test_rebase 'G F C E D B A' 'refs/heads/master' -test_rebase 'G F C D B A' '--onto D refs/heads/master' -test_rebase 'G F C B A' '--keep-base refs/heads/master' +test_rebase 'G F E D B A' +test_rebase 'G F D B A' --onto D +test_rebase 'G F B A' --keep-base +test_rebase 'G F C E D B A' --no-fork-point +test_rebase 'G F C D B A' --no-fork-point --onto D +test_rebase 'G F C B A' --no-fork-point --keep-base +test_rebase 'G F E D B A' --fork-point refs/heads/master +test_rebase 'G F D B A' --fork-point --onto D refs/heads/master +test_rebase 'G F B A' --fork-point --keep-base refs/heads/master +test_rebase 'G F C E D B A' refs/heads/master +test_rebase 'G F C D B A' --onto D refs/heads/master +test_rebase 'G F C B A' --keep-base refs/heads/master test_done -- 2.21.0.windows.1.152.g5895f170b6 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* Re: [PATCH v3 1/4] t3431: add rebase --fork-point tests 2019-04-05 20:19 ` Johannes Schindelin @ 2019-04-05 21:10 ` SZEDER Gábor 0 siblings, 0 replies; 123+ messages in thread From: SZEDER Gábor @ 2019-04-05 21:10 UTC (permalink / raw) To: Johannes Schindelin Cc: Johannes Sixt, Denton Liu, Git Mailing List, Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason On Fri, Apr 05, 2019 at 10:19:59PM +0200, Johannes Schindelin wrote: > Hi, > > On Fri, 5 Apr 2019, Johannes Schindelin wrote: > > > On Fri, 5 Apr 2019, Johannes Sixt wrote: > > > > > Am 05.04.19 um 19:25 schrieb Denton Liu: > > > > On Fri, Apr 05, 2019 at 04:55:37PM +0200, Johannes Schindelin wrote: > > > >> On Mon, 1 Apr 2019, Denton Liu wrote: > > > >>> +test_rebase() { > > > >>> + expected="$1" && > > > >>> + shift && > > > >>> + test_expect_success "git rebase $@" " > > > >>> + git checkout master && > > > >>> + git reset --hard E && > > > >>> + git checkout side && > > > >>> + git reset --hard G && > > > >>> + git rebase $@ && > > > > > Using $@ in these expansions is wrong. You do not want to forward an > > > argument list, but you want to construct a command line. $* is correct > > > here. Then you can remove the single-quotes at the invocation, like so: > > > > > > test_rebase_same_head success > > > test_rebase_same_head success --onto B B > > > > > > Function test_rebase() could be done in the same way, but the first > > > argument, expected, still needs quotes at the call site, of course. > > > > That's a good idea, let me run with it. > > Indeed, this patch fixes it (see e.g. > https://dev.azure.com/Git-for-Windows/git/_build/results?buildId=34370): > > -- snipsnap -- > Subject: [PATCH] fixup??? t3431: add rebase --fork-point tests > > Try to fix the Mac build, which currently fails thusly: > > ++ git reset --hard G > HEAD is now at d8775ba G > ++ git rebase $'\177' > fatal: invalid upstream '?' > error: last command exited with $?=128 > > Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> > --- > t/t3431-rebase-fork-point.sh | 28 ++++++++++++++-------------- > 1 file changed, 14 insertions(+), 14 deletions(-) > > diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh > index 4607e65de6..daa0c77467 100755 > --- a/t/t3431-rebase-fork-point.sh > +++ b/t/t3431-rebase-fork-point.sh > @@ -29,29 +29,29 @@ test_expect_success setup ' > test_rebase() { > expected="$1" && > shift && > - test_expect_success "git rebase $@" " > + test_expect_success "git rebase $*" " > git checkout master && > git reset --hard E && > git checkout side && > git reset --hard G && > - git rebase $@ && > + eval git rebase $* && The 'eval' is not necessary, all Bash versions down to v3.0 work without it. > test_write_lines $expected >expect && > git log --pretty=%s >actual && > test_cmp expect actual > " > } > > -test_rebase 'G F E D B A' '' > -test_rebase 'G F D B A' '--onto D' > -test_rebase 'G F B A' '--keep-base' > -test_rebase 'G F C E D B A' '--no-fork-point' > -test_rebase 'G F C D B A' '--no-fork-point --onto D' > -test_rebase 'G F C B A' '--no-fork-point --keep-base' > -test_rebase 'G F E D B A' '--fork-point refs/heads/master' > -test_rebase 'G F D B A' '--fork-point --onto D refs/heads/master' > -test_rebase 'G F B A' '--fork-point --keep-base refs/heads/master' > -test_rebase 'G F C E D B A' 'refs/heads/master' > -test_rebase 'G F C D B A' '--onto D refs/heads/master' > -test_rebase 'G F C B A' '--keep-base refs/heads/master' > +test_rebase 'G F E D B A' > +test_rebase 'G F D B A' --onto D > +test_rebase 'G F B A' --keep-base > +test_rebase 'G F C E D B A' --no-fork-point > +test_rebase 'G F C D B A' --no-fork-point --onto D > +test_rebase 'G F C B A' --no-fork-point --keep-base > +test_rebase 'G F E D B A' --fork-point refs/heads/master > +test_rebase 'G F D B A' --fork-point --onto D refs/heads/master > +test_rebase 'G F B A' --fork-point --keep-base refs/heads/master > +test_rebase 'G F C E D B A' refs/heads/master > +test_rebase 'G F C D B A' --onto D refs/heads/master > +test_rebase 'G F C B A' --keep-base refs/heads/master > > test_done > -- > 2.21.0.windows.1.152.g5895f170b6 > ^ permalink raw reply [flat|nested] 123+ messages in thread
* [PATCH v3 2/4] t3432: test rebase fast-forward behavior 2019-04-01 20:51 ` [PATCH v3 0/4] " Denton Liu 2019-04-01 20:51 ` [PATCH v3 1/4] t3431: add rebase --fork-point tests Denton Liu @ 2019-04-01 20:51 ` Denton Liu 2019-04-01 20:52 ` [PATCH v3 3/4] rebase: fast-forward --onto in more cases Denton Liu ` (3 subsequent siblings) 5 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-01 20:51 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin When rebase is run on a branch that can be fast-forwarded, this should automatically be done. Create test to ensure this behavior happens. There is one case that currently does not pass. In the case where a feature and master have diverged, running "git rebase master... master" causes a full rebase to happen even though a fast-forward should happen. Mark this case as failure so we can fix it later. Signed-off-by: Denton Liu <liu.denton@gmail.com> --- t/t3432-rebase-fast-forward.sh | 59 ++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100755 t/t3432-rebase-fast-forward.sh diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh new file mode 100755 index 0000000000..3e6362dd9c --- /dev/null +++ b/t/t3432-rebase-fast-forward.sh @@ -0,0 +1,59 @@ +#!/bin/sh +# +# Copyright (c) 2019 Denton Liu +# + +test_description='ensure rebase fast-forwards commits when possible' + +. ./test-lib.sh + +test_expect_success setup ' + test_commit A && + test_commit B && + test_commit C && + test_commit D && + git checkout -t -b side +' + +test_rebase_same_head() { + status="$1" && + shift && + test_expect_$status "git rebase $@ with $changes is no-op" " + oldhead=\$(git rev-parse HEAD) && + test_when_finished 'git reset --hard \$oldhead' && + git rebase $@ && + newhead=\$(git rev-parse HEAD) && + test_cmp_rev \$oldhead \$newhead + " +} + +changes='no changes' +test_rebase_same_head success '' +test_rebase_same_head success 'master' +test_rebase_same_head success '--onto B B' +test_rebase_same_head success '--onto B... B' +test_rebase_same_head success '--onto master... master' + +test_expect_success 'add work to side' ' + test_commit E +' + +changes='our changes' +test_rebase_same_head success '' +test_rebase_same_head success 'master' +test_rebase_same_head success '--onto B B' +test_rebase_same_head success '--onto B... B' +test_rebase_same_head success '--onto master... master' + +test_expect_success 'add work to upstream' ' + git checkout master && + test_commit F && + git checkout side +' + +changes='our and their changes' +test_rebase_same_head success '--onto B B' +test_rebase_same_head success '--onto B... B' +test_rebase_same_head failure '--onto master... master' + +test_done -- 2.21.0.695.gaf8658f249 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v3 3/4] rebase: fast-forward --onto in more cases 2019-04-01 20:51 ` [PATCH v3 0/4] " Denton Liu 2019-04-01 20:51 ` [PATCH v3 1/4] t3431: add rebase --fork-point tests Denton Liu 2019-04-01 20:51 ` [PATCH v3 2/4] t3432: test rebase fast-forward behavior Denton Liu @ 2019-04-01 20:52 ` Denton Liu 2019-04-02 1:25 ` Junio C Hamano 2019-04-01 20:52 ` [PATCH v3 4/4] rebase: teach rebase --keep-base Denton Liu ` (2 subsequent siblings) 5 siblings, 1 reply; 123+ messages in thread From: Denton Liu @ 2019-04-01 20:52 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin Before, when we had the following graph, A---B---C (master) \ D (side) running 'git rebase --onto master... master side' would result in D being always rebased, no matter what. However, the desired behavior is that rebase should notice that this is fast-forwardable and do that instead. Add detection to `can_fast_forward` so that this case can be detected and a fast-forward will be performed. While we're at it, remove a trailing whitespace. Signed-off-by: Denton Liu <liu.denton@gmail.com> --- builtin/rebase.c | 40 +++++++++++++++++++++++----------- t/t3400-rebase.sh | 2 +- t/t3404-rebase-interactive.sh | 2 +- t/t3432-rebase-fast-forward.sh | 2 +- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 77deebc65c..7aa6a090d4 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -895,12 +895,12 @@ static int is_linear_history(struct commit *from, struct commit *to) return 1; } -static int can_fast_forward(struct commit *onto, struct object_id *head_oid, - struct object_id *merge_base) +static int can_fast_forward(struct commit *onto, struct commit *upstream, + struct object_id *head_oid, struct object_id *merge_base) { struct commit *head = lookup_commit(the_repository, head_oid); - struct commit_list *merge_bases; - int res; + struct commit_list *merge_bases = NULL; + int res = 0; if (!head) return 0; @@ -908,12 +908,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, merge_bases = get_merge_bases(onto, head); if (merge_bases && !merge_bases->next) { oidcpy(merge_base, &merge_bases->item->object.oid); - res = oideq(merge_base, &onto->object.oid); + if (!oideq(merge_base, &onto->object.oid)) + goto done; } else { oidcpy(merge_base, &null_oid); - res = 0; + goto done; } + + if (!upstream) + goto done; + free_commit_list(merge_bases); + merge_bases = get_merge_bases(upstream, head); + if (merge_bases && !merge_bases->next) { + if (!oideq(&onto->object.oid, &merge_bases->item->object.oid)) + goto done; + } else + goto done; + + res = 1; + +done: + if (merge_bases) + free_commit_list(merge_bases); return res && is_linear_history(onto, head); } @@ -1682,13 +1699,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) /* * Check if we are already based on onto with linear history, - * but this should be done only when upstream and onto are the same - * and if this is not an interactive rebase. + * but this should be done if this is not an interactive rebase. */ - if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && - !is_interactive(&options) && !options.restrict_revision && - options.upstream && - !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { + if (can_fast_forward(options.onto, options.upstream, &options.orig_head, &merge_base) && + !is_interactive(&options) && !options.restrict_revision) { int flag; if (!(options.flags & REBASE_FORCE)) { @@ -1782,7 +1796,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&msg, "%s: checkout %s", getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name); if (reset_head(&options.onto->object.oid, "checkout", NULL, - RESET_HEAD_DETACH | RESET_ORIG_HEAD | + RESET_HEAD_DETACH | RESET_ORIG_HEAD | RESET_HEAD_RUN_POST_CHECKOUT_HOOK, NULL, msg.buf)) die(_("Could not detach HEAD")); diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 460d0523be..604d624ff8 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' ' echo two >>init.t && git commit -a -m two && git tag two && - test_must_fail git rebase --onto init HEAD^ && + test_must_fail git rebase -f --onto init HEAD^ && GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && grep "show.*$(git rev-parse two)" stderr ) diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index b60b11f9f2..f054186cc7 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int git reset --hard && git checkout conflict-branch && set_fake_editor && - test_must_fail git rebase --onto HEAD~2 HEAD~ && + test_must_fail git rebase -f --onto HEAD~2 HEAD~ && test_must_fail git rebase --edit-todo && git rebase --abort ' diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index 3e6362dd9c..414b4216d6 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -54,6 +54,6 @@ test_expect_success 'add work to upstream' ' changes='our and their changes' test_rebase_same_head success '--onto B B' test_rebase_same_head success '--onto B... B' -test_rebase_same_head failure '--onto master... master' +test_rebase_same_head success '--onto master... master' test_done -- 2.21.0.695.gaf8658f249 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* Re: [PATCH v3 3/4] rebase: fast-forward --onto in more cases 2019-04-01 20:52 ` [PATCH v3 3/4] rebase: fast-forward --onto in more cases Denton Liu @ 2019-04-02 1:25 ` Junio C Hamano 2019-04-02 1:48 ` Junio C Hamano 0 siblings, 1 reply; 123+ messages in thread From: Junio C Hamano @ 2019-04-02 1:25 UTC (permalink / raw) To: Denton Liu Cc: Git Mailing List, Eric Sunshine, Ævar Arnfjörð Bjarmason, Johannes Schindelin Denton Liu <liu.denton@gmail.com> writes: > Before, when we had the following graph, > > A---B---C (master) > \ > D (side) > > running 'git rebase --onto master... master side' would result in D > being always rebased, no matter what. However, the desired behavior is > that rebase should notice that this is fast-forwardable and do that > instead. > > Add detection to `can_fast_forward` so that this case can be detected > and a fast-forward will be performed. OK. As long as the 'onto' commit is a strict ancestor of the side branch being rebased, the 'upstream' that is used only to determine which commits are on the side branch (essentially those that are not reachable from upstream but that are from the side branch) should not count in the equation to decide if we fast-forward or not. That makes sense. > --- > builtin/rebase.c | 40 +++++++++++++++++++++++----------- > t/t3400-rebase.sh | 2 +- > t/t3404-rebase-interactive.sh | 2 +- > t/t3432-rebase-fast-forward.sh | 2 +- > 4 files changed, 30 insertions(+), 16 deletions(-) > > diff --git a/builtin/rebase.c b/builtin/rebase.c > index 77deebc65c..7aa6a090d4 100644 > --- a/builtin/rebase.c > +++ b/builtin/rebase.c > @@ -895,12 +895,12 @@ static int is_linear_history(struct commit *from, struct commit *to) > return 1; > } > > -static int can_fast_forward(struct commit *onto, struct object_id *head_oid, > - struct object_id *merge_base) > +static int can_fast_forward(struct commit *onto, struct commit *upstream, > + struct object_id *head_oid, struct object_id *merge_base) > { > struct commit *head = lookup_commit(the_repository, head_oid); > - struct commit_list *merge_bases; > - int res; > + struct commit_list *merge_bases = NULL; > + int res = 0; > > if (!head) > return 0; > @@ -908,12 +908,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, > merge_bases = get_merge_bases(onto, head); > if (merge_bases && !merge_bases->next) { > oidcpy(merge_base, &merge_bases->item->object.oid); > - res = oideq(merge_base, &onto->object.oid); > + if (!oideq(merge_base, &onto->object.oid)) > + goto done; > } else { > oidcpy(merge_base, &null_oid); > - res = 0; > + goto done; > } The above does not change any existing logic, but purely simplifies the code. In your picture in the log message A---B---C (master) \ D (side) where "rebase --onto master... master side" is run, "onto" in this function is B, and "head" is D. There is a single merge base B (i.e. merge_bases->next == NULL), so we jump to the label "done:" with res==0. So why does the remainder of the function need to be changed? > + if (!upstream) > + goto done; > + > free_commit_list(merge_bases); > + merge_bases = get_merge_bases(upstream, head); > + if (merge_bases && !merge_bases->next) { > + if (!oideq(&onto->object.oid, &merge_bases->item->object.oid)) > + goto done; > + } else > + goto done; This computes the same between C and D (instead of B and D). Why is this needed? Stepping back a bit, I understand that your argument we saw in the log message was that we only need to know if the commit we are transplanting the history on (i.e. "onto") already is an ancestor of the history being transplanted (i.e. "onto..head"), and it does not matter what upstream is. Am I mistaken? Why does this function now need to know what 'upstream' is? Puzzled.... > @@ -1682,13 +1699,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > > /* > * Check if we are already based on onto with linear history, > - * but this should be done only when upstream and onto are the same > - * and if this is not an interactive rebase. > + * but this should be done if this is not an interactive rebase. > */ > - if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && > - !is_interactive(&options) && !options.restrict_revision && > - options.upstream && > - !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { > + if (can_fast_forward(options.onto, options.upstream, &options.orig_head, &merge_base) && > + !is_interactive(&options) && !options.restrict_revision) { Ditto. Shouldn't it be the matter of if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && !is_interactive(&options) && !options.restrict_revision) { i.e. the original without paying any attention to the options.upstream? > diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh > index 460d0523be..604d624ff8 100755 > --- a/t/t3400-rebase.sh > +++ b/t/t3400-rebase.sh > @@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' ' > echo two >>init.t && > git commit -a -m two && > git tag two && > - test_must_fail git rebase --onto init HEAD^ && > + test_must_fail git rebase -f --onto init HEAD^ && > GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && > grep "show.*$(git rev-parse two)" stderr > ) > diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh > index b60b11f9f2..f054186cc7 100755 > --- a/t/t3404-rebase-interactive.sh > +++ b/t/t3404-rebase-interactive.sh > @@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int > git reset --hard && > git checkout conflict-branch && > set_fake_editor && > - test_must_fail git rebase --onto HEAD~2 HEAD~ && > + test_must_fail git rebase -f --onto HEAD~2 HEAD~ && > test_must_fail git rebase --edit-todo && > git rebase --abort > ' The above two changes to 3400 and 3404 are not explained. > diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh > index 3e6362dd9c..414b4216d6 100755 > --- a/t/t3432-rebase-fast-forward.sh > +++ b/t/t3432-rebase-fast-forward.sh > @@ -54,6 +54,6 @@ test_expect_success 'add work to upstream' ' > changes='our and their changes' > test_rebase_same_head success '--onto B B' > test_rebase_same_head success '--onto B... B' > -test_rebase_same_head failure '--onto master... master' > +test_rebase_same_head success '--onto master... master' This shows what the change wanted to achieve, which makes sense to me. ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH v3 3/4] rebase: fast-forward --onto in more cases 2019-04-02 1:25 ` Junio C Hamano @ 2019-04-02 1:48 ` Junio C Hamano 2019-04-02 4:44 ` Denton Liu 0 siblings, 1 reply; 123+ messages in thread From: Junio C Hamano @ 2019-04-02 1:48 UTC (permalink / raw) To: Denton Liu Cc: Git Mailing List, Eric Sunshine, Ævar Arnfjörð Bjarmason, Johannes Schindelin Junio C Hamano <gitster@pobox.com> writes: > Denton Liu <liu.denton@gmail.com> writes: > >> Before, when we had the following graph, >> >> A---B---C (master) >> \ >> D (side) >> >> running 'git rebase --onto master... master side' would result in D >> being always rebased, no matter what. However, the desired behavior is >> that rebase should notice that this is fast-forwardable and do that >> instead. >> >> Add detection to `can_fast_forward` so that this case can be detected >> and a fast-forward will be performed. > > OK. As long as the 'onto' commit is a strict ancestor of the side > branch being rebased, the 'upstream' that is used only to determine > which commits are on the side branch (essentially those that are not > reachable from upstream but that are from the side branch) should > not count in the equation to decide if we fast-forward or not. That > makes sense. > >> --- >> builtin/rebase.c | 40 +++++++++++++++++++++++----------- >> t/t3400-rebase.sh | 2 +- >> t/t3404-rebase-interactive.sh | 2 +- >> t/t3432-rebase-fast-forward.sh | 2 +- >> 4 files changed, 30 insertions(+), 16 deletions(-) >> >> diff --git a/builtin/rebase.c b/builtin/rebase.c >> index 77deebc65c..7aa6a090d4 100644 >> --- a/builtin/rebase.c >> +++ b/builtin/rebase.c >> @@ -895,12 +895,12 @@ static int is_linear_history(struct commit *from, struct commit *to) >> return 1; >> } >> >> -static int can_fast_forward(struct commit *onto, struct object_id *head_oid, >> - struct object_id *merge_base) >> +static int can_fast_forward(struct commit *onto, struct commit *upstream, >> + struct object_id *head_oid, struct object_id *merge_base) >> { >> struct commit *head = lookup_commit(the_repository, head_oid); >> - struct commit_list *merge_bases; >> - int res; >> + struct commit_list *merge_bases = NULL; >> + int res = 0; >> >> if (!head) >> return 0; >> @@ -908,12 +908,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, >> merge_bases = get_merge_bases(onto, head); >> if (merge_bases && !merge_bases->next) { >> oidcpy(merge_base, &merge_bases->item->object.oid); >> - res = oideq(merge_base, &onto->object.oid); >> + if (!oideq(merge_base, &onto->object.oid)) >> + goto done; >> } else { >> oidcpy(merge_base, &null_oid); >> - res = 0; >> + goto done; >> } > > The above does not change any existing logic, but purely simplifies > the code. In your picture in the log message > > A---B---C (master) > \ > D (side) > > where "rebase --onto master... master side" is run, "onto" in this > function is B, and "head" is D. There is a single merge base B > (i.e. merge_bases->next == NULL), so we jump to the label "done:" > with res==0. > > So why does the remainder of the function need to be changed? > >> + if (!upstream) >> + goto done; >> + >> free_commit_list(merge_bases); >> + merge_bases = get_merge_bases(upstream, head); >> + if (merge_bases && !merge_bases->next) { >> + if (!oideq(&onto->object.oid, &merge_bases->item->object.oid)) >> + goto done; >> + } else >> + goto done; > > This computes the same between C and D (instead of B and D). Why is > this needed? > > Stepping back a bit, I understand that your argument we saw in the > log message was that we only need to know if the commit we are > transplanting the history on (i.e. "onto") already is an ancestor of > the history being transplanted (i.e. "onto..head"), and it does not > matter what upstream is. Am I mistaken? Why does this function now > need to know what 'upstream' is? > > Puzzled.... So I replaced the part for builtin/rebase.c from your patch with the above suggestion (attached) and the result seems to pass the three tests you touched. I am still puzzled. Thanks. builtin/rebase.c | 7 ++----- t/t3400-rebase.sh | 2 +- t/t3404-rebase-interactive.sh | 2 +- t/t3432-rebase-fast-forward.sh | 2 +- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 77deebc65c..fe61c2a899 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1682,13 +1682,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) /* * Check if we are already based on onto with linear history, - * but this should be done only when upstream and onto are the same - * and if this is not an interactive rebase. + * but this should be done if this is not an interactive rebase. */ if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && - !is_interactive(&options) && !options.restrict_revision && - options.upstream && - !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { + !is_interactive(&options) && !options.restrict_revision) { int flag; if (!(options.flags & REBASE_FORCE)) { diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 460d0523be..604d624ff8 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' ' echo two >>init.t && git commit -a -m two && git tag two && - test_must_fail git rebase --onto init HEAD^ && + test_must_fail git rebase -f --onto init HEAD^ && GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && grep "show.*$(git rev-parse two)" stderr ) diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index b60b11f9f2..f054186cc7 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int git reset --hard && git checkout conflict-branch && set_fake_editor && - test_must_fail git rebase --onto HEAD~2 HEAD~ && + test_must_fail git rebase -f --onto HEAD~2 HEAD~ && test_must_fail git rebase --edit-todo && git rebase --abort ' diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index 3e6362dd9c..414b4216d6 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -54,6 +54,6 @@ test_expect_success 'add work to upstream' ' changes='our and their changes' test_rebase_same_head success '--onto B B' test_rebase_same_head success '--onto B... B' -test_rebase_same_head failure '--onto master... master' +test_rebase_same_head success '--onto master... master' test_done ^ permalink raw reply related [flat|nested] 123+ messages in thread
* Re: [PATCH v3 3/4] rebase: fast-forward --onto in more cases 2019-04-02 1:48 ` Junio C Hamano @ 2019-04-02 4:44 ` Denton Liu 0 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-02 4:44 UTC (permalink / raw) To: Junio C Hamano Cc: Git Mailing List, Eric Sunshine, Ævar Arnfjörð Bjarmason, Johannes Schindelin Hi Junio, On Tue, Apr 02, 2019 at 10:48:08AM +0900, Junio C Hamano wrote: > Junio C Hamano <gitster@pobox.com> writes: > > > Denton Liu <liu.denton@gmail.com> writes: > > > >> Before, when we had the following graph, > >> > >> A---B---C (master) > >> \ > >> D (side) > >> > >> running 'git rebase --onto master... master side' would result in D > >> being always rebased, no matter what. However, the desired behavior is > >> that rebase should notice that this is fast-forwardable and do that > >> instead. > >> > >> Add detection to `can_fast_forward` so that this case can be detected > >> and a fast-forward will be performed. > > > > OK. As long as the 'onto' commit is a strict ancestor of the side > > branch being rebased, the 'upstream' that is used only to determine > > which commits are on the side branch (essentially those that are not > > reachable from upstream but that are from the side branch) should > > not count in the equation to decide if we fast-forward or not. That > > makes sense. > > > >> --- > >> builtin/rebase.c | 40 +++++++++++++++++++++++----------- > >> t/t3400-rebase.sh | 2 +- > >> t/t3404-rebase-interactive.sh | 2 +- > >> t/t3432-rebase-fast-forward.sh | 2 +- > >> 4 files changed, 30 insertions(+), 16 deletions(-) > >> > >> diff --git a/builtin/rebase.c b/builtin/rebase.c > >> index 77deebc65c..7aa6a090d4 100644 > >> --- a/builtin/rebase.c > >> +++ b/builtin/rebase.c > >> @@ -895,12 +895,12 @@ static int is_linear_history(struct commit *from, struct commit *to) > >> return 1; > >> } > >> > >> -static int can_fast_forward(struct commit *onto, struct object_id *head_oid, > >> - struct object_id *merge_base) > >> +static int can_fast_forward(struct commit *onto, struct commit *upstream, > >> + struct object_id *head_oid, struct object_id *merge_base) > >> { > >> struct commit *head = lookup_commit(the_repository, head_oid); > >> - struct commit_list *merge_bases; > >> - int res; > >> + struct commit_list *merge_bases = NULL; > >> + int res = 0; > >> > >> if (!head) > >> return 0; > >> @@ -908,12 +908,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, > >> merge_bases = get_merge_bases(onto, head); > >> if (merge_bases && !merge_bases->next) { > >> oidcpy(merge_base, &merge_bases->item->object.oid); > >> - res = oideq(merge_base, &onto->object.oid); > >> + if (!oideq(merge_base, &onto->object.oid)) > >> + goto done; > >> } else { > >> oidcpy(merge_base, &null_oid); > >> - res = 0; > >> + goto done; > >> } > > > > The above does not change any existing logic, but purely simplifies > > the code. In your picture in the log message > > > > A---B---C (master) > > \ > > D (side) > > > > where "rebase --onto master... master side" is run, "onto" in this > > function is B, and "head" is D. There is a single merge base B > > (i.e. merge_bases->next == NULL), so we jump to the label "done:" > > with res==0. I don't think this is correct. There is a single merge base so we compare 'merge_base' and 'onto'. They are the same so we don't jump to 'done', instead we keep going. > > > > So why does the remainder of the function need to be changed? > > > >> + if (!upstream) > >> + goto done; > >> + > >> free_commit_list(merge_bases); > >> + merge_bases = get_merge_bases(upstream, head); > >> + if (merge_bases && !merge_bases->next) { > >> + if (!oideq(&onto->object.oid, &merge_bases->item->object.oid)) > >> + goto done; > >> + } else > >> + goto done; > > > > This computes the same between C and D (instead of B and D). Why is > > this needed? Without this logic, t3416 would fail case 2 and 3. It would only see the 'head' and the 'onto' and therefore, it would incorrectly return 1, indicating that it is fast-forwardable. The graph for t3416 is as follows: F---G topic / A---B---C---D---E master \ \ / \ x \ / \ H---I---J---K side And the command that fails is git rebase --onto master...topic F # called from topic Without the additional logic, merge base is C and there is only one merge base. Also, head is G. Finally, onto is also C. We enter the outer if becase there is one merge base then we see that onto == merge_base. Thus, the function would falsely conclude that this is fast-forwardable. This gives us ABCFG when we're expecting ABCG. The additional logic detects if we're not rebasing the entire branch but only part of the branch. In this case, it detects that F is excluded and as a result, we can't fast-forward. > > > > Stepping back a bit, I understand that your argument we saw in the > > log message was that we only need to know if the commit we are > > transplanting the history on (i.e. "onto") already is an ancestor of > > the history being transplanted (i.e. "onto..head"), and it does not > > matter what upstream is. Am I mistaken? Why does this function now > > need to know what 'upstream' is? As stated above, the function needs to know what upstream is because it uses it to determine if the set of commits being rebased is equal to master..topic. If it's not that set, then it's not fast-forwardable. > > > > Puzzled.... ...I guess this is a sign that I need to clarify my commit message. I'll put what I said above into the commit message in my next reroll. Thanks, Denton > > So I replaced the part for builtin/rebase.c from your patch with the > above suggestion (attached) and the result seems to pass the three > tests you touched. I am still puzzled. > > Thanks. > > builtin/rebase.c | 7 ++----- > t/t3400-rebase.sh | 2 +- > t/t3404-rebase-interactive.sh | 2 +- > t/t3432-rebase-fast-forward.sh | 2 +- > 4 files changed, 5 insertions(+), 8 deletions(-) > > diff --git a/builtin/rebase.c b/builtin/rebase.c > index 77deebc65c..fe61c2a899 100644 > --- a/builtin/rebase.c > +++ b/builtin/rebase.c > @@ -1682,13 +1682,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > > /* > * Check if we are already based on onto with linear history, > - * but this should be done only when upstream and onto are the same > - * and if this is not an interactive rebase. > + * but this should be done if this is not an interactive rebase. > */ > if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && > - !is_interactive(&options) && !options.restrict_revision && > - options.upstream && > - !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { > + !is_interactive(&options) && !options.restrict_revision) { > int flag; > > if (!(options.flags & REBASE_FORCE)) { > diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh > index 460d0523be..604d624ff8 100755 > --- a/t/t3400-rebase.sh > +++ b/t/t3400-rebase.sh > @@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' ' > echo two >>init.t && > git commit -a -m two && > git tag two && > - test_must_fail git rebase --onto init HEAD^ && > + test_must_fail git rebase -f --onto init HEAD^ && > GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && > grep "show.*$(git rev-parse two)" stderr > ) > diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh > index b60b11f9f2..f054186cc7 100755 > --- a/t/t3404-rebase-interactive.sh > +++ b/t/t3404-rebase-interactive.sh > @@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int > git reset --hard && > git checkout conflict-branch && > set_fake_editor && > - test_must_fail git rebase --onto HEAD~2 HEAD~ && > + test_must_fail git rebase -f --onto HEAD~2 HEAD~ && > test_must_fail git rebase --edit-todo && > git rebase --abort > ' > diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh > index 3e6362dd9c..414b4216d6 100755 > --- a/t/t3432-rebase-fast-forward.sh > +++ b/t/t3432-rebase-fast-forward.sh > @@ -54,6 +54,6 @@ test_expect_success 'add work to upstream' ' > changes='our and their changes' > test_rebase_same_head success '--onto B B' > test_rebase_same_head success '--onto B... B' > -test_rebase_same_head failure '--onto master... master' > +test_rebase_same_head success '--onto master... master' > > test_done ^ permalink raw reply [flat|nested] 123+ messages in thread
* [PATCH v3 4/4] rebase: teach rebase --keep-base 2019-04-01 20:51 ` [PATCH v3 0/4] " Denton Liu ` (2 preceding siblings ...) 2019-04-01 20:52 ` [PATCH v3 3/4] rebase: fast-forward --onto in more cases Denton Liu @ 2019-04-01 20:52 ` Denton Liu 2019-04-05 21:39 ` [PATCH v4 0/4] " Denton Liu 2019-04-06 19:44 ` [PATCH v3 0/4] rebase: teach rebase --keep-base Ævar Arnfjörð Bjarmason 5 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-01 20:52 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin A common scenario is if a user is working on a topic branch and they wish to make some changes to intermediate commits or autosquash, they would run something such as git rebase -i --onto master... master in order to preserve the merge base. This is useful when contributing a patch series to the Git mailing list, one often starts on top of the current 'master'. However, while developing the patches, 'master' is also developed further and it is sometimes not the bst idea to keep rebasing on top of 'master', but to keep the base commit as-is. Alternatively, a user wishing to test individual commits in a topic branch without changing anything may run git rebase -x ./test.sh master... master Since rebasing onto the merge base of the branch and the upstream is such a common case, introduce the --keep-base option as a shortcut. This allows us to rewrite the above as git rebase -i --keep-base master and git rebase -x ./test.sh --keep-base master respectively. Add tests to ensure --keep-base works correctly in the normal case and fails when there are multiple merge bases, both in regular and interactive mode. Also, test to make sure conflicting options cause rebase to fail. While we're adding test cases, add a missing set_fake_editor call to 'rebase -i --onto master...side'. While we're documenting the --keep-base option, change an instance of "merge-base" to "merge base", which is the consistent spelling. Helped-by: Eric Sunshine <sunshine@sunshineco.com> Helped-by: Junio C Hamano <gitster@pobox.com> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- Documentation/git-rebase.txt | 30 +++++++++++++++-- builtin/rebase.c | 32 ++++++++++++++---- t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++++++++ t/t3431-rebase-fork-point.sh | 4 +++ t/t3432-rebase-fast-forward.sh | 3 ++ 5 files changed, 117 insertions(+), 9 deletions(-) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 6363d674b7..569ab708d4 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip SYNOPSIS -------- [verse] -'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] - [<upstream> [<branch>]] +'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] + [--onto <newbase> | --keep-base] [<upstream> [<branch>]] 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] --root [<branch>] 'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch @@ -217,6 +217,24 @@ As a special case, you may use "A\...B" as a shortcut for the merge base of A and B if there is exactly one merge base. You can leave out at most one of A and B, in which case it defaults to HEAD. +--keep-base:: + Set the starting point at which to create the new commits to the + merge base of <upstream> <branch>. Running + 'git rebase --keep-base <upstream> <branch>' is equivalent to + running 'git rebase --onto <upstream>... <upstream>'. ++ +This option is useful in the case where one is developing a feature on +top of an upstream branch. While the feature is being worked on, the +upstream branch may advance and it may not be the best idea to keep +rebasing on top of the upstream but to keep the base commit as-is. ++ +Although both this option and --fork-point find the merge base between +<upstream> and <branch>, this option uses the merge base as the _starting +point_ on which new commits will be created, whereas --fork-point uses +the merge base to determine the _set of commits_ which will be rebased. ++ +See also INCOMPATIBLE OPTIONS below. + <upstream>:: Upstream branch to compare against. May be any valid commit, not just an existing branch name. Defaults to the configured @@ -364,6 +382,10 @@ ends up being empty, the <upstream> will be used as a fallback. + If either <upstream> or --root is given on the command line, then the default is `--no-fork-point`, otherwise the default is `--fork-point`. ++ +If your branch was based on <upstream> but <upstream> was rewound and +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:: --whitespace=<option>:: @@ -539,6 +561,8 @@ In addition, the following pairs of options are incompatible: * --preserve-merges and --rebase-merges * --rebase-merges and --strategy * --rebase-merges and --strategy-option + * --keep-base and --onto + * --keep-base and --root BEHAVIORAL DIFFERENCES ----------------------- @@ -863,7 +887,7 @@ NOTE: While an "easy case recovery" sometimes appears to be successful --interactive` will be **resurrected**! The idea is to manually tell 'git rebase' "where the old 'subsystem' -ended and your 'topic' began", that is, what the old merge-base +ended and your 'topic' began", that is, what the old merge base between them was. You will have to find a way to name the last commit of the old 'subsystem', for example: diff --git a/builtin/rebase.c b/builtin/rebase.c index 7aa6a090d4..caec1b56e8 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -27,8 +27,8 @@ #include "branch.h" static char const * const builtin_rebase_usage[] = { - N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " - "[<upstream>] [<branch>]"), + N_("git rebase [-i] [options] [--exec <cmd>] " + "[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"), N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " "--root [<branch>]"), N_("git rebase --continue | --abort | --skip | --edit-todo"), @@ -1035,6 +1035,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) }; const char *branch_name; int ret, flags, total_argc, in_progress = 0; + int keep_base = 0; int ok_to_skip_pre_rebase = 0; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; @@ -1068,6 +1069,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_STRING(0, "onto", &options.onto_name, N_("revision"), N_("rebase onto given branch instead of upstream")), + OPT_BOOL(0, "keep-base", &keep_base, + N_("use the merge-base of upstream and branch as the current base")), OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase, N_("allow pre-rebase hook to run")), OPT_NEGBIT('q', "quiet", &options.flags, @@ -1234,6 +1237,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) usage_with_options(builtin_rebase_usage, builtin_rebase_options); + if (keep_base) { + if (options.onto_name) + die(_("cannot combine '--keep-base' with '--onto'")); + if (options.root) + die(_("cannot combine '--keep-base' with '--root'")); + } + if (action != NO_ACTION && !in_progress) die(_("No rebase in progress?")); setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0); @@ -1558,12 +1568,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } /* Make sure the branch to rebase onto is valid. */ - if (!options.onto_name) + if (keep_base) { + strbuf_reset(&buf); + strbuf_addstr(&buf, options.upstream_name); + strbuf_addstr(&buf, "..."); + options.onto_name = xstrdup(buf.buf); + } else if (!options.onto_name) options.onto_name = options.upstream_name; if (strstr(options.onto_name, "...")) { - if (get_oid_mb(options.onto_name, &merge_base) < 0) - die(_("'%s': need exactly one merge base"), - options.onto_name); + if (get_oid_mb(options.onto_name, &merge_base) < 0) { + if (keep_base) + die(_("'%s': need exactly one merge base with branch"), + options.upstream_name); + else + die(_("'%s': need exactly one merge base"), + options.onto_name); + } options.onto = lookup_commit_or_die(&merge_base, options.onto_name); } else { diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh index ddf2f64853..9c2548423b 100755 --- a/t/t3416-rebase-onto-threedots.sh +++ b/t/t3416-rebase-onto-threedots.sh @@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' ' git checkout side && git reset --hard K && + set_fake_editor && test_must_fail git rebase -i --onto master...side J ' +test_expect_success 'rebase --keep-base --onto incompatible' ' + test_must_fail git rebase --keep-base --onto master... +' + +test_expect_success 'rebase --keep-base --root incompatible' ' + test_must_fail git rebase --keep-base --root +' + +test_expect_success 'rebase --keep-base master from topic' ' + git reset --hard && + git checkout topic && + git reset --hard G && + + git rebase --keep-base master && + git rev-parse C >base.expect && + git merge-base master HEAD >base.actual && + test_cmp base.expect base.actual && + + git rev-parse HEAD~2 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase --keep-base master from side' ' + git reset --hard && + git checkout side && + git reset --hard K && + + test_must_fail git rebase --keep-base master +' + +test_expect_success 'rebase -i --keep-base master from topic' ' + git reset --hard && + git checkout topic && + git reset --hard G && + + set_fake_editor && + EXPECT_COUNT=2 git rebase -i --keep-base master && + git rev-parse C >base.expect && + git merge-base master HEAD >base.actual && + test_cmp base.expect base.actual && + + git rev-parse HEAD~2 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase -i --keep-base master from side' ' + git reset --hard && + git checkout side && + git reset --hard K && + + set_fake_editor && + test_must_fail git rebase -i --keep-base master +' + test_done diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh index 8e2483b73e..0311bcbc68 100755 --- a/t/t3431-rebase-fork-point.sh +++ b/t/t3431-rebase-fork-point.sh @@ -43,11 +43,15 @@ test_rebase() { test_rebase 'G F E D B A' '' test_rebase 'G F D B A' '--onto D' +test_rebase 'G F B A' '--keep-base' test_rebase 'G F C E D B A' '--no-fork-point' test_rebase 'G F C D B A' '--no-fork-point --onto D' +test_rebase 'G F C B A' '--no-fork-point --keep-base' test_rebase 'G F E D B A' '--fork-point refs/heads/master' test_rebase 'G F D B A' '--fork-point --onto D refs/heads/master' +test_rebase 'G F B A' '--fork-point --keep-base refs/heads/master' test_rebase 'G F C E D B A' 'refs/heads/master' test_rebase 'G F C D B A' '--onto D refs/heads/master' +test_rebase 'G F C B A' '--keep-base refs/heads/master' test_done diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index 414b4216d6..8585c21c5c 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -33,6 +33,7 @@ test_rebase_same_head success 'master' test_rebase_same_head success '--onto B B' test_rebase_same_head success '--onto B... B' test_rebase_same_head success '--onto master... master' +test_rebase_same_head success '--keep-base master' test_expect_success 'add work to side' ' test_commit E @@ -44,6 +45,7 @@ test_rebase_same_head success 'master' test_rebase_same_head success '--onto B B' test_rebase_same_head success '--onto B... B' test_rebase_same_head success '--onto master... master' +test_rebase_same_head success '--keep-base master' test_expect_success 'add work to upstream' ' git checkout master && @@ -55,5 +57,6 @@ changes='our and their changes' test_rebase_same_head success '--onto B B' test_rebase_same_head success '--onto B... B' test_rebase_same_head success '--onto master... master' +test_rebase_same_head success '--keep-base master' test_done -- 2.21.0.695.gaf8658f249 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v4 0/4] rebase: teach rebase --keep-base 2019-04-01 20:51 ` [PATCH v3 0/4] " Denton Liu ` (3 preceding siblings ...) 2019-04-01 20:52 ` [PATCH v3 4/4] rebase: teach rebase --keep-base Denton Liu @ 2019-04-05 21:39 ` Denton Liu 2019-04-05 21:40 ` [PATCH v4 1/4] t3431: add rebase --fork-point tests Denton Liu ` (5 more replies) 2019-04-06 19:44 ` [PATCH v3 0/4] rebase: teach rebase --keep-base Ævar Arnfjörð Bjarmason 5 siblings, 6 replies; 123+ messages in thread From: Denton Liu @ 2019-04-05 21:39 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Hi all, I dug into it a little more and according to the bash 4.2->4.3 changelog, bbb. Fixed a bug that caused spurious DEL characters (\177) to appear in double-quoted expansion where the RHS is evaluated to the empty string. so, in particular, I've removed all of the '' from the tests. I also downloaded bash 4.2 and tested everything with that. Seems to be working well! Thanks for catching this! --- This patchset now depends "[PATCH 1/8] tests (rebase): spell out the `--keep-empty` option" which is the first patch of Johannes's "Do not use abbreviated options in tests" patchset[1]. (Thanks for catching that, Johannes!) Changes since v1: * Squashed old set into one patch * Fixed indentation style and dangling else * Added more documentation after discussion with Ævar Changes since v2: * Add testing for rebase --fork-point behaviour * Add testing for rebase fast-forward behaviour * Make rebase --onto fast-forward in more cases * Update documentation to include use-case Changes since v3: * Fix tests failing on bash 4.2 * Fix typo in t3431 comment [1]: https://public-inbox.org/git/a1b4b74b9167e279dae4cd8c58fb28d8a714a66a.1553537656.git.gitgitgadget@gmail.com/ Denton Liu (4): t3431: add rebase --fork-point tests t3432: test rebase fast-forward behavior rebase: fast-forward --onto in more cases rebase: teach rebase --keep-base Documentation/git-rebase.txt | 30 +++++++++++-- builtin/rebase.c | 72 +++++++++++++++++++++++--------- t/t3400-rebase.sh | 2 +- t/t3404-rebase-interactive.sh | 2 +- t/t3416-rebase-onto-threedots.sh | 57 +++++++++++++++++++++++++ t/t3431-rebase-fork-point.sh | 57 +++++++++++++++++++++++++ t/t3432-rebase-fast-forward.sh | 62 +++++++++++++++++++++++++++ 7 files changed, 258 insertions(+), 24 deletions(-) create mode 100755 t/t3431-rebase-fork-point.sh create mode 100755 t/t3432-rebase-fast-forward.sh Interdiff against v3: diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh index 0311bcbc68..e63040932f 100755 --- a/t/t3431-rebase-fork-point.sh +++ b/t/t3431-rebase-fork-point.sh @@ -11,7 +11,7 @@ test_description='git rebase --fork-point test' # \ # C*---F---G (side) # -# C was formerly part of master but is side out +# C was formerly part of master but master was rewound to remove C # test_expect_success setup ' test_commit A && @@ -29,29 +29,29 @@ test_expect_success setup ' test_rebase() { expected="$1" && shift && - test_expect_success "git rebase $@" " + test_expect_success "git rebase $*" " git checkout master && git reset --hard E && git checkout side && git reset --hard G && - git rebase $@ && + git rebase $* && test_write_lines $expected >expect && git log --pretty=%s >actual && test_cmp expect actual " } -test_rebase 'G F E D B A' '' -test_rebase 'G F D B A' '--onto D' -test_rebase 'G F B A' '--keep-base' -test_rebase 'G F C E D B A' '--no-fork-point' -test_rebase 'G F C D B A' '--no-fork-point --onto D' -test_rebase 'G F C B A' '--no-fork-point --keep-base' -test_rebase 'G F E D B A' '--fork-point refs/heads/master' -test_rebase 'G F D B A' '--fork-point --onto D refs/heads/master' -test_rebase 'G F B A' '--fork-point --keep-base refs/heads/master' -test_rebase 'G F C E D B A' 'refs/heads/master' -test_rebase 'G F C D B A' '--onto D refs/heads/master' -test_rebase 'G F C B A' '--keep-base refs/heads/master' +test_rebase 'G F E D B A' +test_rebase 'G F D B A' --onto D +test_rebase 'G F B A' --keep-base +test_rebase 'G F C E D B A' --no-fork-point +test_rebase 'G F C D B A' --no-fork-point --onto D +test_rebase 'G F C B A' --no-fork-point --keep-base +test_rebase 'G F E D B A' --fork-point refs/heads/master +test_rebase 'G F D B A' --fork-point --onto D refs/heads/master +test_rebase 'G F B A' --fork-point --keep-base refs/heads/master +test_rebase 'G F C E D B A' refs/heads/master +test_rebase 'G F C D B A' --onto D refs/heads/master +test_rebase 'G F C B A' --keep-base refs/heads/master test_done diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index 8585c21c5c..f493ce64c4 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -18,34 +18,34 @@ test_expect_success setup ' test_rebase_same_head() { status="$1" && shift && - test_expect_$status "git rebase $@ with $changes is no-op" " + test_expect_$status "git rebase $* with $changes is no-op" " oldhead=\$(git rev-parse HEAD) && test_when_finished 'git reset --hard \$oldhead' && - git rebase $@ && + git rebase $* && newhead=\$(git rev-parse HEAD) && test_cmp_rev \$oldhead \$newhead " } changes='no changes' -test_rebase_same_head success '' -test_rebase_same_head success 'master' -test_rebase_same_head success '--onto B B' -test_rebase_same_head success '--onto B... B' -test_rebase_same_head success '--onto master... master' -test_rebase_same_head success '--keep-base master' +test_rebase_same_head success +test_rebase_same_head success master +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head success --onto master... master +test_rebase_same_head success --keep-base master test_expect_success 'add work to side' ' test_commit E ' changes='our changes' -test_rebase_same_head success '' -test_rebase_same_head success 'master' -test_rebase_same_head success '--onto B B' -test_rebase_same_head success '--onto B... B' -test_rebase_same_head success '--onto master... master' -test_rebase_same_head success '--keep-base master' +test_rebase_same_head success +test_rebase_same_head success master +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head success --onto master... master +test_rebase_same_head success --keep-base master test_expect_success 'add work to upstream' ' git checkout master && @@ -54,9 +54,9 @@ test_expect_success 'add work to upstream' ' ' changes='our and their changes' -test_rebase_same_head success '--onto B B' -test_rebase_same_head success '--onto B... B' -test_rebase_same_head success '--onto master... master' -test_rebase_same_head success '--keep-base master' +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head success --onto master... master +test_rebase_same_head success --keep-base master test_done -- 2.21.0.843.gd0ae0373aa ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v4 1/4] t3431: add rebase --fork-point tests 2019-04-05 21:39 ` [PATCH v4 0/4] " Denton Liu @ 2019-04-05 21:40 ` Denton Liu 2019-04-05 21:40 ` [PATCH v4 2/4] t3432: test rebase fast-forward behavior Denton Liu ` (4 subsequent siblings) 5 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-05 21:40 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Signed-off-by: Denton Liu <liu.denton@gmail.com> --- t/t3431-rebase-fork-point.sh | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100755 t/t3431-rebase-fork-point.sh diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh new file mode 100755 index 0000000000..6d523123d0 --- /dev/null +++ b/t/t3431-rebase-fork-point.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# +# Copyright (c) 2019 Denton Liu +# + +test_description='git rebase --fork-point test' + +. ./test-lib.sh + +# A---B---D---E (master) +# \ +# C*---F---G (side) +# +# C was formerly part of master but master was rewound to remove C +# +test_expect_success setup ' + test_commit A && + test_commit B && + test_commit C && + git branch -t side && + git reset --hard HEAD^ && + test_commit D && + test_commit E && + git checkout side && + test_commit F && + test_commit G +' + +test_rebase() { + expected="$1" && + shift && + test_expect_success "git rebase $*" " + git checkout master && + git reset --hard E && + git checkout side && + git reset --hard G && + git rebase $* && + test_write_lines $expected >expect && + git log --pretty=%s >actual && + test_cmp expect actual + " +} + +test_rebase 'G F E D B A' +test_rebase 'G F D B A' --onto D +test_rebase 'G F C E D B A' --no-fork-point +test_rebase 'G F C D B A' --no-fork-point --onto D +test_rebase 'G F E D B A' --fork-point refs/heads/master +test_rebase 'G F D B A' --fork-point --onto D refs/heads/master +test_rebase 'G F C E D B A' refs/heads/master +test_rebase 'G F C D B A' --onto D refs/heads/master + +test_done -- 2.21.0.843.gd0ae0373aa ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v4 2/4] t3432: test rebase fast-forward behavior 2019-04-05 21:39 ` [PATCH v4 0/4] " Denton Liu 2019-04-05 21:40 ` [PATCH v4 1/4] t3431: add rebase --fork-point tests Denton Liu @ 2019-04-05 21:40 ` Denton Liu 2019-04-05 21:40 ` [PATCH v4 3/4] rebase: fast-forward --onto in more cases Denton Liu ` (3 subsequent siblings) 5 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-05 21:40 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor When rebase is run on a branch that can be fast-forwarded, this should automatically be done. Create test to ensure this behavior happens. There is one case that currently does not pass. In the case where a feature and master have diverged, running "git rebase master... master" causes a full rebase to happen even though a fast-forward should happen. Mark this case as failure so we can fix it later. Signed-off-by: Denton Liu <liu.denton@gmail.com> --- t/t3432-rebase-fast-forward.sh | 59 ++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100755 t/t3432-rebase-fast-forward.sh diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh new file mode 100755 index 0000000000..b7c9af17c3 --- /dev/null +++ b/t/t3432-rebase-fast-forward.sh @@ -0,0 +1,59 @@ +#!/bin/sh +# +# Copyright (c) 2019 Denton Liu +# + +test_description='ensure rebase fast-forwards commits when possible' + +. ./test-lib.sh + +test_expect_success setup ' + test_commit A && + test_commit B && + test_commit C && + test_commit D && + git checkout -t -b side +' + +test_rebase_same_head() { + status="$1" && + shift && + test_expect_$status "git rebase $* with $changes is no-op" " + oldhead=\$(git rev-parse HEAD) && + test_when_finished 'git reset --hard \$oldhead' && + git rebase $* && + newhead=\$(git rev-parse HEAD) && + test_cmp_rev \$oldhead \$newhead + " +} + +changes='no changes' +test_rebase_same_head success +test_rebase_same_head success master +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head success --onto master... master + +test_expect_success 'add work to side' ' + test_commit E +' + +changes='our changes' +test_rebase_same_head success +test_rebase_same_head success master +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head success --onto master... master + +test_expect_success 'add work to upstream' ' + git checkout master && + test_commit F && + git checkout side +' + +changes='our and their changes' +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head failure --onto master... master + +test_done -- 2.21.0.843.gd0ae0373aa ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v4 3/4] rebase: fast-forward --onto in more cases 2019-04-05 21:39 ` [PATCH v4 0/4] " Denton Liu 2019-04-05 21:40 ` [PATCH v4 1/4] t3431: add rebase --fork-point tests Denton Liu 2019-04-05 21:40 ` [PATCH v4 2/4] t3432: test rebase fast-forward behavior Denton Liu @ 2019-04-05 21:40 ` Denton Liu 2019-04-05 21:40 ` [PATCH v4 4/4] rebase: teach rebase --keep-base Denton Liu ` (2 subsequent siblings) 5 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-05 21:40 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Before, when we had the following graph, A---B---C (master) \ D (side) running 'git rebase --onto master... master side' would result in D being always rebased, no matter what. However, the desired behavior is that rebase should notice that this is fast-forwardable and do that instead. Add detection to `can_fast_forward` so that this case can be detected and a fast-forward will be performed. First of all, rewrite the function to use gotos which simplifies the logic. Next, since the options.upstream && !oidcmp(&options.upstream->object.oid, &options.onto->object.oid) conditions were removed in `cmd_rebase`, we reintroduce a substitute in `can_fast_forward`. In particular, checking the merge bases of `upstream` and `head` fixes a failing case in t3416. The abbreviated graph for t3416 is as follows: F---G topic / A---B---C---D---E master and the failing command was git rebase --onto master...topic F topic Before, Git would see that there was one merge base (C), and the merge and onto were the same so it would incorrectly return 1, indicating that we could fast-forward. This would cause the rebased graph to be 'ABCFG' when we were expecting 'ABCG'. With the additional logic, we detect that upstream and head's merge base is F. Since onto isn't F, it means we're not rebasing the full set of commits from master..topic. Since we're excluding some commits, a fast-forward cannot be performed and so we correctly return 0. Add '-f' to test cases that failed as a result of this change because they were not expecting a fast-forward so that a rebase is forced. While we're at it, remove a trailing whitespace from rebase.c. Signed-off-by: Denton Liu <liu.denton@gmail.com> --- builtin/rebase.c | 40 +++++++++++++++++++++++----------- t/t3400-rebase.sh | 2 +- t/t3404-rebase-interactive.sh | 2 +- t/t3432-rebase-fast-forward.sh | 2 +- 4 files changed, 30 insertions(+), 16 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 77deebc65c..7aa6a090d4 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -895,12 +895,12 @@ static int is_linear_history(struct commit *from, struct commit *to) return 1; } -static int can_fast_forward(struct commit *onto, struct object_id *head_oid, - struct object_id *merge_base) +static int can_fast_forward(struct commit *onto, struct commit *upstream, + struct object_id *head_oid, struct object_id *merge_base) { struct commit *head = lookup_commit(the_repository, head_oid); - struct commit_list *merge_bases; - int res; + struct commit_list *merge_bases = NULL; + int res = 0; if (!head) return 0; @@ -908,12 +908,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, merge_bases = get_merge_bases(onto, head); if (merge_bases && !merge_bases->next) { oidcpy(merge_base, &merge_bases->item->object.oid); - res = oideq(merge_base, &onto->object.oid); + if (!oideq(merge_base, &onto->object.oid)) + goto done; } else { oidcpy(merge_base, &null_oid); - res = 0; + goto done; } + + if (!upstream) + goto done; + free_commit_list(merge_bases); + merge_bases = get_merge_bases(upstream, head); + if (merge_bases && !merge_bases->next) { + if (!oideq(&onto->object.oid, &merge_bases->item->object.oid)) + goto done; + } else + goto done; + + res = 1; + +done: + if (merge_bases) + free_commit_list(merge_bases); return res && is_linear_history(onto, head); } @@ -1682,13 +1699,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) /* * Check if we are already based on onto with linear history, - * but this should be done only when upstream and onto are the same - * and if this is not an interactive rebase. + * but this should be done if this is not an interactive rebase. */ - if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && - !is_interactive(&options) && !options.restrict_revision && - options.upstream && - !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { + if (can_fast_forward(options.onto, options.upstream, &options.orig_head, &merge_base) && + !is_interactive(&options) && !options.restrict_revision) { int flag; if (!(options.flags & REBASE_FORCE)) { @@ -1782,7 +1796,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&msg, "%s: checkout %s", getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name); if (reset_head(&options.onto->object.oid, "checkout", NULL, - RESET_HEAD_DETACH | RESET_ORIG_HEAD | + RESET_HEAD_DETACH | RESET_ORIG_HEAD | RESET_HEAD_RUN_POST_CHECKOUT_HOOK, NULL, msg.buf)) die(_("Could not detach HEAD")); diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 460d0523be..604d624ff8 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' ' echo two >>init.t && git commit -a -m two && git tag two && - test_must_fail git rebase --onto init HEAD^ && + test_must_fail git rebase -f --onto init HEAD^ && GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && grep "show.*$(git rev-parse two)" stderr ) diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index b60b11f9f2..f054186cc7 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int git reset --hard && git checkout conflict-branch && set_fake_editor && - test_must_fail git rebase --onto HEAD~2 HEAD~ && + test_must_fail git rebase -f --onto HEAD~2 HEAD~ && test_must_fail git rebase --edit-todo && git rebase --abort ' diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index b7c9af17c3..357cf17bdd 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -54,6 +54,6 @@ test_expect_success 'add work to upstream' ' changes='our and their changes' test_rebase_same_head success --onto B B test_rebase_same_head success --onto B... B -test_rebase_same_head failure --onto master... master +test_rebase_same_head success --onto master... master test_done -- 2.21.0.843.gd0ae0373aa ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v4 4/4] rebase: teach rebase --keep-base 2019-04-05 21:39 ` [PATCH v4 0/4] " Denton Liu ` (2 preceding siblings ...) 2019-04-05 21:40 ` [PATCH v4 3/4] rebase: fast-forward --onto in more cases Denton Liu @ 2019-04-05 21:40 ` Denton Liu 2019-04-15 22:29 ` [PATCH v5 0/5] " Denton Liu 2019-04-17 18:01 ` [PATCH v6 0/5] " Denton Liu 5 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-05 21:40 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor A common scenario is if a user is working on a topic branch and they wish to make some changes to intermediate commits or autosquash, they would run something such as git rebase -i --onto master... master in order to preserve the merge base. This is useful when contributing a patch series to the Git mailing list, one often starts on top of the current 'master'. However, while developing the patches, 'master' is also developed further and it is sometimes not the bst idea to keep rebasing on top of 'master', but to keep the base commit as-is. Alternatively, a user wishing to test individual commits in a topic branch without changing anything may run git rebase -x ./test.sh master... master Since rebasing onto the merge base of the branch and the upstream is such a common case, introduce the --keep-base option as a shortcut. This allows us to rewrite the above as git rebase -i --keep-base master and git rebase -x ./test.sh --keep-base master respectively. Add tests to ensure --keep-base works correctly in the normal case and fails when there are multiple merge bases, both in regular and interactive mode. Also, test to make sure conflicting options cause rebase to fail. While we're adding test cases, add a missing set_fake_editor call to 'rebase -i --onto master...side'. While we're documenting the --keep-base option, change an instance of "merge-base" to "merge base", which is the consistent spelling. Helped-by: Eric Sunshine <sunshine@sunshineco.com> Helped-by: Junio C Hamano <gitster@pobox.com> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- Documentation/git-rebase.txt | 30 +++++++++++++++-- builtin/rebase.c | 32 ++++++++++++++---- t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++++++++ t/t3431-rebase-fork-point.sh | 4 +++ t/t3432-rebase-fast-forward.sh | 3 ++ 5 files changed, 117 insertions(+), 9 deletions(-) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 6363d674b7..569ab708d4 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip SYNOPSIS -------- [verse] -'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] - [<upstream> [<branch>]] +'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] + [--onto <newbase> | --keep-base] [<upstream> [<branch>]] 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] --root [<branch>] 'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch @@ -217,6 +217,24 @@ As a special case, you may use "A\...B" as a shortcut for the merge base of A and B if there is exactly one merge base. You can leave out at most one of A and B, in which case it defaults to HEAD. +--keep-base:: + Set the starting point at which to create the new commits to the + merge base of <upstream> <branch>. Running + 'git rebase --keep-base <upstream> <branch>' is equivalent to + running 'git rebase --onto <upstream>... <upstream>'. ++ +This option is useful in the case where one is developing a feature on +top of an upstream branch. While the feature is being worked on, the +upstream branch may advance and it may not be the best idea to keep +rebasing on top of the upstream but to keep the base commit as-is. ++ +Although both this option and --fork-point find the merge base between +<upstream> and <branch>, this option uses the merge base as the _starting +point_ on which new commits will be created, whereas --fork-point uses +the merge base to determine the _set of commits_ which will be rebased. ++ +See also INCOMPATIBLE OPTIONS below. + <upstream>:: Upstream branch to compare against. May be any valid commit, not just an existing branch name. Defaults to the configured @@ -364,6 +382,10 @@ ends up being empty, the <upstream> will be used as a fallback. + If either <upstream> or --root is given on the command line, then the default is `--no-fork-point`, otherwise the default is `--fork-point`. ++ +If your branch was based on <upstream> but <upstream> was rewound and +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:: --whitespace=<option>:: @@ -539,6 +561,8 @@ In addition, the following pairs of options are incompatible: * --preserve-merges and --rebase-merges * --rebase-merges and --strategy * --rebase-merges and --strategy-option + * --keep-base and --onto + * --keep-base and --root BEHAVIORAL DIFFERENCES ----------------------- @@ -863,7 +887,7 @@ NOTE: While an "easy case recovery" sometimes appears to be successful --interactive` will be **resurrected**! The idea is to manually tell 'git rebase' "where the old 'subsystem' -ended and your 'topic' began", that is, what the old merge-base +ended and your 'topic' began", that is, what the old merge base between them was. You will have to find a way to name the last commit of the old 'subsystem', for example: diff --git a/builtin/rebase.c b/builtin/rebase.c index 7aa6a090d4..caec1b56e8 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -27,8 +27,8 @@ #include "branch.h" static char const * const builtin_rebase_usage[] = { - N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " - "[<upstream>] [<branch>]"), + N_("git rebase [-i] [options] [--exec <cmd>] " + "[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"), N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " "--root [<branch>]"), N_("git rebase --continue | --abort | --skip | --edit-todo"), @@ -1035,6 +1035,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) }; const char *branch_name; int ret, flags, total_argc, in_progress = 0; + int keep_base = 0; int ok_to_skip_pre_rebase = 0; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; @@ -1068,6 +1069,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_STRING(0, "onto", &options.onto_name, N_("revision"), N_("rebase onto given branch instead of upstream")), + OPT_BOOL(0, "keep-base", &keep_base, + N_("use the merge-base of upstream and branch as the current base")), OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase, N_("allow pre-rebase hook to run")), OPT_NEGBIT('q', "quiet", &options.flags, @@ -1234,6 +1237,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) usage_with_options(builtin_rebase_usage, builtin_rebase_options); + if (keep_base) { + if (options.onto_name) + die(_("cannot combine '--keep-base' with '--onto'")); + if (options.root) + die(_("cannot combine '--keep-base' with '--root'")); + } + if (action != NO_ACTION && !in_progress) die(_("No rebase in progress?")); setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0); @@ -1558,12 +1568,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } /* Make sure the branch to rebase onto is valid. */ - if (!options.onto_name) + if (keep_base) { + strbuf_reset(&buf); + strbuf_addstr(&buf, options.upstream_name); + strbuf_addstr(&buf, "..."); + options.onto_name = xstrdup(buf.buf); + } else if (!options.onto_name) options.onto_name = options.upstream_name; if (strstr(options.onto_name, "...")) { - if (get_oid_mb(options.onto_name, &merge_base) < 0) - die(_("'%s': need exactly one merge base"), - options.onto_name); + if (get_oid_mb(options.onto_name, &merge_base) < 0) { + if (keep_base) + die(_("'%s': need exactly one merge base with branch"), + options.upstream_name); + else + die(_("'%s': need exactly one merge base"), + options.onto_name); + } options.onto = lookup_commit_or_die(&merge_base, options.onto_name); } else { diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh index ddf2f64853..9c2548423b 100755 --- a/t/t3416-rebase-onto-threedots.sh +++ b/t/t3416-rebase-onto-threedots.sh @@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' ' git checkout side && git reset --hard K && + set_fake_editor && test_must_fail git rebase -i --onto master...side J ' +test_expect_success 'rebase --keep-base --onto incompatible' ' + test_must_fail git rebase --keep-base --onto master... +' + +test_expect_success 'rebase --keep-base --root incompatible' ' + test_must_fail git rebase --keep-base --root +' + +test_expect_success 'rebase --keep-base master from topic' ' + git reset --hard && + git checkout topic && + git reset --hard G && + + git rebase --keep-base master && + git rev-parse C >base.expect && + git merge-base master HEAD >base.actual && + test_cmp base.expect base.actual && + + git rev-parse HEAD~2 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase --keep-base master from side' ' + git reset --hard && + git checkout side && + git reset --hard K && + + test_must_fail git rebase --keep-base master +' + +test_expect_success 'rebase -i --keep-base master from topic' ' + git reset --hard && + git checkout topic && + git reset --hard G && + + set_fake_editor && + EXPECT_COUNT=2 git rebase -i --keep-base master && + git rev-parse C >base.expect && + git merge-base master HEAD >base.actual && + test_cmp base.expect base.actual && + + git rev-parse HEAD~2 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase -i --keep-base master from side' ' + git reset --hard && + git checkout side && + git reset --hard K && + + set_fake_editor && + test_must_fail git rebase -i --keep-base master +' + test_done diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh index 6d523123d0..e63040932f 100755 --- a/t/t3431-rebase-fork-point.sh +++ b/t/t3431-rebase-fork-point.sh @@ -43,11 +43,15 @@ test_rebase() { test_rebase 'G F E D B A' test_rebase 'G F D B A' --onto D +test_rebase 'G F B A' --keep-base test_rebase 'G F C E D B A' --no-fork-point test_rebase 'G F C D B A' --no-fork-point --onto D +test_rebase 'G F C B A' --no-fork-point --keep-base test_rebase 'G F E D B A' --fork-point refs/heads/master test_rebase 'G F D B A' --fork-point --onto D refs/heads/master +test_rebase 'G F B A' --fork-point --keep-base refs/heads/master test_rebase 'G F C E D B A' refs/heads/master test_rebase 'G F C D B A' --onto D refs/heads/master +test_rebase 'G F C B A' --keep-base refs/heads/master test_done diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index 357cf17bdd..f493ce64c4 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -33,6 +33,7 @@ test_rebase_same_head success master test_rebase_same_head success --onto B B test_rebase_same_head success --onto B... B test_rebase_same_head success --onto master... master +test_rebase_same_head success --keep-base master test_expect_success 'add work to side' ' test_commit E @@ -44,6 +45,7 @@ test_rebase_same_head success master test_rebase_same_head success --onto B B test_rebase_same_head success --onto B... B test_rebase_same_head success --onto master... master +test_rebase_same_head success --keep-base master test_expect_success 'add work to upstream' ' git checkout master && @@ -55,5 +57,6 @@ changes='our and their changes' test_rebase_same_head success --onto B B test_rebase_same_head success --onto B... B test_rebase_same_head success --onto master... master +test_rebase_same_head success --keep-base master test_done -- 2.21.0.843.gd0ae0373aa ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v5 0/5] rebase: teach rebase --keep-base 2019-04-05 21:39 ` [PATCH v4 0/4] " Denton Liu ` (3 preceding siblings ...) 2019-04-05 21:40 ` [PATCH v4 4/4] rebase: teach rebase --keep-base Denton Liu @ 2019-04-15 22:29 ` Denton Liu 2019-04-15 22:29 ` [PATCH v5 1/5] t3431: add rebase --fork-point tests Denton Liu ` (4 more replies) 2019-04-17 18:01 ` [PATCH v6 0/5] " Denton Liu 5 siblings, 5 replies; 123+ messages in thread From: Denton Liu @ 2019-04-15 22:29 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Thanks for the example, Ævar, it clarified a lot. I think that _now_ we're on the same page. ;) I made can_fast_forward detect this case and now, it should behave as expected. The change can be seen in 4/5. Hopefully this saves you some work later. --- This patchset now depends "[PATCH 1/8] tests (rebase): spell out the `--keep-empty` option" which is the first patch of Johannes's "Do not use abbreviated options in tests" patchset[1]. (Thanks for catching that, Johannes!) Changes since v1: * Squashed old set into one patch * Fixed indentation style and dangling else * Added more documentation after discussion with Ævar Changes since v2: * Add testing for rebase --fork-point behaviour * Add testing for rebase fast-forward behaviour * Make rebase --onto fast-forward in more cases * Update documentation to include use-case Changes since v3: * Fix tests failing on bash 4.2 * Fix typo in t3431 comment Changes since v4: * Make rebase --fork-point fast-forward in more cases [1]: https://public-inbox.org/git/a1b4b74b9167e279dae4cd8c58fb28d8a714a66a.1553537656.git.gitgitgadget@gmail.com/ Denton Liu (5): t3431: add rebase --fork-point tests t3432: test rebase fast-forward behavior rebase: fast-forward --onto in more cases rebase: fast-forward --fork-point in more cases rebase: teach rebase --keep-base Documentation/git-rebase.txt | 30 ++++++++++-- builtin/rebase.c | 77 +++++++++++++++++++++-------- t/t3400-rebase.sh | 2 +- t/t3404-rebase-interactive.sh | 2 +- t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++ t/t3431-rebase-fork-point.sh | 57 ++++++++++++++++++++++ t/t3432-rebase-fast-forward.sh | 83 ++++++++++++++++++++++++++++++++ 7 files changed, 284 insertions(+), 24 deletions(-) create mode 100755 t/t3431-rebase-fork-point.sh create mode 100755 t/t3432-rebase-fast-forward.sh -- 2.21.0.921.gb27c68c4e9 ^ permalink raw reply [flat|nested] 123+ messages in thread
* [PATCH v5 1/5] t3431: add rebase --fork-point tests 2019-04-15 22:29 ` [PATCH v5 0/5] " Denton Liu @ 2019-04-15 22:29 ` Denton Liu 2019-04-15 22:29 ` [PATCH v5 2/5] t3432: test rebase fast-forward behavior Denton Liu ` (3 subsequent siblings) 4 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-15 22:29 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Signed-off-by: Denton Liu <liu.denton@gmail.com> --- t/t3431-rebase-fork-point.sh | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100755 t/t3431-rebase-fork-point.sh diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh new file mode 100755 index 0000000000..6d523123d0 --- /dev/null +++ b/t/t3431-rebase-fork-point.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# +# Copyright (c) 2019 Denton Liu +# + +test_description='git rebase --fork-point test' + +. ./test-lib.sh + +# A---B---D---E (master) +# \ +# C*---F---G (side) +# +# C was formerly part of master but master was rewound to remove C +# +test_expect_success setup ' + test_commit A && + test_commit B && + test_commit C && + git branch -t side && + git reset --hard HEAD^ && + test_commit D && + test_commit E && + git checkout side && + test_commit F && + test_commit G +' + +test_rebase() { + expected="$1" && + shift && + test_expect_success "git rebase $*" " + git checkout master && + git reset --hard E && + git checkout side && + git reset --hard G && + git rebase $* && + test_write_lines $expected >expect && + git log --pretty=%s >actual && + test_cmp expect actual + " +} + +test_rebase 'G F E D B A' +test_rebase 'G F D B A' --onto D +test_rebase 'G F C E D B A' --no-fork-point +test_rebase 'G F C D B A' --no-fork-point --onto D +test_rebase 'G F E D B A' --fork-point refs/heads/master +test_rebase 'G F D B A' --fork-point --onto D refs/heads/master +test_rebase 'G F C E D B A' refs/heads/master +test_rebase 'G F C D B A' --onto D refs/heads/master + +test_done -- 2.21.0.921.gb27c68c4e9 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v5 2/5] t3432: test rebase fast-forward behavior 2019-04-15 22:29 ` [PATCH v5 0/5] " Denton Liu 2019-04-15 22:29 ` [PATCH v5 1/5] t3431: add rebase --fork-point tests Denton Liu @ 2019-04-15 22:29 ` Denton Liu 2019-04-15 22:29 ` [PATCH v5 3/5] rebase: fast-forward --onto in more cases Denton Liu ` (2 subsequent siblings) 4 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-15 22:29 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor When rebase is run on a branch that can be fast-forwarded, this should automatically be done. Create test to ensure this behavior happens. There are some cases that currently don't pass. The first case is where a feature and master have diverged, running "git rebase master... master" causes a full rebase to happen even though a fast-forward should happen. The second case is when we are doing "git rebase --fork-point" and a fork-point commit is found. Once again, a full rebase happens even though a fast-forward should happen. Mark these cases as failure so we can fix it later. Signed-off-by: Denton Liu <liu.denton@gmail.com> --- t/t3432-rebase-fast-forward.sh | 72 ++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100755 t/t3432-rebase-fast-forward.sh diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh new file mode 100755 index 0000000000..4f04d67fd7 --- /dev/null +++ b/t/t3432-rebase-fast-forward.sh @@ -0,0 +1,72 @@ +#!/bin/sh +# +# Copyright (c) 2019 Denton Liu +# + +test_description='ensure rebase fast-forwards commits when possible' + +. ./test-lib.sh + +test_expect_success setup ' + test_commit A && + test_commit B && + test_commit C && + test_commit D && + git checkout -t -b side +' + +test_rebase_same_head() { + status="$1" && + shift && + test_expect_$status "git rebase $* with $changes is no-op" " + oldhead=\$(git rev-parse HEAD) && + test_when_finished 'git reset --hard \$oldhead' && + git rebase $* && + newhead=\$(git rev-parse HEAD) && + test_cmp_rev \$oldhead \$newhead + " +} + +changes='no changes' +test_rebase_same_head success +test_rebase_same_head success master +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head success --onto master... master +test_rebase_same_head success --no-fork-point +test_rebase_same_head success --fork-point master +test_rebase_same_head failure --fork-point --onto B B +test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head success --fork-point --onto master... master + +test_expect_success 'add work to side' ' + test_commit E +' + +changes='our changes' +test_rebase_same_head success +test_rebase_same_head success master +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head success --onto master... master +test_rebase_same_head success --no-fork-point +test_rebase_same_head success --fork-point master +test_rebase_same_head failure --fork-point --onto B B +test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head success --fork-point --onto master... master + +test_expect_success 'add work to upstream' ' + git checkout master && + test_commit F && + git checkout side +' + +changes='our and their changes' +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head failure --onto master... master +test_rebase_same_head failure --fork-point --onto B B +test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head failure --fork-point --onto master... master + +test_done -- 2.21.0.921.gb27c68c4e9 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v5 3/5] rebase: fast-forward --onto in more cases 2019-04-15 22:29 ` [PATCH v5 0/5] " Denton Liu 2019-04-15 22:29 ` [PATCH v5 1/5] t3431: add rebase --fork-point tests Denton Liu 2019-04-15 22:29 ` [PATCH v5 2/5] t3432: test rebase fast-forward behavior Denton Liu @ 2019-04-15 22:29 ` Denton Liu 2019-04-16 6:26 ` Junio C Hamano ` (2 more replies) 2019-04-15 22:29 ` [PATCH v5 4/5] rebase: fast-forward --fork-point " Denton Liu 2019-04-15 22:29 ` [PATCH v5 5/5] rebase: teach rebase --keep-base Denton Liu 4 siblings, 3 replies; 123+ messages in thread From: Denton Liu @ 2019-04-15 22:29 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Before, when we had the following graph, A---B---C (master) \ D (side) running 'git rebase --onto master... master side' would result in D being always rebased, no matter what. However, the desired behavior is that rebase should notice that this is fast-forwardable and do that instead. Add detection to `can_fast_forward` so that this case can be detected and a fast-forward will be performed. First of all, rewrite the function to use gotos which simplifies the logic. Next, since the options.upstream && !oidcmp(&options.upstream->object.oid, &options.onto->object.oid) conditions were removed in `cmd_rebase`, we reintroduce a substitute in `can_fast_forward`. In particular, checking the merge bases of `upstream` and `head` fixes a failing case in t3416. The abbreviated graph for t3416 is as follows: F---G topic / A---B---C---D---E master and the failing command was git rebase --onto master...topic F topic Before, Git would see that there was one merge base (C), and the merge and onto were the same so it would incorrectly return 1, indicating that we could fast-forward. This would cause the rebased graph to be 'ABCFG' when we were expecting 'ABCG'. With the additional logic, we detect that upstream and head's merge base is F. Since onto isn't F, it means we're not rebasing the full set of commits from master..topic. Since we're excluding some commits, a fast-forward cannot be performed and so we correctly return 0. Add '-f' to test cases that failed as a result of this change because they were not expecting a fast-forward so that a rebase is forced. While we're at it, remove a trailing whitespace from rebase.c. Signed-off-by: Denton Liu <liu.denton@gmail.com> --- builtin/rebase.c | 40 +++++++++++++++++++++++----------- t/t3400-rebase.sh | 2 +- t/t3404-rebase-interactive.sh | 2 +- t/t3432-rebase-fast-forward.sh | 4 ++-- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 77deebc65c..7aa6a090d4 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -895,12 +895,12 @@ static int is_linear_history(struct commit *from, struct commit *to) return 1; } -static int can_fast_forward(struct commit *onto, struct object_id *head_oid, - struct object_id *merge_base) +static int can_fast_forward(struct commit *onto, struct commit *upstream, + struct object_id *head_oid, struct object_id *merge_base) { struct commit *head = lookup_commit(the_repository, head_oid); - struct commit_list *merge_bases; - int res; + struct commit_list *merge_bases = NULL; + int res = 0; if (!head) return 0; @@ -908,12 +908,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, merge_bases = get_merge_bases(onto, head); if (merge_bases && !merge_bases->next) { oidcpy(merge_base, &merge_bases->item->object.oid); - res = oideq(merge_base, &onto->object.oid); + if (!oideq(merge_base, &onto->object.oid)) + goto done; } else { oidcpy(merge_base, &null_oid); - res = 0; + goto done; } + + if (!upstream) + goto done; + free_commit_list(merge_bases); + merge_bases = get_merge_bases(upstream, head); + if (merge_bases && !merge_bases->next) { + if (!oideq(&onto->object.oid, &merge_bases->item->object.oid)) + goto done; + } else + goto done; + + res = 1; + +done: + if (merge_bases) + free_commit_list(merge_bases); return res && is_linear_history(onto, head); } @@ -1682,13 +1699,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) /* * Check if we are already based on onto with linear history, - * but this should be done only when upstream and onto are the same - * and if this is not an interactive rebase. + * but this should be done if this is not an interactive rebase. */ - if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && - !is_interactive(&options) && !options.restrict_revision && - options.upstream && - !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { + if (can_fast_forward(options.onto, options.upstream, &options.orig_head, &merge_base) && + !is_interactive(&options) && !options.restrict_revision) { int flag; if (!(options.flags & REBASE_FORCE)) { @@ -1782,7 +1796,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&msg, "%s: checkout %s", getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name); if (reset_head(&options.onto->object.oid, "checkout", NULL, - RESET_HEAD_DETACH | RESET_ORIG_HEAD | + RESET_HEAD_DETACH | RESET_ORIG_HEAD | RESET_HEAD_RUN_POST_CHECKOUT_HOOK, NULL, msg.buf)) die(_("Could not detach HEAD")); diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 460d0523be..604d624ff8 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' ' echo two >>init.t && git commit -a -m two && git tag two && - test_must_fail git rebase --onto init HEAD^ && + test_must_fail git rebase -f --onto init HEAD^ && GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && grep "show.*$(git rev-parse two)" stderr ) diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index b60b11f9f2..f054186cc7 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int git reset --hard && git checkout conflict-branch && set_fake_editor && - test_must_fail git rebase --onto HEAD~2 HEAD~ && + test_must_fail git rebase -f --onto HEAD~2 HEAD~ && test_must_fail git rebase --edit-todo && git rebase --abort ' diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index 4f04d67fd7..d0e5b1f3e6 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -64,9 +64,9 @@ test_expect_success 'add work to upstream' ' changes='our and their changes' test_rebase_same_head success --onto B B test_rebase_same_head success --onto B... B -test_rebase_same_head failure --onto master... master +test_rebase_same_head success --onto master... master test_rebase_same_head failure --fork-point --onto B B test_rebase_same_head failure --fork-point --onto B... B -test_rebase_same_head failure --fork-point --onto master... master +test_rebase_same_head success --fork-point --onto master... master test_done -- 2.21.0.921.gb27c68c4e9 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* Re: [PATCH v5 3/5] rebase: fast-forward --onto in more cases 2019-04-15 22:29 ` [PATCH v5 3/5] rebase: fast-forward --onto in more cases Denton Liu @ 2019-04-16 6:26 ` Junio C Hamano 2019-04-16 13:59 ` Phillip Wood 2019-04-19 17:08 ` Denton Liu 2 siblings, 0 replies; 123+ messages in thread From: Junio C Hamano @ 2019-04-16 6:26 UTC (permalink / raw) To: Denton Liu Cc: Git Mailing List, Eric Sunshine, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Denton Liu <liu.denton@gmail.com> writes: > Before, when we had the following graph, > > A---B---C (master) > \ > D (side) This is minor, but comparing the above with below > F---G topic > / > A---B---C---D---E master you'll notice that branches growing downwards in your picture (this applies also to an illustration in your tests) are off by one column. D / A---B---C \ E > @@ -1682,13 +1699,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > > /* > * Check if we are already based on onto with linear history, > - * but this should be done only when upstream and onto are the same > - * and if this is not an interactive rebase. > + * but this should be done if this is not an interactive rebase. > */ Two issues. - One is shared with the original (i.e. not a problem with this patch), but what "this" refers to is not what has already been stated in the sentence. We check, to see if we are in a situation where a specific optimization is possible. But this (== optimization to fast-forward without actually replaying the commit's changes) should be done only under such and such condition. - The other is more grave. "should be done if this is not an interactive rebase" drops "only" from "only if" in the original, which changes the meaning of the sentence (it can be read as "we check if we can optimize, but the optimization should be done for rebase-i regardless of the result of that said check", which is not what you mean). Perhaps Check if we are ..., 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. ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH v5 3/5] rebase: fast-forward --onto in more cases 2019-04-15 22:29 ` [PATCH v5 3/5] rebase: fast-forward --onto in more cases Denton Liu 2019-04-16 6:26 ` Junio C Hamano @ 2019-04-16 13:59 ` Phillip Wood 2019-04-17 6:44 ` Denton Liu 2019-04-19 17:08 ` Denton Liu 2 siblings, 1 reply; 123+ messages in thread From: Phillip Wood @ 2019-04-16 13:59 UTC (permalink / raw) To: Denton Liu, Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Hi Denton It's good to see rebase fast-forwarding properly when it should On 15/04/2019 23:29, Denton Liu wrote: > Before, when we had the following graph, > > A---B---C (master) > \ > D (side) > > running 'git rebase --onto master... master side' would result in D > being always rebased, no matter what. However, the desired behavior is > that rebase should notice that this is fast-forwardable and do that > instead. > > Add detection to `can_fast_forward` so that this case can be detected > and a fast-forward will be performed. First of all, rewrite the function > to use gotos which simplifies the logic. Next, since the > > options.upstream && > !oidcmp(&options.upstream->object.oid, &options.onto->object.oid) > > conditions were removed in `cmd_rebase`, we reintroduce a substitute in > `can_fast_forward`. In particular, checking the merge bases of > `upstream` and `head` fixes a failing case in t3416. > > The abbreviated graph for t3416 is as follows: > > F---G topic > / > A---B---C---D---E master > > and the failing command was > > git rebase --onto master...topic F topic > > Before, Git would see that there was one merge base (C), and the merge > and onto were the same so it would incorrectly return 1, indicating that > we could fast-forward. This would cause the rebased graph to be 'ABCFG' > when we were expecting 'ABCG'. > > With the additional logic, we detect that upstream and head's merge base > is F. Since onto isn't F, it means we're not rebasing the full set of > commits from master..topic. Since we're excluding some commits, a > fast-forward cannot be performed and so we correctly return 0. > > Add '-f' to test cases that failed as a result of this change because > they were not expecting a fast-forward so that a rebase is forced. > > While we're at it, remove a trailing whitespace from rebase.c. > > Signed-off-by: Denton Liu <liu.denton@gmail.com> > --- > builtin/rebase.c | 40 +++++++++++++++++++++++----------- > t/t3400-rebase.sh | 2 +- > t/t3404-rebase-interactive.sh | 2 +- > t/t3432-rebase-fast-forward.sh | 4 ++-- > 4 files changed, 31 insertions(+), 17 deletions(-) > > diff --git a/builtin/rebase.c b/builtin/rebase.c > index 77deebc65c..7aa6a090d4 100644 > --- a/builtin/rebase.c > +++ b/builtin/rebase.c > @@ -895,12 +895,12 @@ static int is_linear_history(struct commit *from, struct commit *to) > return 1; > } > > -static int can_fast_forward(struct commit *onto, struct object_id *head_oid, > - struct object_id *merge_base) > +static int can_fast_forward(struct commit *onto, struct commit *upstream, > + struct object_id *head_oid, struct object_id *merge_base) > { > struct commit *head = lookup_commit(the_repository, head_oid); > - struct commit_list *merge_bases; > - int res; > + struct commit_list *merge_bases = NULL; > + int res = 0; > > if (!head) > return 0; > @@ -908,12 +908,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, > merge_bases = get_merge_bases(onto, head); > if (merge_bases && !merge_bases->next) { > oidcpy(merge_base, &merge_bases->item->object.oid); > - res = oideq(merge_base, &onto->object.oid); > + if (!oideq(merge_base, &onto->object.oid)) > + goto done; > } else { > oidcpy(merge_base, &null_oid); > - res = 0; > + goto done; > } > + > + if (!upstream) > + goto done; > + > free_commit_list(merge_bases); > + merge_bases = get_merge_bases(upstream, head); > + if (merge_bases && !merge_bases->next) { > + if (!oideq(&onto->object.oid, &merge_bases->item->object.oid)) > + goto done; > + } else > + goto done; > + > + res = 1; > + > +done: > + if (merge_bases) > + free_commit_list(merge_bases); > return res && is_linear_history(onto, head); > } > I had a hard time following all those gotos. When you 'goto done' in both branches of an if statement it is hard to work out which cases fall through to the rest of the code. If I've understood it correctly I think it is clearer as merge_bases = get_merge_bases(onto, head); if (merge_bases && !merge_bases->next) { oidcpy(merge_base, &merge_bases->item->object.oid); if (oideq(merge_base, &onto->object.oid) && upstream) { free_commit_list(merge_bases); merge_bases = get_merge_bases(upstream, head); if (merge_bases && !merge_bases->next) if (oideq(&onto->object.oid, &merge_bases->item->object.oid)) res = 1; } } else { oidcpy(merge_base, &null_oid); } if (merge_bases) free_commit_list(merge_bases); return res && is_linear_history(onto, head); } The nested if's aren't great but I think it is easier to follow > @@ -1682,13 +1699,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > > /* > * Check if we are already based on onto with linear history, > - * but this should be done only when upstream and onto are the same > - * and if this is not an interactive rebase. > + * but this should be done if this is not an interactive rebase. > */ > - if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && > - !is_interactive(&options) && !options.restrict_revision && > - options.upstream && > - !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { > + if (can_fast_forward(options.onto, options.upstream, &options.orig_head, &merge_base) && This is rather long, perhaps break the argument list Best Wishes Phillip > + !is_interactive(&options) && !options.restrict_revision) { > int flag; > > if (!(options.flags & REBASE_FORCE)) { > @@ -1782,7 +1796,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > strbuf_addf(&msg, "%s: checkout %s", > getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name); > if (reset_head(&options.onto->object.oid, "checkout", NULL, > - RESET_HEAD_DETACH | RESET_ORIG_HEAD | > + RESET_HEAD_DETACH | RESET_ORIG_HEAD | > RESET_HEAD_RUN_POST_CHECKOUT_HOOK, > NULL, msg.buf)) > die(_("Could not detach HEAD")); > diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh > index 460d0523be..604d624ff8 100755 > --- a/t/t3400-rebase.sh > +++ b/t/t3400-rebase.sh > @@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' ' > echo two >>init.t && > git commit -a -m two && > git tag two && > - test_must_fail git rebase --onto init HEAD^ && > + test_must_fail git rebase -f --onto init HEAD^ && > GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && > grep "show.*$(git rev-parse two)" stderr > ) > diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh > index b60b11f9f2..f054186cc7 100755 > --- a/t/t3404-rebase-interactive.sh > +++ b/t/t3404-rebase-interactive.sh > @@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int > git reset --hard && > git checkout conflict-branch && > set_fake_editor && > - test_must_fail git rebase --onto HEAD~2 HEAD~ && > + test_must_fail git rebase -f --onto HEAD~2 HEAD~ && > test_must_fail git rebase --edit-todo && > git rebase --abort > ' > diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh > index 4f04d67fd7..d0e5b1f3e6 100755 > --- a/t/t3432-rebase-fast-forward.sh > +++ b/t/t3432-rebase-fast-forward.sh > @@ -64,9 +64,9 @@ test_expect_success 'add work to upstream' ' > changes='our and their changes' > test_rebase_same_head success --onto B B > test_rebase_same_head success --onto B... B > -test_rebase_same_head failure --onto master... master > +test_rebase_same_head success --onto master... master > test_rebase_same_head failure --fork-point --onto B B > test_rebase_same_head failure --fork-point --onto B... B > -test_rebase_same_head failure --fork-point --onto master... master > +test_rebase_same_head success --fork-point --onto master... master > > test_done > ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH v5 3/5] rebase: fast-forward --onto in more cases 2019-04-16 13:59 ` Phillip Wood @ 2019-04-17 6:44 ` Denton Liu 2019-04-17 14:14 ` Phillip Wood 0 siblings, 1 reply; 123+ messages in thread From: Denton Liu @ 2019-04-17 6:44 UTC (permalink / raw) To: phillip.wood Cc: Git Mailing List, Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor On Tue, Apr 16, 2019 at 02:59:12PM +0100, Phillip Wood wrote: > Hi Denton > > It's good to see rebase fast-forwarding properly when it should > > On 15/04/2019 23:29, Denton Liu wrote: > > Before, when we had the following graph, > > > > A---B---C (master) > > \ > > D (side) > > > > running 'git rebase --onto master... master side' would result in D > > being always rebased, no matter what. However, the desired behavior is > > that rebase should notice that this is fast-forwardable and do that > > instead. > > > > Add detection to `can_fast_forward` so that this case can be detected > > and a fast-forward will be performed. First of all, rewrite the function > > to use gotos which simplifies the logic. Next, since the > > > > options.upstream && > > !oidcmp(&options.upstream->object.oid, &options.onto->object.oid) > > > > conditions were removed in `cmd_rebase`, we reintroduce a substitute in > > `can_fast_forward`. In particular, checking the merge bases of > > `upstream` and `head` fixes a failing case in t3416. > > > > The abbreviated graph for t3416 is as follows: > > > > F---G topic > > / > > A---B---C---D---E master > > > > and the failing command was > > > > git rebase --onto master...topic F topic > > > > Before, Git would see that there was one merge base (C), and the merge > > and onto were the same so it would incorrectly return 1, indicating that > > we could fast-forward. This would cause the rebased graph to be 'ABCFG' > > when we were expecting 'ABCG'. > > > > With the additional logic, we detect that upstream and head's merge base > > is F. Since onto isn't F, it means we're not rebasing the full set of > > commits from master..topic. Since we're excluding some commits, a > > fast-forward cannot be performed and so we correctly return 0. > > > > Add '-f' to test cases that failed as a result of this change because > > they were not expecting a fast-forward so that a rebase is forced. > > > > While we're at it, remove a trailing whitespace from rebase.c. > > > > Signed-off-by: Denton Liu <liu.denton@gmail.com> > > --- > > builtin/rebase.c | 40 +++++++++++++++++++++++----------- > > t/t3400-rebase.sh | 2 +- > > t/t3404-rebase-interactive.sh | 2 +- > > t/t3432-rebase-fast-forward.sh | 4 ++-- > > 4 files changed, 31 insertions(+), 17 deletions(-) > > > > diff --git a/builtin/rebase.c b/builtin/rebase.c > > index 77deebc65c..7aa6a090d4 100644 > > --- a/builtin/rebase.c > > +++ b/builtin/rebase.c > > @@ -895,12 +895,12 @@ static int is_linear_history(struct commit *from, struct commit *to) > > return 1; > > } > > -static int can_fast_forward(struct commit *onto, struct object_id *head_oid, > > - struct object_id *merge_base) > > +static int can_fast_forward(struct commit *onto, struct commit *upstream, > > + struct object_id *head_oid, struct object_id *merge_base) > > { > > struct commit *head = lookup_commit(the_repository, head_oid); > > - struct commit_list *merge_bases; > > - int res; > > + struct commit_list *merge_bases = NULL; > > + int res = 0; > > if (!head) > > return 0; > > @@ -908,12 +908,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, > > merge_bases = get_merge_bases(onto, head); > > if (merge_bases && !merge_bases->next) { > > oidcpy(merge_base, &merge_bases->item->object.oid); > > - res = oideq(merge_base, &onto->object.oid); > > + if (!oideq(merge_base, &onto->object.oid)) > > + goto done; > > } else { > > oidcpy(merge_base, &null_oid); > > - res = 0; > > + goto done; > > } > > + > > + if (!upstream) > > + goto done; > > + > > free_commit_list(merge_bases); > > + merge_bases = get_merge_bases(upstream, head); > > + if (merge_bases && !merge_bases->next) { > > + if (!oideq(&onto->object.oid, &merge_bases->item->object.oid)) > > + goto done; > > + } else > > + goto done; > > + > > + res = 1; > > + > > +done: > > + if (merge_bases) > > + free_commit_list(merge_bases); > > return res && is_linear_history(onto, head); > > } > > I had a hard time following all those gotos. When you 'goto done' in both > branches of an if statement it is hard to work out which cases fall through > to the rest of the code. If I've understood it correctly I think it is > clearer as > > merge_bases = get_merge_bases(onto, head); > if (merge_bases && !merge_bases->next) { > oidcpy(merge_base, &merge_bases->item->object.oid); > if (oideq(merge_base, &onto->object.oid) && upstream) { > free_commit_list(merge_bases); > merge_bases = get_merge_bases(upstream, head); > if (merge_bases && !merge_bases->next) > if (oideq(&onto->object.oid, > &merge_bases->item->object.oid)) > res = 1; > } > } else { > oidcpy(merge_base, &null_oid); > } > > if (merge_bases) > free_commit_list(merge_bases); > return res && is_linear_history(onto, head); > } > > The nested if's aren't great but I think it is easier to follow I am pretty impartial between gotos and ifs. If no one else has any strong opinions between the two, I'll reroll with ifs. > > > @@ -1682,13 +1699,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > > /* > > * Check if we are already based on onto with linear history, > > - * but this should be done only when upstream and onto are the same > > - * and if this is not an interactive rebase. > > + * but this should be done if this is not an interactive rebase. > > */ > > - if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && > > - !is_interactive(&options) && !options.restrict_revision && > > - options.upstream && > > - !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { > > + if (can_fast_forward(options.onto, options.upstream, &options.orig_head, &merge_base) && > > This is rather long, perhaps break the argument list Thanks, will do. > > Best Wishes > > Phillip > > + !is_interactive(&options) && !options.restrict_revision) { > > int flag; > > if (!(options.flags & REBASE_FORCE)) { > > @@ -1782,7 +1796,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > > strbuf_addf(&msg, "%s: checkout %s", > > getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name); > > if (reset_head(&options.onto->object.oid, "checkout", NULL, > > - RESET_HEAD_DETACH | RESET_ORIG_HEAD | > > + RESET_HEAD_DETACH | RESET_ORIG_HEAD | > > RESET_HEAD_RUN_POST_CHECKOUT_HOOK, > > NULL, msg.buf)) > > die(_("Could not detach HEAD")); > > diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh > > index 460d0523be..604d624ff8 100755 > > --- a/t/t3400-rebase.sh > > +++ b/t/t3400-rebase.sh > > @@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' ' > > echo two >>init.t && > > git commit -a -m two && > > git tag two && > > - test_must_fail git rebase --onto init HEAD^ && > > + test_must_fail git rebase -f --onto init HEAD^ && > > GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && > > grep "show.*$(git rev-parse two)" stderr > > ) > > diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh > > index b60b11f9f2..f054186cc7 100755 > > --- a/t/t3404-rebase-interactive.sh > > +++ b/t/t3404-rebase-interactive.sh > > @@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int > > git reset --hard && > > git checkout conflict-branch && > > set_fake_editor && > > - test_must_fail git rebase --onto HEAD~2 HEAD~ && > > + test_must_fail git rebase -f --onto HEAD~2 HEAD~ && > > test_must_fail git rebase --edit-todo && > > git rebase --abort > > ' > > diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh > > index 4f04d67fd7..d0e5b1f3e6 100755 > > --- a/t/t3432-rebase-fast-forward.sh > > +++ b/t/t3432-rebase-fast-forward.sh > > @@ -64,9 +64,9 @@ test_expect_success 'add work to upstream' ' > > changes='our and their changes' > > test_rebase_same_head success --onto B B > > test_rebase_same_head success --onto B... B > > -test_rebase_same_head failure --onto master... master > > +test_rebase_same_head success --onto master... master > > test_rebase_same_head failure --fork-point --onto B B > > test_rebase_same_head failure --fork-point --onto B... B > > -test_rebase_same_head failure --fork-point --onto master... master > > +test_rebase_same_head success --fork-point --onto master... master > > test_done > > ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH v5 3/5] rebase: fast-forward --onto in more cases 2019-04-17 6:44 ` Denton Liu @ 2019-04-17 14:14 ` Phillip Wood 0 siblings, 0 replies; 123+ messages in thread From: Phillip Wood @ 2019-04-17 14:14 UTC (permalink / raw) To: Denton Liu, phillip.wood Cc: Git Mailing List, Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor On 17/04/2019 07:44, Denton Liu wrote: > On Tue, Apr 16, 2019 at 02:59:12PM +0100, Phillip Wood wrote: >> Hi Denton >> >> It's good to see rebase fast-forwarding properly when it should >> >> On 15/04/2019 23:29, Denton Liu wrote: >>> Before, when we had the following graph, >>> >>> A---B---C (master) >>> \ >>> D (side) >>> >>> running 'git rebase --onto master... master side' would result in D >>> being always rebased, no matter what. However, the desired behavior is >>> that rebase should notice that this is fast-forwardable and do that >>> instead. >>> >>> Add detection to `can_fast_forward` so that this case can be detected >>> and a fast-forward will be performed. First of all, rewrite the function >>> to use gotos which simplifies the logic. Next, since the >>> >>> options.upstream && >>> !oidcmp(&options.upstream->object.oid, &options.onto->object.oid) >>> >>> conditions were removed in `cmd_rebase`, we reintroduce a substitute in >>> `can_fast_forward`. In particular, checking the merge bases of >>> `upstream` and `head` fixes a failing case in t3416. >>> >>> The abbreviated graph for t3416 is as follows: >>> >>> F---G topic >>> / >>> A---B---C---D---E master >>> >>> and the failing command was >>> >>> git rebase --onto master...topic F topic >>> >>> Before, Git would see that there was one merge base (C), and the merge >>> and onto were the same so it would incorrectly return 1, indicating that >>> we could fast-forward. This would cause the rebased graph to be 'ABCFG' >>> when we were expecting 'ABCG'. >>> >>> With the additional logic, we detect that upstream and head's merge base >>> is F. Since onto isn't F, it means we're not rebasing the full set of >>> commits from master..topic. Since we're excluding some commits, a >>> fast-forward cannot be performed and so we correctly return 0. >>> >>> Add '-f' to test cases that failed as a result of this change because >>> they were not expecting a fast-forward so that a rebase is forced. >>> >>> While we're at it, remove a trailing whitespace from rebase.c. >>> >>> Signed-off-by: Denton Liu <liu.denton@gmail.com> >>> --- >>> builtin/rebase.c | 40 +++++++++++++++++++++++----------- >>> t/t3400-rebase.sh | 2 +- >>> t/t3404-rebase-interactive.sh | 2 +- >>> t/t3432-rebase-fast-forward.sh | 4 ++-- >>> 4 files changed, 31 insertions(+), 17 deletions(-) >>> >>> diff --git a/builtin/rebase.c b/builtin/rebase.c >>> index 77deebc65c..7aa6a090d4 100644 >>> --- a/builtin/rebase.c >>> +++ b/builtin/rebase.c >>> @@ -895,12 +895,12 @@ static int is_linear_history(struct commit *from, struct commit *to) >>> return 1; >>> } >>> -static int can_fast_forward(struct commit *onto, struct object_id *head_oid, >>> - struct object_id *merge_base) >>> +static int can_fast_forward(struct commit *onto, struct commit *upstream, >>> + struct object_id *head_oid, struct object_id *merge_base) >>> { >>> struct commit *head = lookup_commit(the_repository, head_oid); >>> - struct commit_list *merge_bases; >>> - int res; >>> + struct commit_list *merge_bases = NULL; >>> + int res = 0; >>> if (!head) >>> return 0; >>> @@ -908,12 +908,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, >>> merge_bases = get_merge_bases(onto, head); >>> if (merge_bases && !merge_bases->next) { >>> oidcpy(merge_base, &merge_bases->item->object.oid); >>> - res = oideq(merge_base, &onto->object.oid); >>> + if (!oideq(merge_base, &onto->object.oid)) >>> + goto done; >>> } else { >>> oidcpy(merge_base, &null_oid); >>> - res = 0; >>> + goto done; >>> } >>> + >>> + if (!upstream) >>> + goto done; >>> + >>> free_commit_list(merge_bases); >>> + merge_bases = get_merge_bases(upstream, head); >>> + if (merge_bases && !merge_bases->next) { >>> + if (!oideq(&onto->object.oid, &merge_bases->item->object.oid)) >>> + goto done; >>> + } else >>> + goto done; >>> + >>> + res = 1; >>> + >>> +done: >>> + if (merge_bases) >>> + free_commit_list(merge_bases); >>> return res && is_linear_history(onto, head); >>> } >> >> I had a hard time following all those gotos. When you 'goto done' in both >> branches of an if statement it is hard to work out which cases fall through >> to the rest of the code. If I've understood it correctly I think it is >> clearer as >> >> merge_bases = get_merge_bases(onto, head); >> if (merge_bases && !merge_bases->next) { >> oidcpy(merge_base, &merge_bases->item->object.oid); >> if (oideq(merge_base, &onto->object.oid) && upstream) { >> free_commit_list(merge_bases); >> merge_bases = get_merge_bases(upstream, head); >> if (merge_bases && !merge_bases->next) >> if (oideq(&onto->object.oid, >> &merge_bases->item->object.oid)) >> res = 1; that would be better as res = oideq(&onto->object.oid, &merge_bases->item->object.oid); without the last if >> } >> } else { >> oidcpy(merge_base, &null_oid); >> } >> >> if (merge_bases) >> free_commit_list(merge_bases); >> return res && is_linear_history(onto, head); >> } >> >> The nested if's aren't great but I think it is easier to follow > > I am pretty impartial between gotos and ifs. If no one else has any > strong opinions between the two, I'll reroll with ifs. If you want to keep the goto approach then refactoring the ifs as follows is clearer as it avoids jumping from both arms, each stage is a simple single armed if (something) goto done merge_bases = get_merge_bases(onto, head); if (!merge_bases || merge_bases->next) { oidcpy(merge_base, &null_oid); goto done; } oidcpy(merge_base, &merge_bases->item->object.oid); if (!oideq(merge_base, &onto->object.oid)) goto done; if (!upstream) goto done; free_commit_list(merge_bases); merge_bases = get_merge_bases(upstream, head); if (merge_bases && !merge_bases->next) res = oideq(&onto->object.oid, &merge_bases->item->object.oid); done: if (merge_bases) free_commit_list(merge_bases); return res && is_linear_history(onto, head); } Best Wishes Phillip >> >>> @@ -1682,13 +1699,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) >>> /* >>> * Check if we are already based on onto with linear history, >>> - * but this should be done only when upstream and onto are the same >>> - * and if this is not an interactive rebase. >>> + * but this should be done if this is not an interactive rebase. >>> */ >>> - if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && >>> - !is_interactive(&options) && !options.restrict_revision && >>> - options.upstream && >>> - !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { >>> + if (can_fast_forward(options.onto, options.upstream, &options.orig_head, &merge_base) && >> >> This is rather long, perhaps break the argument list > > Thanks, will do. > >> >> Best Wishes >> >> Phillip >>> + !is_interactive(&options) && !options.restrict_revision) { >>> int flag; >>> if (!(options.flags & REBASE_FORCE)) { >>> @@ -1782,7 +1796,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) >>> strbuf_addf(&msg, "%s: checkout %s", >>> getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name); >>> if (reset_head(&options.onto->object.oid, "checkout", NULL, >>> - RESET_HEAD_DETACH | RESET_ORIG_HEAD | >>> + RESET_HEAD_DETACH | RESET_ORIG_HEAD | >>> RESET_HEAD_RUN_POST_CHECKOUT_HOOK, >>> NULL, msg.buf)) >>> die(_("Could not detach HEAD")); >>> diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh >>> index 460d0523be..604d624ff8 100755 >>> --- a/t/t3400-rebase.sh >>> +++ b/t/t3400-rebase.sh >>> @@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' ' >>> echo two >>init.t && >>> git commit -a -m two && >>> git tag two && >>> - test_must_fail git rebase --onto init HEAD^ && >>> + test_must_fail git rebase -f --onto init HEAD^ && >>> GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && >>> grep "show.*$(git rev-parse two)" stderr >>> ) >>> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh >>> index b60b11f9f2..f054186cc7 100755 >>> --- a/t/t3404-rebase-interactive.sh >>> +++ b/t/t3404-rebase-interactive.sh >>> @@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int >>> git reset --hard && >>> git checkout conflict-branch && >>> set_fake_editor && >>> - test_must_fail git rebase --onto HEAD~2 HEAD~ && >>> + test_must_fail git rebase -f --onto HEAD~2 HEAD~ && >>> test_must_fail git rebase --edit-todo && >>> git rebase --abort >>> ' >>> diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh >>> index 4f04d67fd7..d0e5b1f3e6 100755 >>> --- a/t/t3432-rebase-fast-forward.sh >>> +++ b/t/t3432-rebase-fast-forward.sh >>> @@ -64,9 +64,9 @@ test_expect_success 'add work to upstream' ' >>> changes='our and their changes' >>> test_rebase_same_head success --onto B B >>> test_rebase_same_head success --onto B... B >>> -test_rebase_same_head failure --onto master... master >>> +test_rebase_same_head success --onto master... master >>> test_rebase_same_head failure --fork-point --onto B B >>> test_rebase_same_head failure --fork-point --onto B... B >>> -test_rebase_same_head failure --fork-point --onto master... master >>> +test_rebase_same_head success --fork-point --onto master... master >>> test_done >>> ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH v5 3/5] rebase: fast-forward --onto in more cases 2019-04-15 22:29 ` [PATCH v5 3/5] rebase: fast-forward --onto in more cases Denton Liu 2019-04-16 6:26 ` Junio C Hamano 2019-04-16 13:59 ` Phillip Wood @ 2019-04-19 17:08 ` Denton Liu 2 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-19 17:08 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Hi Junio, On Mon, Apr 15, 2019 at 03:29:24PM -0700, Denton Liu wrote: > Before, when we had the following graph, > > A---B---C (master) > \ > D (side) > > running 'git rebase --onto master... master side' would result in D > being always rebased, no matter what. However, the desired behavior is > that rebase should notice that this is fast-forwardable and do that > instead. > > Add detection to `can_fast_forward` so that this case can be detected > and a fast-forward will be performed. First of all, rewrite the function > to use gotos which simplifies the logic. Next, since the > > options.upstream && > !oidcmp(&options.upstream->object.oid, &options.onto->object.oid) > > conditions were removed in `cmd_rebase`, we reintroduce a substitute in > `can_fast_forward`. In particular, checking the merge bases of > `upstream` and `head` fixes a failing case in t3416. > > The abbreviated graph for t3416 is as follows: > > F---G topic > / > A---B---C---D---E master > > and the failing command was > > git rebase --onto master...topic F topic > > Before, Git would see that there was one merge base (C), and the merge > and onto were the same so it would incorrectly return 1, indicating that > we could fast-forward. This would cause the rebased graph to be 'ABCFG' > when we were expecting 'ABCG'. > > With the additional logic, we detect that upstream and head's merge base > is F. Since onto isn't F, it means we're not rebasing the full set of > commits from master..topic. Since we're excluding some commits, a > fast-forward cannot be performed and so we correctly return 0. > > Add '-f' to test cases that failed as a result of this change because > they were not expecting a fast-forward so that a rebase is forced. > > While we're at it, remove a trailing whitespace from rebase.c. > > Signed-off-by: Denton Liu <liu.denton@gmail.com> > --- > builtin/rebase.c | 40 +++++++++++++++++++++++----------- > t/t3400-rebase.sh | 2 +- > t/t3404-rebase-interactive.sh | 2 +- > t/t3432-rebase-fast-forward.sh | 4 ++-- > 4 files changed, 31 insertions(+), 17 deletions(-) > > diff --git a/builtin/rebase.c b/builtin/rebase.c > index 77deebc65c..7aa6a090d4 100644 > --- a/builtin/rebase.c > +++ b/builtin/rebase.c > @@ -895,12 +895,12 @@ static int is_linear_history(struct commit *from, struct commit *to) > return 1; > } > > -static int can_fast_forward(struct commit *onto, struct object_id *head_oid, > - struct object_id *merge_base) > +static int can_fast_forward(struct commit *onto, struct commit *upstream, > + struct object_id *head_oid, struct object_id *merge_base) > { > struct commit *head = lookup_commit(the_repository, head_oid); > - struct commit_list *merge_bases; > - int res; > + struct commit_list *merge_bases = NULL; > + int res = 0; > > if (!head) > return 0; > @@ -908,12 +908,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, > merge_bases = get_merge_bases(onto, head); > if (merge_bases && !merge_bases->next) { > oidcpy(merge_base, &merge_bases->item->object.oid); > - res = oideq(merge_base, &onto->object.oid); > + if (!oideq(merge_base, &onto->object.oid)) > + goto done; > } else { > oidcpy(merge_base, &null_oid); > - res = 0; > + goto done; > } > + > + if (!upstream) > + goto done; > + > free_commit_list(merge_bases); > + merge_bases = get_merge_bases(upstream, head); > + if (merge_bases && !merge_bases->next) { > + if (!oideq(&onto->object.oid, &merge_bases->item->object.oid)) > + goto done; > + } else > + goto done; > + > + res = 1; > + > +done: > + if (merge_bases) > + free_commit_list(merge_bases); > return res && is_linear_history(onto, head); > } > > @@ -1682,13 +1699,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > > /* > * Check if we are already based on onto with linear history, > - * but this should be done only when upstream and onto are the same > - * and if this is not an interactive rebase. > + * but this should be done if this is not an interactive rebase. > */ I forgot to incorporate your comment about this comment block in the last reroll. I'm not sure if this is worth another reroll so could you please change the comment block to this: /* * 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. */ Thanks, Denton > - if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && > - !is_interactive(&options) && !options.restrict_revision && > - options.upstream && > - !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { > + if (can_fast_forward(options.onto, options.upstream, &options.orig_head, &merge_base) && > + !is_interactive(&options) && !options.restrict_revision) { > int flag; > > if (!(options.flags & REBASE_FORCE)) { > @@ -1782,7 +1796,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > strbuf_addf(&msg, "%s: checkout %s", > getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name); > if (reset_head(&options.onto->object.oid, "checkout", NULL, > - RESET_HEAD_DETACH | RESET_ORIG_HEAD | > + RESET_HEAD_DETACH | RESET_ORIG_HEAD | > RESET_HEAD_RUN_POST_CHECKOUT_HOOK, > NULL, msg.buf)) > die(_("Could not detach HEAD")); > diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh > index 460d0523be..604d624ff8 100755 > --- a/t/t3400-rebase.sh > +++ b/t/t3400-rebase.sh > @@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' ' > echo two >>init.t && > git commit -a -m two && > git tag two && > - test_must_fail git rebase --onto init HEAD^ && > + test_must_fail git rebase -f --onto init HEAD^ && > GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && > grep "show.*$(git rev-parse two)" stderr > ) > diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh > index b60b11f9f2..f054186cc7 100755 > --- a/t/t3404-rebase-interactive.sh > +++ b/t/t3404-rebase-interactive.sh > @@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int > git reset --hard && > git checkout conflict-branch && > set_fake_editor && > - test_must_fail git rebase --onto HEAD~2 HEAD~ && > + test_must_fail git rebase -f --onto HEAD~2 HEAD~ && > test_must_fail git rebase --edit-todo && > git rebase --abort > ' > diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh > index 4f04d67fd7..d0e5b1f3e6 100755 > --- a/t/t3432-rebase-fast-forward.sh > +++ b/t/t3432-rebase-fast-forward.sh > @@ -64,9 +64,9 @@ test_expect_success 'add work to upstream' ' > changes='our and their changes' > test_rebase_same_head success --onto B B > test_rebase_same_head success --onto B... B > -test_rebase_same_head failure --onto master... master > +test_rebase_same_head success --onto master... master > test_rebase_same_head failure --fork-point --onto B B > test_rebase_same_head failure --fork-point --onto B... B > -test_rebase_same_head failure --fork-point --onto master... master > +test_rebase_same_head success --fork-point --onto master... master > > test_done > -- > 2.21.0.921.gb27c68c4e9 > ^ permalink raw reply [flat|nested] 123+ messages in thread
* [PATCH v5 4/5] rebase: fast-forward --fork-point in more cases 2019-04-15 22:29 ` [PATCH v5 0/5] " Denton Liu ` (2 preceding siblings ...) 2019-04-15 22:29 ` [PATCH v5 3/5] rebase: fast-forward --onto in more cases Denton Liu @ 2019-04-15 22:29 ` Denton Liu 2019-04-15 22:29 ` [PATCH v5 5/5] rebase: teach rebase --keep-base Denton Liu 4 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-15 22:29 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Before, when we rebased with a --fork-point invocation where the fork-point wasn't empty, we would be setting options.restrict_revision. The fast-forward logic would automatically declare that the rebase was not fast-forwardable if it was set. However, this was painting with a very broad brush. Refine the logic so that we can fast-forward in the case where the restricted revision is equal to the merge base, since we stop rebasing at the merge base anyway. Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- builtin/rebase.c | 9 +++++++-- t/t3432-rebase-fast-forward.sh | 12 ++++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 7aa6a090d4..5f00d91b68 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -896,6 +896,7 @@ static int is_linear_history(struct commit *from, struct commit *to) } static int can_fast_forward(struct commit *onto, struct commit *upstream, + struct commit *restrict_revision, struct object_id *head_oid, struct object_id *merge_base) { struct commit *head = lookup_commit(the_repository, head_oid); @@ -915,6 +916,9 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream, goto done; } + if (restrict_revision && !oideq(&restrict_revision->object.oid, merge_base)) + goto done; + if (!upstream) goto done; @@ -1701,8 +1705,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) * Check if we are already based on onto with linear history, * but this should be done if this is not an interactive rebase. */ - if (can_fast_forward(options.onto, options.upstream, &options.orig_head, &merge_base) && - !is_interactive(&options) && !options.restrict_revision) { + if (can_fast_forward(options.onto, options.upstream, options.restrict_revision, + &options.orig_head, &merge_base) && + !is_interactive(&options)) { int flag; if (!(options.flags & REBASE_FORCE)) { diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index d0e5b1f3e6..1cb2896fb4 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -35,8 +35,8 @@ test_rebase_same_head success --onto B... B test_rebase_same_head success --onto master... master test_rebase_same_head success --no-fork-point test_rebase_same_head success --fork-point master -test_rebase_same_head failure --fork-point --onto B B -test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head success --fork-point --onto B B +test_rebase_same_head success --fork-point --onto B... B test_rebase_same_head success --fork-point --onto master... master test_expect_success 'add work to side' ' @@ -51,8 +51,8 @@ test_rebase_same_head success --onto B... B test_rebase_same_head success --onto master... master test_rebase_same_head success --no-fork-point test_rebase_same_head success --fork-point master -test_rebase_same_head failure --fork-point --onto B B -test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head success --fork-point --onto B B +test_rebase_same_head success --fork-point --onto B... B test_rebase_same_head success --fork-point --onto master... master test_expect_success 'add work to upstream' ' @@ -65,8 +65,8 @@ changes='our and their changes' test_rebase_same_head success --onto B B test_rebase_same_head success --onto B... B test_rebase_same_head success --onto master... master -test_rebase_same_head failure --fork-point --onto B B -test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head success --fork-point --onto B B +test_rebase_same_head success --fork-point --onto B... B test_rebase_same_head success --fork-point --onto master... master test_done -- 2.21.0.921.gb27c68c4e9 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v5 5/5] rebase: teach rebase --keep-base 2019-04-15 22:29 ` [PATCH v5 0/5] " Denton Liu ` (3 preceding siblings ...) 2019-04-15 22:29 ` [PATCH v5 4/5] rebase: fast-forward --fork-point " Denton Liu @ 2019-04-15 22:29 ` Denton Liu 4 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-15 22:29 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor A common scenario is if a user is working on a topic branch and they wish to make some changes to intermediate commits or autosquash, they would run something such as git rebase -i --onto master... master in order to preserve the merge base. This is useful when contributing a patch series to the Git mailing list, one often starts on top of the current 'master'. However, while developing the patches, 'master' is also developed further and it is sometimes not the bst idea to keep rebasing on top of 'master', but to keep the base commit as-is. Alternatively, a user wishing to test individual commits in a topic branch without changing anything may run git rebase -x ./test.sh master... master Since rebasing onto the merge base of the branch and the upstream is such a common case, introduce the --keep-base option as a shortcut. This allows us to rewrite the above as git rebase -i --keep-base master and git rebase -x ./test.sh --keep-base master respectively. Add tests to ensure --keep-base works correctly in the normal case and fails when there are multiple merge bases, both in regular and interactive mode. Also, test to make sure conflicting options cause rebase to fail. While we're adding test cases, add a missing set_fake_editor call to 'rebase -i --onto master...side'. While we're documenting the --keep-base option, change an instance of "merge-base" to "merge base", which is the consistent spelling. Helped-by: Eric Sunshine <sunshine@sunshineco.com> Helped-by: Junio C Hamano <gitster@pobox.com> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- Documentation/git-rebase.txt | 30 +++++++++++++++-- builtin/rebase.c | 32 ++++++++++++++---- t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++++++++ t/t3431-rebase-fork-point.sh | 4 +++ t/t3432-rebase-fast-forward.sh | 11 ++++++ 5 files changed, 125 insertions(+), 9 deletions(-) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 6363d674b7..569ab708d4 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip SYNOPSIS -------- [verse] -'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] - [<upstream> [<branch>]] +'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] + [--onto <newbase> | --keep-base] [<upstream> [<branch>]] 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] --root [<branch>] 'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch @@ -217,6 +217,24 @@ As a special case, you may use "A\...B" as a shortcut for the merge base of A and B if there is exactly one merge base. You can leave out at most one of A and B, in which case it defaults to HEAD. +--keep-base:: + Set the starting point at which to create the new commits to the + merge base of <upstream> <branch>. Running + 'git rebase --keep-base <upstream> <branch>' is equivalent to + running 'git rebase --onto <upstream>... <upstream>'. ++ +This option is useful in the case where one is developing a feature on +top of an upstream branch. While the feature is being worked on, the +upstream branch may advance and it may not be the best idea to keep +rebasing on top of the upstream but to keep the base commit as-is. ++ +Although both this option and --fork-point find the merge base between +<upstream> and <branch>, this option uses the merge base as the _starting +point_ on which new commits will be created, whereas --fork-point uses +the merge base to determine the _set of commits_ which will be rebased. ++ +See also INCOMPATIBLE OPTIONS below. + <upstream>:: Upstream branch to compare against. May be any valid commit, not just an existing branch name. Defaults to the configured @@ -364,6 +382,10 @@ ends up being empty, the <upstream> will be used as a fallback. + If either <upstream> or --root is given on the command line, then the default is `--no-fork-point`, otherwise the default is `--fork-point`. ++ +If your branch was based on <upstream> but <upstream> was rewound and +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:: --whitespace=<option>:: @@ -539,6 +561,8 @@ In addition, the following pairs of options are incompatible: * --preserve-merges and --rebase-merges * --rebase-merges and --strategy * --rebase-merges and --strategy-option + * --keep-base and --onto + * --keep-base and --root BEHAVIORAL DIFFERENCES ----------------------- @@ -863,7 +887,7 @@ NOTE: While an "easy case recovery" sometimes appears to be successful --interactive` will be **resurrected**! The idea is to manually tell 'git rebase' "where the old 'subsystem' -ended and your 'topic' began", that is, what the old merge-base +ended and your 'topic' began", that is, what the old merge base between them was. You will have to find a way to name the last commit of the old 'subsystem', for example: diff --git a/builtin/rebase.c b/builtin/rebase.c index 5f00d91b68..289763651e 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -27,8 +27,8 @@ #include "branch.h" static char const * const builtin_rebase_usage[] = { - N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " - "[<upstream>] [<branch>]"), + N_("git rebase [-i] [options] [--exec <cmd>] " + "[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"), N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " "--root [<branch>]"), N_("git rebase --continue | --abort | --skip | --edit-todo"), @@ -1039,6 +1039,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) }; const char *branch_name; int ret, flags, total_argc, in_progress = 0; + int keep_base = 0; int ok_to_skip_pre_rebase = 0; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; @@ -1072,6 +1073,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_STRING(0, "onto", &options.onto_name, N_("revision"), N_("rebase onto given branch instead of upstream")), + OPT_BOOL(0, "keep-base", &keep_base, + N_("use the merge-base of upstream and branch as the current base")), OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase, N_("allow pre-rebase hook to run")), OPT_NEGBIT('q', "quiet", &options.flags, @@ -1238,6 +1241,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) usage_with_options(builtin_rebase_usage, builtin_rebase_options); + if (keep_base) { + if (options.onto_name) + die(_("cannot combine '--keep-base' with '--onto'")); + if (options.root) + die(_("cannot combine '--keep-base' with '--root'")); + } + if (action != NO_ACTION && !in_progress) die(_("No rebase in progress?")); setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0); @@ -1562,12 +1572,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } /* Make sure the branch to rebase onto is valid. */ - if (!options.onto_name) + if (keep_base) { + strbuf_reset(&buf); + strbuf_addstr(&buf, options.upstream_name); + strbuf_addstr(&buf, "..."); + options.onto_name = xstrdup(buf.buf); + } else if (!options.onto_name) options.onto_name = options.upstream_name; if (strstr(options.onto_name, "...")) { - if (get_oid_mb(options.onto_name, &merge_base) < 0) - die(_("'%s': need exactly one merge base"), - options.onto_name); + if (get_oid_mb(options.onto_name, &merge_base) < 0) { + if (keep_base) + die(_("'%s': need exactly one merge base with branch"), + options.upstream_name); + else + die(_("'%s': need exactly one merge base"), + options.onto_name); + } options.onto = lookup_commit_or_die(&merge_base, options.onto_name); } else { diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh index ddf2f64853..9c2548423b 100755 --- a/t/t3416-rebase-onto-threedots.sh +++ b/t/t3416-rebase-onto-threedots.sh @@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' ' git checkout side && git reset --hard K && + set_fake_editor && test_must_fail git rebase -i --onto master...side J ' +test_expect_success 'rebase --keep-base --onto incompatible' ' + test_must_fail git rebase --keep-base --onto master... +' + +test_expect_success 'rebase --keep-base --root incompatible' ' + test_must_fail git rebase --keep-base --root +' + +test_expect_success 'rebase --keep-base master from topic' ' + git reset --hard && + git checkout topic && + git reset --hard G && + + git rebase --keep-base master && + git rev-parse C >base.expect && + git merge-base master HEAD >base.actual && + test_cmp base.expect base.actual && + + git rev-parse HEAD~2 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase --keep-base master from side' ' + git reset --hard && + git checkout side && + git reset --hard K && + + test_must_fail git rebase --keep-base master +' + +test_expect_success 'rebase -i --keep-base master from topic' ' + git reset --hard && + git checkout topic && + git reset --hard G && + + set_fake_editor && + EXPECT_COUNT=2 git rebase -i --keep-base master && + git rev-parse C >base.expect && + git merge-base master HEAD >base.actual && + test_cmp base.expect base.actual && + + git rev-parse HEAD~2 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase -i --keep-base master from side' ' + git reset --hard && + git checkout side && + git reset --hard K && + + set_fake_editor && + test_must_fail git rebase -i --keep-base master +' + test_done diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh index 6d523123d0..e63040932f 100755 --- a/t/t3431-rebase-fork-point.sh +++ b/t/t3431-rebase-fork-point.sh @@ -43,11 +43,15 @@ test_rebase() { test_rebase 'G F E D B A' test_rebase 'G F D B A' --onto D +test_rebase 'G F B A' --keep-base test_rebase 'G F C E D B A' --no-fork-point test_rebase 'G F C D B A' --no-fork-point --onto D +test_rebase 'G F C B A' --no-fork-point --keep-base test_rebase 'G F E D B A' --fork-point refs/heads/master test_rebase 'G F D B A' --fork-point --onto D refs/heads/master +test_rebase 'G F B A' --fork-point --keep-base refs/heads/master test_rebase 'G F C E D B A' refs/heads/master test_rebase 'G F C D B A' --onto D refs/heads/master +test_rebase 'G F C B A' --keep-base refs/heads/master test_done diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index 1cb2896fb4..1ba02cc95f 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -33,11 +33,15 @@ test_rebase_same_head success master test_rebase_same_head success --onto B B test_rebase_same_head success --onto B... B test_rebase_same_head success --onto master... master +test_rebase_same_head success --keep-base master +test_rebase_same_head success --keep-base test_rebase_same_head success --no-fork-point +test_rebase_same_head success --keep-base --no-fork-point test_rebase_same_head success --fork-point master test_rebase_same_head success --fork-point --onto B B test_rebase_same_head success --fork-point --onto B... B test_rebase_same_head success --fork-point --onto master... master +test_rebase_same_head success --fork-point --keep-base master test_expect_success 'add work to side' ' test_commit E @@ -49,11 +53,15 @@ test_rebase_same_head success master test_rebase_same_head success --onto B B test_rebase_same_head success --onto B... B test_rebase_same_head success --onto master... master +test_rebase_same_head success --keep-base master +test_rebase_same_head success --keep-base test_rebase_same_head success --no-fork-point +test_rebase_same_head success --keep-base --no-fork-point test_rebase_same_head success --fork-point master test_rebase_same_head success --fork-point --onto B B test_rebase_same_head success --fork-point --onto B... B test_rebase_same_head success --fork-point --onto master... master +test_rebase_same_head success --fork-point --keep-base master test_expect_success 'add work to upstream' ' git checkout master && @@ -65,8 +73,11 @@ changes='our and their changes' test_rebase_same_head success --onto B B test_rebase_same_head success --onto B... B test_rebase_same_head success --onto master... master +test_rebase_same_head success --keep-base master +test_rebase_same_head success --keep-base test_rebase_same_head success --fork-point --onto B B test_rebase_same_head success --fork-point --onto B... B test_rebase_same_head success --fork-point --onto master... master +test_rebase_same_head success --fork-point --keep-base master test_done -- 2.21.0.921.gb27c68c4e9 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v6 0/5] rebase: teach rebase --keep-base 2019-04-05 21:39 ` [PATCH v4 0/4] " Denton Liu ` (4 preceding siblings ...) 2019-04-15 22:29 ` [PATCH v5 0/5] " Denton Liu @ 2019-04-17 18:01 ` Denton Liu 2019-04-17 18:01 ` [PATCH v6 1/6] t3431: add rebase --fork-point tests Denton Liu ` (6 more replies) 5 siblings, 7 replies; 123+ messages in thread From: Denton Liu @ 2019-04-17 18:01 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Thanks for the comments, Junio and Phillip. I've fixed the ASCII art graphs and also refactored the if-else into a goto tower. Hopefully, this will be the last reroll of this series ;) --- This patchset now depends "[PATCH 1/8] tests (rebase): spell out the `--keep-empty` option" which is the first patch of Johannes's "Do not use abbreviated options in tests" patchset[1]. (Thanks for catching that, Johannes!) Changes since v1: * Squashed old set into one patch * Fixed indentation style and dangling else * Added more documentation after discussion with Ævar Changes since v2: * Add testing for rebase --fork-point behaviour * Add testing for rebase fast-forward behaviour * Make rebase --onto fast-forward in more cases * Update documentation to include use-case Changes since v3: * Fix tests failing on bash 4.2 * Fix typo in t3431 comment Changes since v4: * Make rebase --fork-point fast-forward in more cases Changes since v5: * Fix graph illustrations so that the "branch off" is visually in the correct place * Refactor if-else in can_fast_forward into one-level-deep ifs to increase clarity [1]: https://public-inbox.org/git/a1b4b74b9167e279dae4cd8c58fb28d8a714a66a.1553537656.git.gitgitgadget@gmail.com/ Denton Liu (6): t3431: add rebase --fork-point tests t3432: test rebase fast-forward behavior rebase: refactor can_fast_forward into goto tower rebase: fast-forward --onto in more cases rebase: fast-forward --fork-point in more cases rebase: teach rebase --keep-base Documentation/git-rebase.txt | 30 +++++++++-- builtin/rebase.c | 86 +++++++++++++++++++++++--------- t/t3400-rebase.sh | 2 +- t/t3404-rebase-interactive.sh | 2 +- t/t3416-rebase-onto-threedots.sh | 57 +++++++++++++++++++++ t/t3431-rebase-fork-point.sh | 57 +++++++++++++++++++++ t/t3432-rebase-fast-forward.sh | 83 ++++++++++++++++++++++++++++++ 7 files changed, 289 insertions(+), 28 deletions(-) create mode 100755 t/t3431-rebase-fork-point.sh create mode 100755 t/t3432-rebase-fast-forward.sh Range-diff against v5: 1: 0f1e9ac5c8 ! 1: eb64f6c91d t3431: add rebase --fork-point tests @@ -18,9 +18,9 @@ + +. ./test-lib.sh + -+# A---B---D---E (master) -+# \ -+# C*---F---G (side) ++# A---B---D---E (master) ++# \ ++# C*---F---G (side) +# +# C was formerly part of master but master was rewound to remove C +# 2: 148027a14b = 2: 4c087ec041 t3432: test rebase fast-forward behavior -: ---------- > 3: 3d348d2c37 rebase: refactor can_fast_forward into goto tower 3: ec55da0719 ! 4: 2559ab54a2 rebase: fast-forward --onto in more cases @@ -5,8 +5,8 @@ Before, when we had the following graph, A---B---C (master) - \ - D (side) + \ + D (side) running 'git rebase --onto master... master side' would result in D being always rebased, no matter what. However, the desired behavior is @@ -49,6 +49,7 @@ While we're at it, remove a trailing whitespace from rebase.c. + Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk> Signed-off-by: Denton Liu <liu.denton@gmail.com> diff --git a/builtin/rebase.c b/builtin/rebase.c @@ -64,45 +65,26 @@ + struct object_id *head_oid, struct object_id *merge_base) { struct commit *head = lookup_commit(the_repository, head_oid); -- struct commit_list *merge_bases; -- int res; -+ struct commit_list *merge_bases = NULL; -+ int res = 0; - - if (!head) - return 0; + struct commit_list *merge_bases = NULL; @@ - merge_bases = get_merge_bases(onto, head); - if (merge_bases && !merge_bases->next) { - oidcpy(merge_base, &merge_bases->item->object.oid); -- res = oideq(merge_base, &onto->object.oid); -+ if (!oideq(merge_base, &onto->object.oid)) -+ goto done; - } else { - oidcpy(merge_base, &null_oid); -- res = 0; -+ goto done; - } -+ + if (!oideq(merge_base, &onto->object.oid)) + goto done; + + if (!upstream) + goto done; + - free_commit_list(merge_bases); ++ free_commit_list(merge_bases); + merge_bases = get_merge_bases(upstream, head); -+ if (merge_bases && !merge_bases->next) { -+ if (!oideq(&onto->object.oid, &merge_bases->item->object.oid)) -+ goto done; -+ } else ++ if (!merge_bases || merge_bases->next) { + goto done; ++ } + -+ res = 1; ++ if (!oideq(&onto->object.oid, &merge_bases->item->object.oid)) ++ goto done; + -+done: -+ if (merge_bases) -+ free_commit_list(merge_bases); - return res && is_linear_history(onto, head); - } + res = 1; + done: @@ /* @@ -115,7 +97,8 @@ - !is_interactive(&options) && !options.restrict_revision && - options.upstream && - !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { -+ if (can_fast_forward(options.onto, options.upstream, &options.orig_head, &merge_base) && ++ if (can_fast_forward(options.onto, options.upstream, &options.orig_head, ++ &merge_base) && + !is_interactive(&options) && !options.restrict_revision) { int flag; 4: 2256a902c1 ! 5: 0a466e830f rebase: fast-forward --fork-point in more cases @@ -27,8 +27,8 @@ { struct commit *head = lookup_commit(the_repository, head_oid); @@ + if (!oideq(merge_base, &onto->object.oid)) goto done; - } + if (restrict_revision && !oideq(&restrict_revision->object.oid, merge_base)) + goto done; @@ -40,7 +40,8 @@ * Check if we are already based on onto with linear history, * but this should be done if this is not an interactive rebase. */ -- if (can_fast_forward(options.onto, options.upstream, &options.orig_head, &merge_base) && +- if (can_fast_forward(options.onto, options.upstream, &options.orig_head, +- &merge_base) && - !is_interactive(&options) && !options.restrict_revision) { + if (can_fast_forward(options.onto, options.upstream, options.restrict_revision, + &options.orig_head, &merge_base) && 5: d6e7e1ca4b = 6: c542c7afc1 rebase: teach rebase --keep-base -- 2.21.0.944.gce45564dfd ^ permalink raw reply [flat|nested] 123+ messages in thread
* [PATCH v6 1/6] t3431: add rebase --fork-point tests 2019-04-17 18:01 ` [PATCH v6 0/5] " Denton Liu @ 2019-04-17 18:01 ` Denton Liu 2019-04-17 18:01 ` [PATCH v6 2/6] t3432: test rebase fast-forward behavior Denton Liu ` (5 subsequent siblings) 6 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-17 18:01 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Signed-off-by: Denton Liu <liu.denton@gmail.com> --- t/t3431-rebase-fork-point.sh | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100755 t/t3431-rebase-fork-point.sh diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh new file mode 100755 index 0000000000..9b517d87a3 --- /dev/null +++ b/t/t3431-rebase-fork-point.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# +# Copyright (c) 2019 Denton Liu +# + +test_description='git rebase --fork-point test' + +. ./test-lib.sh + +# A---B---D---E (master) +# \ +# C*---F---G (side) +# +# C was formerly part of master but master was rewound to remove C +# +test_expect_success setup ' + test_commit A && + test_commit B && + test_commit C && + git branch -t side && + git reset --hard HEAD^ && + test_commit D && + test_commit E && + git checkout side && + test_commit F && + test_commit G +' + +test_rebase() { + expected="$1" && + shift && + test_expect_success "git rebase $*" " + git checkout master && + git reset --hard E && + git checkout side && + git reset --hard G && + git rebase $* && + test_write_lines $expected >expect && + git log --pretty=%s >actual && + test_cmp expect actual + " +} + +test_rebase 'G F E D B A' +test_rebase 'G F D B A' --onto D +test_rebase 'G F C E D B A' --no-fork-point +test_rebase 'G F C D B A' --no-fork-point --onto D +test_rebase 'G F E D B A' --fork-point refs/heads/master +test_rebase 'G F D B A' --fork-point --onto D refs/heads/master +test_rebase 'G F C E D B A' refs/heads/master +test_rebase 'G F C D B A' --onto D refs/heads/master + +test_done -- 2.21.0.944.gce45564dfd ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v6 2/6] t3432: test rebase fast-forward behavior 2019-04-17 18:01 ` [PATCH v6 0/5] " Denton Liu 2019-04-17 18:01 ` [PATCH v6 1/6] t3431: add rebase --fork-point tests Denton Liu @ 2019-04-17 18:01 ` Denton Liu 2019-04-17 18:01 ` [PATCH v6 3/6] rebase: refactor can_fast_forward into goto tower Denton Liu ` (4 subsequent siblings) 6 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-17 18:01 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor When rebase is run on a branch that can be fast-forwarded, this should automatically be done. Create test to ensure this behavior happens. There are some cases that currently don't pass. The first case is where a feature and master have diverged, running "git rebase master... master" causes a full rebase to happen even though a fast-forward should happen. The second case is when we are doing "git rebase --fork-point" and a fork-point commit is found. Once again, a full rebase happens even though a fast-forward should happen. Mark these cases as failure so we can fix it later. Signed-off-by: Denton Liu <liu.denton@gmail.com> --- t/t3432-rebase-fast-forward.sh | 72 ++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100755 t/t3432-rebase-fast-forward.sh diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh new file mode 100755 index 0000000000..4f04d67fd7 --- /dev/null +++ b/t/t3432-rebase-fast-forward.sh @@ -0,0 +1,72 @@ +#!/bin/sh +# +# Copyright (c) 2019 Denton Liu +# + +test_description='ensure rebase fast-forwards commits when possible' + +. ./test-lib.sh + +test_expect_success setup ' + test_commit A && + test_commit B && + test_commit C && + test_commit D && + git checkout -t -b side +' + +test_rebase_same_head() { + status="$1" && + shift && + test_expect_$status "git rebase $* with $changes is no-op" " + oldhead=\$(git rev-parse HEAD) && + test_when_finished 'git reset --hard \$oldhead' && + git rebase $* && + newhead=\$(git rev-parse HEAD) && + test_cmp_rev \$oldhead \$newhead + " +} + +changes='no changes' +test_rebase_same_head success +test_rebase_same_head success master +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head success --onto master... master +test_rebase_same_head success --no-fork-point +test_rebase_same_head success --fork-point master +test_rebase_same_head failure --fork-point --onto B B +test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head success --fork-point --onto master... master + +test_expect_success 'add work to side' ' + test_commit E +' + +changes='our changes' +test_rebase_same_head success +test_rebase_same_head success master +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head success --onto master... master +test_rebase_same_head success --no-fork-point +test_rebase_same_head success --fork-point master +test_rebase_same_head failure --fork-point --onto B B +test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head success --fork-point --onto master... master + +test_expect_success 'add work to upstream' ' + git checkout master && + test_commit F && + git checkout side +' + +changes='our and their changes' +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head failure --onto master... master +test_rebase_same_head failure --fork-point --onto B B +test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head failure --fork-point --onto master... master + +test_done -- 2.21.0.944.gce45564dfd ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v6 3/6] rebase: refactor can_fast_forward into goto tower 2019-04-17 18:01 ` [PATCH v6 0/5] " Denton Liu 2019-04-17 18:01 ` [PATCH v6 1/6] t3431: add rebase --fork-point tests Denton Liu 2019-04-17 18:01 ` [PATCH v6 2/6] t3432: test rebase fast-forward behavior Denton Liu @ 2019-04-17 18:01 ` Denton Liu 2019-04-17 18:01 ` [PATCH v6 4/6] rebase: fast-forward --onto in more cases Denton Liu ` (3 subsequent siblings) 6 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-17 18:01 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Before, can_fast_forward was written with an if-else statement. However, in the future, we may be adding more termination cases which would lead to deeply nested if statements. Refactor to use a goto tower so that future cases can be easily inserted. Signed-off-by: Denton Liu <liu.denton@gmail.com> --- builtin/rebase.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 77deebc65c..de10b6f5ad 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -899,21 +899,27 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, struct object_id *merge_base) { struct commit *head = lookup_commit(the_repository, head_oid); - struct commit_list *merge_bases; - int res; + struct commit_list *merge_bases = NULL; + int res = 0; if (!head) - return 0; + goto done; merge_bases = get_merge_bases(onto, head); - if (merge_bases && !merge_bases->next) { - oidcpy(merge_base, &merge_bases->item->object.oid); - res = oideq(merge_base, &onto->object.oid); - } else { + if (!merge_bases || merge_bases->next) { oidcpy(merge_base, &null_oid); - res = 0; + goto done; } - free_commit_list(merge_bases); + + oidcpy(merge_base, &merge_bases->item->object.oid); + if (!oideq(merge_base, &onto->object.oid)) + goto done; + + res = 1; + +done: + if (merge_bases) + free_commit_list(merge_bases); return res && is_linear_history(onto, head); } -- 2.21.0.944.gce45564dfd ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v6 4/6] rebase: fast-forward --onto in more cases 2019-04-17 18:01 ` [PATCH v6 0/5] " Denton Liu ` (2 preceding siblings ...) 2019-04-17 18:01 ` [PATCH v6 3/6] rebase: refactor can_fast_forward into goto tower Denton Liu @ 2019-04-17 18:01 ` Denton Liu 2019-04-17 19:59 ` Phillip Wood 2019-04-17 18:01 ` [PATCH v6 5/6] rebase: fast-forward --fork-point " Denton Liu ` (2 subsequent siblings) 6 siblings, 1 reply; 123+ messages in thread From: Denton Liu @ 2019-04-17 18:01 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Before, when we had the following graph, A---B---C (master) \ D (side) running 'git rebase --onto master... master side' would result in D being always rebased, no matter what. However, the desired behavior is that rebase should notice that this is fast-forwardable and do that instead. Add detection to `can_fast_forward` so that this case can be detected and a fast-forward will be performed. First of all, rewrite the function to use gotos which simplifies the logic. Next, since the options.upstream && !oidcmp(&options.upstream->object.oid, &options.onto->object.oid) conditions were removed in `cmd_rebase`, we reintroduce a substitute in `can_fast_forward`. In particular, checking the merge bases of `upstream` and `head` fixes a failing case in t3416. The abbreviated graph for t3416 is as follows: F---G topic / A---B---C---D---E master and the failing command was git rebase --onto master...topic F topic Before, Git would see that there was one merge base (C), and the merge and onto were the same so it would incorrectly return 1, indicating that we could fast-forward. This would cause the rebased graph to be 'ABCFG' when we were expecting 'ABCG'. With the additional logic, we detect that upstream and head's merge base is F. Since onto isn't F, it means we're not rebasing the full set of commits from master..topic. Since we're excluding some commits, a fast-forward cannot be performed and so we correctly return 0. Add '-f' to test cases that failed as a result of this change because they were not expecting a fast-forward so that a rebase is forced. While we're at it, remove a trailing whitespace from rebase.c. Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- builtin/rebase.c | 28 +++++++++++++++++++--------- t/t3400-rebase.sh | 2 +- t/t3404-rebase-interactive.sh | 2 +- t/t3432-rebase-fast-forward.sh | 4 ++-- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index de10b6f5ad..f5aca1eee0 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -895,8 +895,8 @@ static int is_linear_history(struct commit *from, struct commit *to) return 1; } -static int can_fast_forward(struct commit *onto, struct object_id *head_oid, - struct object_id *merge_base) +static int can_fast_forward(struct commit *onto, struct commit *upstream, + struct object_id *head_oid, struct object_id *merge_base) { struct commit *head = lookup_commit(the_repository, head_oid); struct commit_list *merge_bases = NULL; @@ -915,6 +915,18 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, if (!oideq(merge_base, &onto->object.oid)) goto done; + if (!upstream) + goto done; + + free_commit_list(merge_bases); + merge_bases = get_merge_bases(upstream, head); + if (!merge_bases || merge_bases->next) { + goto done; + } + + if (!oideq(&onto->object.oid, &merge_bases->item->object.oid)) + goto done; + res = 1; done: @@ -1688,13 +1700,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) /* * Check if we are already based on onto with linear history, - * but this should be done only when upstream and onto are the same - * and if this is not an interactive rebase. + * but this should be done if this is not an interactive rebase. */ - if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && - !is_interactive(&options) && !options.restrict_revision && - options.upstream && - !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { + if (can_fast_forward(options.onto, options.upstream, &options.orig_head, + &merge_base) && + !is_interactive(&options) && !options.restrict_revision) { int flag; if (!(options.flags & REBASE_FORCE)) { @@ -1788,7 +1798,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&msg, "%s: checkout %s", getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name); if (reset_head(&options.onto->object.oid, "checkout", NULL, - RESET_HEAD_DETACH | RESET_ORIG_HEAD | + RESET_HEAD_DETACH | RESET_ORIG_HEAD | RESET_HEAD_RUN_POST_CHECKOUT_HOOK, NULL, msg.buf)) die(_("Could not detach HEAD")); diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 460d0523be..604d624ff8 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' ' echo two >>init.t && git commit -a -m two && git tag two && - test_must_fail git rebase --onto init HEAD^ && + test_must_fail git rebase -f --onto init HEAD^ && GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && grep "show.*$(git rev-parse two)" stderr ) diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index b60b11f9f2..f054186cc7 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int git reset --hard && git checkout conflict-branch && set_fake_editor && - test_must_fail git rebase --onto HEAD~2 HEAD~ && + test_must_fail git rebase -f --onto HEAD~2 HEAD~ && test_must_fail git rebase --edit-todo && git rebase --abort ' diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index 4f04d67fd7..d0e5b1f3e6 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -64,9 +64,9 @@ test_expect_success 'add work to upstream' ' changes='our and their changes' test_rebase_same_head success --onto B B test_rebase_same_head success --onto B... B -test_rebase_same_head failure --onto master... master +test_rebase_same_head success --onto master... master test_rebase_same_head failure --fork-point --onto B B test_rebase_same_head failure --fork-point --onto B... B -test_rebase_same_head failure --fork-point --onto master... master +test_rebase_same_head success --fork-point --onto master... master test_done -- 2.21.0.944.gce45564dfd ^ permalink raw reply related [flat|nested] 123+ messages in thread
* Re: [PATCH v6 4/6] rebase: fast-forward --onto in more cases 2019-04-17 18:01 ` [PATCH v6 4/6] rebase: fast-forward --onto in more cases Denton Liu @ 2019-04-17 19:59 ` Phillip Wood 0 siblings, 0 replies; 123+ messages in thread From: Phillip Wood @ 2019-04-17 19:59 UTC (permalink / raw) To: Denton Liu, Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Hi Denton On 17/04/2019 19:01, Denton Liu wrote: > Before, when we had the following graph, > > A---B---C (master) > \ > D (side) > > running 'git rebase --onto master... master side' would result in D > being always rebased, no matter what. However, the desired behavior is > that rebase should notice that this is fast-forwardable and do that > instead. > > Add detection to `can_fast_forward` so that this case can be detected > and a fast-forward will be performed. First of all, rewrite the function > to use gotos which simplifies the logic. Next, since the > > options.upstream && > !oidcmp(&options.upstream->object.oid, &options.onto->object.oid) > > conditions were removed in `cmd_rebase`, we reintroduce a substitute in > `can_fast_forward`. In particular, checking the merge bases of > `upstream` and `head` fixes a failing case in t3416. > > The abbreviated graph for t3416 is as follows: > > F---G topic > / > A---B---C---D---E master > > and the failing command was > > git rebase --onto master...topic F topic > > Before, Git would see that there was one merge base (C), and the merge > and onto were the same so it would incorrectly return 1, indicating that > we could fast-forward. This would cause the rebased graph to be 'ABCFG' > when we were expecting 'ABCG'. > > With the additional logic, we detect that upstream and head's merge base > is F. Since onto isn't F, it means we're not rebasing the full set of > commits from master..topic. Since we're excluding some commits, a > fast-forward cannot be performed and so we correctly return 0. > > Add '-f' to test cases that failed as a result of this change because > they were not expecting a fast-forward so that a rebase is forced. > > While we're at it, remove a trailing whitespace from rebase.c. > > Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk> > Signed-off-by: Denton Liu <liu.denton@gmail.com> > --- > builtin/rebase.c | 28 +++++++++++++++++++--------- > t/t3400-rebase.sh | 2 +- > t/t3404-rebase-interactive.sh | 2 +- > t/t3432-rebase-fast-forward.sh | 4 ++-- > 4 files changed, 23 insertions(+), 13 deletions(-) > > diff --git a/builtin/rebase.c b/builtin/rebase.c > index de10b6f5ad..f5aca1eee0 100644 > --- a/builtin/rebase.c > +++ b/builtin/rebase.c > @@ -895,8 +895,8 @@ static int is_linear_history(struct commit *from, struct commit *to) > return 1; > } > > -static int can_fast_forward(struct commit *onto, struct object_id *head_oid, > - struct object_id *merge_base) > +static int can_fast_forward(struct commit *onto, struct commit *upstream, > + struct object_id *head_oid, struct object_id *merge_base) > { > struct commit *head = lookup_commit(the_repository, head_oid); > struct commit_list *merge_bases = NULL; > @@ -915,6 +915,18 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, > if (!oideq(merge_base, &onto->object.oid)) > goto done; > > + if (!upstream) > + goto done; > + > + free_commit_list(merge_bases); > + merge_bases = get_merge_bases(upstream, head); > + if (!merge_bases || merge_bases->next) { > + goto done; > + } Thanks for changing the ifs in this patch and the previous one, I find it much easier to follow now. Just one style nit (probably not worth a reroll) - we don't put braces around a single conditional statement like this unless another branch of the same if requires them (then all branches should have them). Best Wishes Phillip > + > + if (!oideq(&onto->object.oid, &merge_bases->item->object.oid)) > + goto done; > + > res = 1; > > done: > @@ -1688,13 +1700,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > > /* > * Check if we are already based on onto with linear history, > - * but this should be done only when upstream and onto are the same > - * and if this is not an interactive rebase. > + * but this should be done if this is not an interactive rebase. > */ > - if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && > - !is_interactive(&options) && !options.restrict_revision && > - options.upstream && > - !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { > + if (can_fast_forward(options.onto, options.upstream, &options.orig_head, > + &merge_base) && > + !is_interactive(&options) && !options.restrict_revision) { > int flag; > > if (!(options.flags & REBASE_FORCE)) { > @@ -1788,7 +1798,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > strbuf_addf(&msg, "%s: checkout %s", > getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name); > if (reset_head(&options.onto->object.oid, "checkout", NULL, > - RESET_HEAD_DETACH | RESET_ORIG_HEAD | > + RESET_HEAD_DETACH | RESET_ORIG_HEAD | > RESET_HEAD_RUN_POST_CHECKOUT_HOOK, > NULL, msg.buf)) > die(_("Could not detach HEAD")); > diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh > index 460d0523be..604d624ff8 100755 > --- a/t/t3400-rebase.sh > +++ b/t/t3400-rebase.sh > @@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' ' > echo two >>init.t && > git commit -a -m two && > git tag two && > - test_must_fail git rebase --onto init HEAD^ && > + test_must_fail git rebase -f --onto init HEAD^ && > GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && > grep "show.*$(git rev-parse two)" stderr > ) > diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh > index b60b11f9f2..f054186cc7 100755 > --- a/t/t3404-rebase-interactive.sh > +++ b/t/t3404-rebase-interactive.sh > @@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int > git reset --hard && > git checkout conflict-branch && > set_fake_editor && > - test_must_fail git rebase --onto HEAD~2 HEAD~ && > + test_must_fail git rebase -f --onto HEAD~2 HEAD~ && > test_must_fail git rebase --edit-todo && > git rebase --abort > ' > diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh > index 4f04d67fd7..d0e5b1f3e6 100755 > --- a/t/t3432-rebase-fast-forward.sh > +++ b/t/t3432-rebase-fast-forward.sh > @@ -64,9 +64,9 @@ test_expect_success 'add work to upstream' ' > changes='our and their changes' > test_rebase_same_head success --onto B B > test_rebase_same_head success --onto B... B > -test_rebase_same_head failure --onto master... master > +test_rebase_same_head success --onto master... master > test_rebase_same_head failure --fork-point --onto B B > test_rebase_same_head failure --fork-point --onto B... B > -test_rebase_same_head failure --fork-point --onto master... master > +test_rebase_same_head success --fork-point --onto master... master > > test_done > ^ permalink raw reply [flat|nested] 123+ messages in thread
* [PATCH v6 5/6] rebase: fast-forward --fork-point in more cases 2019-04-17 18:01 ` [PATCH v6 0/5] " Denton Liu ` (3 preceding siblings ...) 2019-04-17 18:01 ` [PATCH v6 4/6] rebase: fast-forward --onto in more cases Denton Liu @ 2019-04-17 18:01 ` Denton Liu 2019-04-17 18:01 ` [PATCH v6 6/6] rebase: teach rebase --keep-base Denton Liu 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu 6 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-17 18:01 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Before, when we rebased with a --fork-point invocation where the fork-point wasn't empty, we would be setting options.restrict_revision. The fast-forward logic would automatically declare that the rebase was not fast-forwardable if it was set. However, this was painting with a very broad brush. Refine the logic so that we can fast-forward in the case where the restricted revision is equal to the merge base, since we stop rebasing at the merge base anyway. Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- builtin/rebase.c | 10 +++++++--- t/t3432-rebase-fast-forward.sh | 12 ++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index f5aca1eee0..2e29ea652f 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -896,6 +896,7 @@ static int is_linear_history(struct commit *from, struct commit *to) } static int can_fast_forward(struct commit *onto, struct commit *upstream, + struct commit *restrict_revision, struct object_id *head_oid, struct object_id *merge_base) { struct commit *head = lookup_commit(the_repository, head_oid); @@ -915,6 +916,9 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream, if (!oideq(merge_base, &onto->object.oid)) goto done; + if (restrict_revision && !oideq(&restrict_revision->object.oid, merge_base)) + goto done; + if (!upstream) goto done; @@ -1702,9 +1706,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) * Check if we are already based on onto with linear history, * but this should be done if this is not an interactive rebase. */ - if (can_fast_forward(options.onto, options.upstream, &options.orig_head, - &merge_base) && - !is_interactive(&options) && !options.restrict_revision) { + if (can_fast_forward(options.onto, options.upstream, options.restrict_revision, + &options.orig_head, &merge_base) && + !is_interactive(&options)) { int flag; if (!(options.flags & REBASE_FORCE)) { diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index d0e5b1f3e6..1cb2896fb4 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -35,8 +35,8 @@ test_rebase_same_head success --onto B... B test_rebase_same_head success --onto master... master test_rebase_same_head success --no-fork-point test_rebase_same_head success --fork-point master -test_rebase_same_head failure --fork-point --onto B B -test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head success --fork-point --onto B B +test_rebase_same_head success --fork-point --onto B... B test_rebase_same_head success --fork-point --onto master... master test_expect_success 'add work to side' ' @@ -51,8 +51,8 @@ test_rebase_same_head success --onto B... B test_rebase_same_head success --onto master... master test_rebase_same_head success --no-fork-point test_rebase_same_head success --fork-point master -test_rebase_same_head failure --fork-point --onto B B -test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head success --fork-point --onto B B +test_rebase_same_head success --fork-point --onto B... B test_rebase_same_head success --fork-point --onto master... master test_expect_success 'add work to upstream' ' @@ -65,8 +65,8 @@ changes='our and their changes' test_rebase_same_head success --onto B B test_rebase_same_head success --onto B... B test_rebase_same_head success --onto master... master -test_rebase_same_head failure --fork-point --onto B B -test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head success --fork-point --onto B B +test_rebase_same_head success --fork-point --onto B... B test_rebase_same_head success --fork-point --onto master... master test_done -- 2.21.0.944.gce45564dfd ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v6 6/6] rebase: teach rebase --keep-base 2019-04-17 18:01 ` [PATCH v6 0/5] " Denton Liu ` (4 preceding siblings ...) 2019-04-17 18:01 ` [PATCH v6 5/6] rebase: fast-forward --fork-point " Denton Liu @ 2019-04-17 18:01 ` Denton Liu 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu 6 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-17 18:01 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor A common scenario is if a user is working on a topic branch and they wish to make some changes to intermediate commits or autosquash, they would run something such as git rebase -i --onto master... master in order to preserve the merge base. This is useful when contributing a patch series to the Git mailing list, one often starts on top of the current 'master'. However, while developing the patches, 'master' is also developed further and it is sometimes not the bst idea to keep rebasing on top of 'master', but to keep the base commit as-is. Alternatively, a user wishing to test individual commits in a topic branch without changing anything may run git rebase -x ./test.sh master... master Since rebasing onto the merge base of the branch and the upstream is such a common case, introduce the --keep-base option as a shortcut. This allows us to rewrite the above as git rebase -i --keep-base master and git rebase -x ./test.sh --keep-base master respectively. Add tests to ensure --keep-base works correctly in the normal case and fails when there are multiple merge bases, both in regular and interactive mode. Also, test to make sure conflicting options cause rebase to fail. While we're adding test cases, add a missing set_fake_editor call to 'rebase -i --onto master...side'. While we're documenting the --keep-base option, change an instance of "merge-base" to "merge base", which is the consistent spelling. Helped-by: Eric Sunshine <sunshine@sunshineco.com> Helped-by: Junio C Hamano <gitster@pobox.com> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- Documentation/git-rebase.txt | 30 +++++++++++++++-- builtin/rebase.c | 32 ++++++++++++++---- t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++++++++ t/t3431-rebase-fork-point.sh | 4 +++ t/t3432-rebase-fast-forward.sh | 11 ++++++ 5 files changed, 125 insertions(+), 9 deletions(-) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 6363d674b7..569ab708d4 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip SYNOPSIS -------- [verse] -'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] - [<upstream> [<branch>]] +'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] + [--onto <newbase> | --keep-base] [<upstream> [<branch>]] 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] --root [<branch>] 'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch @@ -217,6 +217,24 @@ As a special case, you may use "A\...B" as a shortcut for the merge base of A and B if there is exactly one merge base. You can leave out at most one of A and B, in which case it defaults to HEAD. +--keep-base:: + Set the starting point at which to create the new commits to the + merge base of <upstream> <branch>. Running + 'git rebase --keep-base <upstream> <branch>' is equivalent to + running 'git rebase --onto <upstream>... <upstream>'. ++ +This option is useful in the case where one is developing a feature on +top of an upstream branch. While the feature is being worked on, the +upstream branch may advance and it may not be the best idea to keep +rebasing on top of the upstream but to keep the base commit as-is. ++ +Although both this option and --fork-point find the merge base between +<upstream> and <branch>, this option uses the merge base as the _starting +point_ on which new commits will be created, whereas --fork-point uses +the merge base to determine the _set of commits_ which will be rebased. ++ +See also INCOMPATIBLE OPTIONS below. + <upstream>:: Upstream branch to compare against. May be any valid commit, not just an existing branch name. Defaults to the configured @@ -364,6 +382,10 @@ ends up being empty, the <upstream> will be used as a fallback. + If either <upstream> or --root is given on the command line, then the default is `--no-fork-point`, otherwise the default is `--fork-point`. ++ +If your branch was based on <upstream> but <upstream> was rewound and +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:: --whitespace=<option>:: @@ -539,6 +561,8 @@ In addition, the following pairs of options are incompatible: * --preserve-merges and --rebase-merges * --rebase-merges and --strategy * --rebase-merges and --strategy-option + * --keep-base and --onto + * --keep-base and --root BEHAVIORAL DIFFERENCES ----------------------- @@ -863,7 +887,7 @@ NOTE: While an "easy case recovery" sometimes appears to be successful --interactive` will be **resurrected**! The idea is to manually tell 'git rebase' "where the old 'subsystem' -ended and your 'topic' began", that is, what the old merge-base +ended and your 'topic' began", that is, what the old merge base between them was. You will have to find a way to name the last commit of the old 'subsystem', for example: diff --git a/builtin/rebase.c b/builtin/rebase.c index 2e29ea652f..87a23f17eb 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -27,8 +27,8 @@ #include "branch.h" static char const * const builtin_rebase_usage[] = { - N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " - "[<upstream>] [<branch>]"), + N_("git rebase [-i] [options] [--exec <cmd>] " + "[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"), N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " "--root [<branch>]"), N_("git rebase --continue | --abort | --skip | --edit-todo"), @@ -1040,6 +1040,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) }; const char *branch_name; int ret, flags, total_argc, in_progress = 0; + int keep_base = 0; int ok_to_skip_pre_rebase = 0; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; @@ -1073,6 +1074,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_STRING(0, "onto", &options.onto_name, N_("revision"), N_("rebase onto given branch instead of upstream")), + OPT_BOOL(0, "keep-base", &keep_base, + N_("use the merge-base of upstream and branch as the current base")), OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase, N_("allow pre-rebase hook to run")), OPT_NEGBIT('q', "quiet", &options.flags, @@ -1239,6 +1242,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) usage_with_options(builtin_rebase_usage, builtin_rebase_options); + if (keep_base) { + if (options.onto_name) + die(_("cannot combine '--keep-base' with '--onto'")); + if (options.root) + die(_("cannot combine '--keep-base' with '--root'")); + } + if (action != NO_ACTION && !in_progress) die(_("No rebase in progress?")); setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0); @@ -1563,12 +1573,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } /* Make sure the branch to rebase onto is valid. */ - if (!options.onto_name) + if (keep_base) { + strbuf_reset(&buf); + strbuf_addstr(&buf, options.upstream_name); + strbuf_addstr(&buf, "..."); + options.onto_name = xstrdup(buf.buf); + } else if (!options.onto_name) options.onto_name = options.upstream_name; if (strstr(options.onto_name, "...")) { - if (get_oid_mb(options.onto_name, &merge_base) < 0) - die(_("'%s': need exactly one merge base"), - options.onto_name); + if (get_oid_mb(options.onto_name, &merge_base) < 0) { + if (keep_base) + die(_("'%s': need exactly one merge base with branch"), + options.upstream_name); + else + die(_("'%s': need exactly one merge base"), + options.onto_name); + } options.onto = lookup_commit_or_die(&merge_base, options.onto_name); } else { diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh index ddf2f64853..9c2548423b 100755 --- a/t/t3416-rebase-onto-threedots.sh +++ b/t/t3416-rebase-onto-threedots.sh @@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' ' git checkout side && git reset --hard K && + set_fake_editor && test_must_fail git rebase -i --onto master...side J ' +test_expect_success 'rebase --keep-base --onto incompatible' ' + test_must_fail git rebase --keep-base --onto master... +' + +test_expect_success 'rebase --keep-base --root incompatible' ' + test_must_fail git rebase --keep-base --root +' + +test_expect_success 'rebase --keep-base master from topic' ' + git reset --hard && + git checkout topic && + git reset --hard G && + + git rebase --keep-base master && + git rev-parse C >base.expect && + git merge-base master HEAD >base.actual && + test_cmp base.expect base.actual && + + git rev-parse HEAD~2 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase --keep-base master from side' ' + git reset --hard && + git checkout side && + git reset --hard K && + + test_must_fail git rebase --keep-base master +' + +test_expect_success 'rebase -i --keep-base master from topic' ' + git reset --hard && + git checkout topic && + git reset --hard G && + + set_fake_editor && + EXPECT_COUNT=2 git rebase -i --keep-base master && + git rev-parse C >base.expect && + git merge-base master HEAD >base.actual && + test_cmp base.expect base.actual && + + git rev-parse HEAD~2 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase -i --keep-base master from side' ' + git reset --hard && + git checkout side && + git reset --hard K && + + set_fake_editor && + test_must_fail git rebase -i --keep-base master +' + test_done diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh index 9b517d87a3..f848946f5a 100755 --- a/t/t3431-rebase-fork-point.sh +++ b/t/t3431-rebase-fork-point.sh @@ -43,11 +43,15 @@ test_rebase() { test_rebase 'G F E D B A' test_rebase 'G F D B A' --onto D +test_rebase 'G F B A' --keep-base test_rebase 'G F C E D B A' --no-fork-point test_rebase 'G F C D B A' --no-fork-point --onto D +test_rebase 'G F C B A' --no-fork-point --keep-base test_rebase 'G F E D B A' --fork-point refs/heads/master test_rebase 'G F D B A' --fork-point --onto D refs/heads/master +test_rebase 'G F B A' --fork-point --keep-base refs/heads/master test_rebase 'G F C E D B A' refs/heads/master test_rebase 'G F C D B A' --onto D refs/heads/master +test_rebase 'G F C B A' --keep-base refs/heads/master test_done diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index 1cb2896fb4..1ba02cc95f 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -33,11 +33,15 @@ test_rebase_same_head success master test_rebase_same_head success --onto B B test_rebase_same_head success --onto B... B test_rebase_same_head success --onto master... master +test_rebase_same_head success --keep-base master +test_rebase_same_head success --keep-base test_rebase_same_head success --no-fork-point +test_rebase_same_head success --keep-base --no-fork-point test_rebase_same_head success --fork-point master test_rebase_same_head success --fork-point --onto B B test_rebase_same_head success --fork-point --onto B... B test_rebase_same_head success --fork-point --onto master... master +test_rebase_same_head success --fork-point --keep-base master test_expect_success 'add work to side' ' test_commit E @@ -49,11 +53,15 @@ test_rebase_same_head success master test_rebase_same_head success --onto B B test_rebase_same_head success --onto B... B test_rebase_same_head success --onto master... master +test_rebase_same_head success --keep-base master +test_rebase_same_head success --keep-base test_rebase_same_head success --no-fork-point +test_rebase_same_head success --keep-base --no-fork-point test_rebase_same_head success --fork-point master test_rebase_same_head success --fork-point --onto B B test_rebase_same_head success --fork-point --onto B... B test_rebase_same_head success --fork-point --onto master... master +test_rebase_same_head success --fork-point --keep-base master test_expect_success 'add work to upstream' ' git checkout master && @@ -65,8 +73,11 @@ changes='our and their changes' test_rebase_same_head success --onto B B test_rebase_same_head success --onto B... B test_rebase_same_head success --onto master... master +test_rebase_same_head success --keep-base master +test_rebase_same_head success --keep-base test_rebase_same_head success --fork-point --onto B B test_rebase_same_head success --fork-point --onto B... B test_rebase_same_head success --fork-point --onto master... master +test_rebase_same_head success --fork-point --keep-base master test_done -- 2.21.0.944.gce45564dfd ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v7 0/6] rebase: learn --keep-base 2019-04-17 18:01 ` [PATCH v6 0/5] " Denton Liu ` (5 preceding siblings ...) 2019-04-17 18:01 ` [PATCH v6 6/6] rebase: teach rebase --keep-base Denton Liu @ 2019-04-21 8:11 ` Denton Liu 2019-04-21 8:11 ` [PATCH v7 1/6] t3431: add rebase --fork-point tests Denton Liu ` (19 more replies) 6 siblings, 20 replies; 123+ messages in thread From: Denton Liu @ 2019-04-21 8:11 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Hi all, Sorry for yet another reroll. I realised earlier that rebase doesn't use --git-completion-helper for completion so I had to manually insert --keep-base into the completion script. Between this and forgetting to update the comment around the can_fast_forward call, I figured with the number of mistakes to fix, it was worth another reroll. Thanks, Denton --- This patchset now depends "[PATCH 1/8] tests (rebase): spell out the `--keep-empty` option" which is the first patch of Johannes's "Do not use abbreviated options in tests" patchset[1]. (Thanks for catching that, Johannes!) Changes since v1: * Squashed old set into one patch * Fixed indentation style and dangling else * Added more documentation after discussion with Ævar Changes since v2: * Add testing for rebase --fork-point behaviour * Add testing for rebase fast-forward behaviour * Make rebase --onto fast-forward in more cases * Update documentation to include use-case Changes since v3: * Fix tests failing on bash 4.2 * Fix typo in t3431 comment Changes since v4: * Make rebase --fork-point fast-forward in more cases Changes since v5: * Fix graph illustrations so that the "branch off" is visually in the correct place * Refactor if-else in can_fast_forward into one-level-deep ifs to increase clarity Changes since v6: * Remove redundant braces around if * Update comment around can_fast_forward call * Add completion for rebase [1]: https://public-inbox.org/git/a1b4b74b9167e279dae4cd8c58fb28d8a714a66a.1553537656.git.gitgitgadget@gmail.com/ Denton Liu (6): t3431: add rebase --fork-point tests t3432: test rebase fast-forward behavior rebase: refactor can_fast_forward into goto tower rebase: fast-forward --onto in more cases rebase: fast-forward --fork-point in more cases rebase: teach rebase --keep-base Documentation/git-rebase.txt | 30 ++++++++- builtin/rebase.c | 87 +++++++++++++++++++------- contrib/completion/git-completion.bash | 2 +- t/t3400-rebase.sh | 2 +- t/t3404-rebase-interactive.sh | 2 +- t/t3416-rebase-onto-threedots.sh | 57 +++++++++++++++++ t/t3431-rebase-fork-point.sh | 57 +++++++++++++++++ t/t3432-rebase-fast-forward.sh | 83 ++++++++++++++++++++++++ 8 files changed, 291 insertions(+), 29 deletions(-) create mode 100755 t/t3431-rebase-fork-point.sh create mode 100755 t/t3432-rebase-fast-forward.sh Interdiff against v6: diff --git a/builtin/rebase.c b/builtin/rebase.c index cf63195e7d..19e7e2a1c9 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1723,7 +1723,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) /* * Check if we are already based on onto with linear history, - * but this should be done if this is not an interactive rebase. + * 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. */ if (can_fast_forward(options.onto, options.upstream, options.restrict_revision, &options.orig_head, &merge_base) && diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 976e4a6548..f9dc431a39 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -2027,7 +2027,7 @@ _git_rebase () --autosquash --no-autosquash --fork-point --no-fork-point --autostash --no-autostash - --verify --no-verify + --verify --no-verify --keep-base --keep-empty --root --force-rebase --no-ff --rerere-autoupdate --exec -- 2.21.0.967.gf85e14fd49 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v7 1/6] t3431: add rebase --fork-point tests 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu @ 2019-04-21 8:11 ` Denton Liu 2019-04-23 23:12 ` Denton Liu 2019-04-21 8:11 ` [PATCH v7 2/6] t3432: test rebase fast-forward behavior Denton Liu ` (18 subsequent siblings) 19 siblings, 1 reply; 123+ messages in thread From: Denton Liu @ 2019-04-21 8:11 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Signed-off-by: Denton Liu <liu.denton@gmail.com> --- t/t3431-rebase-fork-point.sh | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100755 t/t3431-rebase-fork-point.sh diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh new file mode 100755 index 0000000000..9b517d87a3 --- /dev/null +++ b/t/t3431-rebase-fork-point.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# +# Copyright (c) 2019 Denton Liu +# + +test_description='git rebase --fork-point test' + +. ./test-lib.sh + +# A---B---D---E (master) +# \ +# C*---F---G (side) +# +# C was formerly part of master but master was rewound to remove C +# +test_expect_success setup ' + test_commit A && + test_commit B && + test_commit C && + git branch -t side && + git reset --hard HEAD^ && + test_commit D && + test_commit E && + git checkout side && + test_commit F && + test_commit G +' + +test_rebase() { + expected="$1" && + shift && + test_expect_success "git rebase $*" " + git checkout master && + git reset --hard E && + git checkout side && + git reset --hard G && + git rebase $* && + test_write_lines $expected >expect && + git log --pretty=%s >actual && + test_cmp expect actual + " +} + +test_rebase 'G F E D B A' +test_rebase 'G F D B A' --onto D +test_rebase 'G F C E D B A' --no-fork-point +test_rebase 'G F C D B A' --no-fork-point --onto D +test_rebase 'G F E D B A' --fork-point refs/heads/master +test_rebase 'G F D B A' --fork-point --onto D refs/heads/master +test_rebase 'G F C E D B A' refs/heads/master +test_rebase 'G F C D B A' --onto D refs/heads/master + +test_done -- 2.21.0.967.gf85e14fd49 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* Re: [PATCH v7 1/6] t3431: add rebase --fork-point tests 2019-04-21 8:11 ` [PATCH v7 1/6] t3431: add rebase --fork-point tests Denton Liu @ 2019-04-23 23:12 ` Denton Liu 0 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-23 23:12 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Hi Junio, On Sun, Apr 21, 2019 at 01:11:18AM -0700, Denton Liu wrote: > Signed-off-by: Denton Liu <liu.denton@gmail.com> > --- > t/t3431-rebase-fork-point.sh | 53 ++++++++++++++++++++++++++++++++++++ > 1 file changed, 53 insertions(+) > create mode 100755 t/t3431-rebase-fork-point.sh > > diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh > new file mode 100755 > index 0000000000..9b517d87a3 > --- /dev/null > +++ b/t/t3431-rebase-fork-point.sh > @@ -0,0 +1,53 @@ > +#!/bin/sh > +# > +# Copyright (c) 2019 Denton Liu > +# > + > +test_description='git rebase --fork-point test' > + > +. ./test-lib.sh > + > +# A---B---D---E (master) > +# \ > +# C*---F---G (side) > +# > +# C was formerly part of master but master was rewound to remove C > +# > +test_expect_success setup ' > + test_commit A && > + test_commit B && > + test_commit C && > + git branch -t side && > + git reset --hard HEAD^ && > + test_commit D && > + test_commit E && > + git checkout side && > + test_commit F && > + test_commit G > +' > + > +test_rebase() { I read in an email thread earlier that the preferred style for function definitions is to include a space after the name. Sorry for not catching this earlier. Could you please change this to `test_rebase () {` for me? Also, same comment applies to `test_rebase_same_head` in 2/6. Thanks so much, Denton > + expected="$1" && > + shift && > + test_expect_success "git rebase $*" " > + git checkout master && > + git reset --hard E && > + git checkout side && > + git reset --hard G && > + git rebase $* && > + test_write_lines $expected >expect && > + git log --pretty=%s >actual && > + test_cmp expect actual > + " > +} > + > +test_rebase 'G F E D B A' > +test_rebase 'G F D B A' --onto D > +test_rebase 'G F C E D B A' --no-fork-point > +test_rebase 'G F C D B A' --no-fork-point --onto D > +test_rebase 'G F E D B A' --fork-point refs/heads/master > +test_rebase 'G F D B A' --fork-point --onto D refs/heads/master > +test_rebase 'G F C E D B A' refs/heads/master > +test_rebase 'G F C D B A' --onto D refs/heads/master > + > +test_done > -- > 2.21.0.967.gf85e14fd49 > ^ permalink raw reply [flat|nested] 123+ messages in thread
* [PATCH v7 2/6] t3432: test rebase fast-forward behavior 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu 2019-04-21 8:11 ` [PATCH v7 1/6] t3431: add rebase --fork-point tests Denton Liu @ 2019-04-21 8:11 ` Denton Liu 2019-04-21 8:11 ` [PATCH v7 3/6] rebase: refactor can_fast_forward into goto tower Denton Liu ` (17 subsequent siblings) 19 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-21 8:11 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor When rebase is run on a branch that can be fast-forwarded, this should automatically be done. Create test to ensure this behavior happens. There are some cases that currently don't pass. The first case is where a feature and master have diverged, running "git rebase master... master" causes a full rebase to happen even though a fast-forward should happen. The second case is when we are doing "git rebase --fork-point" and a fork-point commit is found. Once again, a full rebase happens even though a fast-forward should happen. Mark these cases as failure so we can fix it later. Signed-off-by: Denton Liu <liu.denton@gmail.com> --- t/t3432-rebase-fast-forward.sh | 72 ++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100755 t/t3432-rebase-fast-forward.sh diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh new file mode 100755 index 0000000000..4f04d67fd7 --- /dev/null +++ b/t/t3432-rebase-fast-forward.sh @@ -0,0 +1,72 @@ +#!/bin/sh +# +# Copyright (c) 2019 Denton Liu +# + +test_description='ensure rebase fast-forwards commits when possible' + +. ./test-lib.sh + +test_expect_success setup ' + test_commit A && + test_commit B && + test_commit C && + test_commit D && + git checkout -t -b side +' + +test_rebase_same_head() { + status="$1" && + shift && + test_expect_$status "git rebase $* with $changes is no-op" " + oldhead=\$(git rev-parse HEAD) && + test_when_finished 'git reset --hard \$oldhead' && + git rebase $* && + newhead=\$(git rev-parse HEAD) && + test_cmp_rev \$oldhead \$newhead + " +} + +changes='no changes' +test_rebase_same_head success +test_rebase_same_head success master +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head success --onto master... master +test_rebase_same_head success --no-fork-point +test_rebase_same_head success --fork-point master +test_rebase_same_head failure --fork-point --onto B B +test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head success --fork-point --onto master... master + +test_expect_success 'add work to side' ' + test_commit E +' + +changes='our changes' +test_rebase_same_head success +test_rebase_same_head success master +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head success --onto master... master +test_rebase_same_head success --no-fork-point +test_rebase_same_head success --fork-point master +test_rebase_same_head failure --fork-point --onto B B +test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head success --fork-point --onto master... master + +test_expect_success 'add work to upstream' ' + git checkout master && + test_commit F && + git checkout side +' + +changes='our and their changes' +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head failure --onto master... master +test_rebase_same_head failure --fork-point --onto B B +test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head failure --fork-point --onto master... master + +test_done -- 2.21.0.967.gf85e14fd49 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v7 3/6] rebase: refactor can_fast_forward into goto tower 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu 2019-04-21 8:11 ` [PATCH v7 1/6] t3431: add rebase --fork-point tests Denton Liu 2019-04-21 8:11 ` [PATCH v7 2/6] t3432: test rebase fast-forward behavior Denton Liu @ 2019-04-21 8:11 ` Denton Liu 2019-04-21 8:11 ` [PATCH v7 4/6] rebase: fast-forward --onto in more cases Denton Liu ` (16 subsequent siblings) 19 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-21 8:11 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Before, can_fast_forward was written with an if-else statement. However, in the future, we may be adding more termination cases which would lead to deeply nested if statements. Refactor to use a goto tower so that future cases can be easily inserted. Signed-off-by: Denton Liu <liu.denton@gmail.com> --- builtin/rebase.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 77deebc65c..de10b6f5ad 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -899,21 +899,27 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, struct object_id *merge_base) { struct commit *head = lookup_commit(the_repository, head_oid); - struct commit_list *merge_bases; - int res; + struct commit_list *merge_bases = NULL; + int res = 0; if (!head) - return 0; + goto done; merge_bases = get_merge_bases(onto, head); - if (merge_bases && !merge_bases->next) { - oidcpy(merge_base, &merge_bases->item->object.oid); - res = oideq(merge_base, &onto->object.oid); - } else { + if (!merge_bases || merge_bases->next) { oidcpy(merge_base, &null_oid); - res = 0; + goto done; } - free_commit_list(merge_bases); + + oidcpy(merge_base, &merge_bases->item->object.oid); + if (!oideq(merge_base, &onto->object.oid)) + goto done; + + res = 1; + +done: + if (merge_bases) + free_commit_list(merge_bases); return res && is_linear_history(onto, head); } -- 2.21.0.967.gf85e14fd49 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v7 4/6] rebase: fast-forward --onto in more cases 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu ` (2 preceding siblings ...) 2019-04-21 8:11 ` [PATCH v7 3/6] rebase: refactor can_fast_forward into goto tower Denton Liu @ 2019-04-21 8:11 ` Denton Liu 2019-04-21 8:11 ` [PATCH v7 5/6] rebase: fast-forward --fork-point " Denton Liu ` (15 subsequent siblings) 19 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-21 8:11 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Before, when we had the following graph, A---B---C (master) \ D (side) running 'git rebase --onto master... master side' would result in D being always rebased, no matter what. However, the desired behavior is that rebase should notice that this is fast-forwardable and do that instead. Add detection to `can_fast_forward` so that this case can be detected and a fast-forward will be performed. First of all, rewrite the function to use gotos which simplifies the logic. Next, since the options.upstream && !oidcmp(&options.upstream->object.oid, &options.onto->object.oid) conditions were removed in `cmd_rebase`, we reintroduce a substitute in `can_fast_forward`. In particular, checking the merge bases of `upstream` and `head` fixes a failing case in t3416. The abbreviated graph for t3416 is as follows: F---G topic / A---B---C---D---E master and the failing command was git rebase --onto master...topic F topic Before, Git would see that there was one merge base (C), and the merge and onto were the same so it would incorrectly return 1, indicating that we could fast-forward. This would cause the rebased graph to be 'ABCFG' when we were expecting 'ABCG'. With the additional logic, we detect that upstream and head's merge base is F. Since onto isn't F, it means we're not rebasing the full set of commits from master..topic. Since we're excluding some commits, a fast-forward cannot be performed and so we correctly return 0. Add '-f' to test cases that failed as a result of this change because they were not expecting a fast-forward so that a rebase is forced. While we're at it, remove a trailing whitespace from rebase.c. Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- builtin/rebase.c | 29 ++++++++++++++++++++--------- t/t3400-rebase.sh | 2 +- t/t3404-rebase-interactive.sh | 2 +- t/t3432-rebase-fast-forward.sh | 4 ++-- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index de10b6f5ad..fdb42eb09f 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -895,8 +895,8 @@ static int is_linear_history(struct commit *from, struct commit *to) return 1; } -static int can_fast_forward(struct commit *onto, struct object_id *head_oid, - struct object_id *merge_base) +static int can_fast_forward(struct commit *onto, struct commit *upstream, + struct object_id *head_oid, struct object_id *merge_base) { struct commit *head = lookup_commit(the_repository, head_oid); struct commit_list *merge_bases = NULL; @@ -915,6 +915,17 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, if (!oideq(merge_base, &onto->object.oid)) goto done; + if (!upstream) + goto done; + + free_commit_list(merge_bases); + merge_bases = get_merge_bases(upstream, head); + if (!merge_bases || merge_bases->next) + goto done; + + if (!oideq(&onto->object.oid, &merge_bases->item->object.oid)) + goto done; + res = 1; done: @@ -1688,13 +1699,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) /* * Check if we are already based on onto with linear history, - * but this should be done only when upstream and onto are the same - * and if this is not an interactive rebase. + * 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. */ - if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && - !is_interactive(&options) && !options.restrict_revision && - options.upstream && - !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { + if (can_fast_forward(options.onto, options.upstream, &options.orig_head, + &merge_base) && + !is_interactive(&options) && !options.restrict_revision) { int flag; if (!(options.flags & REBASE_FORCE)) { @@ -1788,7 +1799,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&msg, "%s: checkout %s", getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name); if (reset_head(&options.onto->object.oid, "checkout", NULL, - RESET_HEAD_DETACH | RESET_ORIG_HEAD | + RESET_HEAD_DETACH | RESET_ORIG_HEAD | RESET_HEAD_RUN_POST_CHECKOUT_HOOK, NULL, msg.buf)) die(_("Could not detach HEAD")); diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 460d0523be..604d624ff8 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' ' echo two >>init.t && git commit -a -m two && git tag two && - test_must_fail git rebase --onto init HEAD^ && + test_must_fail git rebase -f --onto init HEAD^ && GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && grep "show.*$(git rev-parse two)" stderr ) diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index b60b11f9f2..f054186cc7 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int git reset --hard && git checkout conflict-branch && set_fake_editor && - test_must_fail git rebase --onto HEAD~2 HEAD~ && + test_must_fail git rebase -f --onto HEAD~2 HEAD~ && test_must_fail git rebase --edit-todo && git rebase --abort ' diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index 4f04d67fd7..d0e5b1f3e6 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -64,9 +64,9 @@ test_expect_success 'add work to upstream' ' changes='our and their changes' test_rebase_same_head success --onto B B test_rebase_same_head success --onto B... B -test_rebase_same_head failure --onto master... master +test_rebase_same_head success --onto master... master test_rebase_same_head failure --fork-point --onto B B test_rebase_same_head failure --fork-point --onto B... B -test_rebase_same_head failure --fork-point --onto master... master +test_rebase_same_head success --fork-point --onto master... master test_done -- 2.21.0.967.gf85e14fd49 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v7 5/6] rebase: fast-forward --fork-point in more cases 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu ` (3 preceding siblings ...) 2019-04-21 8:11 ` [PATCH v7 4/6] rebase: fast-forward --onto in more cases Denton Liu @ 2019-04-21 8:11 ` Denton Liu 2019-04-21 8:11 ` [PATCH v7 6/6] rebase: teach rebase --keep-base Denton Liu ` (14 subsequent siblings) 19 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-21 8:11 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Before, when we rebased with a --fork-point invocation where the fork-point wasn't empty, we would be setting options.restrict_revision. The fast-forward logic would automatically declare that the rebase was not fast-forwardable if it was set. However, this was painting with a very broad brush. Refine the logic so that we can fast-forward in the case where the restricted revision is equal to the merge base, since we stop rebasing at the merge base anyway. Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- builtin/rebase.c | 10 +++++++--- t/t3432-rebase-fast-forward.sh | 12 ++++++------ 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index fdb42eb09f..66c59ebe22 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -896,6 +896,7 @@ static int is_linear_history(struct commit *from, struct commit *to) } static int can_fast_forward(struct commit *onto, struct commit *upstream, + struct commit *restrict_revision, struct object_id *head_oid, struct object_id *merge_base) { struct commit *head = lookup_commit(the_repository, head_oid); @@ -915,6 +916,9 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream, if (!oideq(merge_base, &onto->object.oid)) goto done; + if (restrict_revision && !oideq(&restrict_revision->object.oid, merge_base)) + goto done; + if (!upstream) goto done; @@ -1703,9 +1707,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) * with new commits recreated by replaying their changes. This * optimization must not be done if this is an interactive rebase. */ - if (can_fast_forward(options.onto, options.upstream, &options.orig_head, - &merge_base) && - !is_interactive(&options) && !options.restrict_revision) { + if (can_fast_forward(options.onto, options.upstream, options.restrict_revision, + &options.orig_head, &merge_base) && + !is_interactive(&options)) { int flag; if (!(options.flags & REBASE_FORCE)) { diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index d0e5b1f3e6..1cb2896fb4 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -35,8 +35,8 @@ test_rebase_same_head success --onto B... B test_rebase_same_head success --onto master... master test_rebase_same_head success --no-fork-point test_rebase_same_head success --fork-point master -test_rebase_same_head failure --fork-point --onto B B -test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head success --fork-point --onto B B +test_rebase_same_head success --fork-point --onto B... B test_rebase_same_head success --fork-point --onto master... master test_expect_success 'add work to side' ' @@ -51,8 +51,8 @@ test_rebase_same_head success --onto B... B test_rebase_same_head success --onto master... master test_rebase_same_head success --no-fork-point test_rebase_same_head success --fork-point master -test_rebase_same_head failure --fork-point --onto B B -test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head success --fork-point --onto B B +test_rebase_same_head success --fork-point --onto B... B test_rebase_same_head success --fork-point --onto master... master test_expect_success 'add work to upstream' ' @@ -65,8 +65,8 @@ changes='our and their changes' test_rebase_same_head success --onto B B test_rebase_same_head success --onto B... B test_rebase_same_head success --onto master... master -test_rebase_same_head failure --fork-point --onto B B -test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head success --fork-point --onto B B +test_rebase_same_head success --fork-point --onto B... B test_rebase_same_head success --fork-point --onto master... master test_done -- 2.21.0.967.gf85e14fd49 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v7 6/6] rebase: teach rebase --keep-base 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu ` (4 preceding siblings ...) 2019-04-21 8:11 ` [PATCH v7 5/6] rebase: fast-forward --fork-point " Denton Liu @ 2019-04-21 8:11 ` Denton Liu 2019-05-08 0:12 ` [RFC WIP PATCH v8 00/13] learn --keep-base & more Ævar Arnfjörð Bjarmason ` (13 subsequent siblings) 19 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-04-21 8:11 UTC (permalink / raw) To: Git Mailing List Cc: Eric Sunshine, Junio C Hamano, Ævar Arnfjörð Bjarmason, Johannes Schindelin, Johannes Sixt, SZEDER Gábor A common scenario is if a user is working on a topic branch and they wish to make some changes to intermediate commits or autosquash, they would run something such as git rebase -i --onto master... master in order to preserve the merge base. This is useful when contributing a patch series to the Git mailing list, one often starts on top of the current 'master'. However, while developing the patches, 'master' is also developed further and it is sometimes not the bst idea to keep rebasing on top of 'master', but to keep the base commit as-is. Alternatively, a user wishing to test individual commits in a topic branch without changing anything may run git rebase -x ./test.sh master... master Since rebasing onto the merge base of the branch and the upstream is such a common case, introduce the --keep-base option as a shortcut. This allows us to rewrite the above as git rebase -i --keep-base master and git rebase -x ./test.sh --keep-base master respectively. Add tests to ensure --keep-base works correctly in the normal case and fails when there are multiple merge bases, both in regular and interactive mode. Also, test to make sure conflicting options cause rebase to fail. While we're adding test cases, add a missing set_fake_editor call to 'rebase -i --onto master...side'. While we're documenting the --keep-base option, change an instance of "merge-base" to "merge base", which is the consistent spelling. Helped-by: Eric Sunshine <sunshine@sunshineco.com> Helped-by: Junio C Hamano <gitster@pobox.com> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- Documentation/git-rebase.txt | 30 ++++++++++++-- builtin/rebase.c | 32 ++++++++++++--- contrib/completion/git-completion.bash | 2 +- t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++ t/t3431-rebase-fork-point.sh | 4 ++ t/t3432-rebase-fast-forward.sh | 11 +++++ 6 files changed, 126 insertions(+), 10 deletions(-) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 6363d674b7..569ab708d4 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip SYNOPSIS -------- [verse] -'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] - [<upstream> [<branch>]] +'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] + [--onto <newbase> | --keep-base] [<upstream> [<branch>]] 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] --root [<branch>] 'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch @@ -217,6 +217,24 @@ As a special case, you may use "A\...B" as a shortcut for the merge base of A and B if there is exactly one merge base. You can leave out at most one of A and B, in which case it defaults to HEAD. +--keep-base:: + Set the starting point at which to create the new commits to the + merge base of <upstream> <branch>. Running + 'git rebase --keep-base <upstream> <branch>' is equivalent to + running 'git rebase --onto <upstream>... <upstream>'. ++ +This option is useful in the case where one is developing a feature on +top of an upstream branch. While the feature is being worked on, the +upstream branch may advance and it may not be the best idea to keep +rebasing on top of the upstream but to keep the base commit as-is. ++ +Although both this option and --fork-point find the merge base between +<upstream> and <branch>, this option uses the merge base as the _starting +point_ on which new commits will be created, whereas --fork-point uses +the merge base to determine the _set of commits_ which will be rebased. ++ +See also INCOMPATIBLE OPTIONS below. + <upstream>:: Upstream branch to compare against. May be any valid commit, not just an existing branch name. Defaults to the configured @@ -364,6 +382,10 @@ ends up being empty, the <upstream> will be used as a fallback. + If either <upstream> or --root is given on the command line, then the default is `--no-fork-point`, otherwise the default is `--fork-point`. ++ +If your branch was based on <upstream> but <upstream> was rewound and +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:: --whitespace=<option>:: @@ -539,6 +561,8 @@ In addition, the following pairs of options are incompatible: * --preserve-merges and --rebase-merges * --rebase-merges and --strategy * --rebase-merges and --strategy-option + * --keep-base and --onto + * --keep-base and --root BEHAVIORAL DIFFERENCES ----------------------- @@ -863,7 +887,7 @@ NOTE: While an "easy case recovery" sometimes appears to be successful --interactive` will be **resurrected**! The idea is to manually tell 'git rebase' "where the old 'subsystem' -ended and your 'topic' began", that is, what the old merge-base +ended and your 'topic' began", that is, what the old merge base between them was. You will have to find a way to name the last commit of the old 'subsystem', for example: diff --git a/builtin/rebase.c b/builtin/rebase.c index 66c59ebe22..19e7e2a1c9 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -27,8 +27,8 @@ #include "branch.h" static char const * const builtin_rebase_usage[] = { - N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " - "[<upstream>] [<branch>]"), + N_("git rebase [-i] [options] [--exec <cmd>] " + "[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"), N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " "--root [<branch>]"), N_("git rebase --continue | --abort | --skip | --edit-todo"), @@ -1039,6 +1039,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) }; const char *branch_name; int ret, flags, total_argc, in_progress = 0; + int keep_base = 0; int ok_to_skip_pre_rebase = 0; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; @@ -1072,6 +1073,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_STRING(0, "onto", &options.onto_name, N_("revision"), N_("rebase onto given branch instead of upstream")), + OPT_BOOL(0, "keep-base", &keep_base, + N_("use the merge-base of upstream and branch as the current base")), OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase, N_("allow pre-rebase hook to run")), OPT_NEGBIT('q', "quiet", &options.flags, @@ -1238,6 +1241,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) usage_with_options(builtin_rebase_usage, builtin_rebase_options); + if (keep_base) { + if (options.onto_name) + die(_("cannot combine '--keep-base' with '--onto'")); + if (options.root) + die(_("cannot combine '--keep-base' with '--root'")); + } + if (action != NO_ACTION && !in_progress) die(_("No rebase in progress?")); setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0); @@ -1562,12 +1572,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } /* Make sure the branch to rebase onto is valid. */ - if (!options.onto_name) + if (keep_base) { + strbuf_reset(&buf); + strbuf_addstr(&buf, options.upstream_name); + strbuf_addstr(&buf, "..."); + options.onto_name = xstrdup(buf.buf); + } else if (!options.onto_name) options.onto_name = options.upstream_name; if (strstr(options.onto_name, "...")) { - if (get_oid_mb(options.onto_name, &merge_base) < 0) - die(_("'%s': need exactly one merge base"), - options.onto_name); + if (get_oid_mb(options.onto_name, &merge_base) < 0) { + if (keep_base) + die(_("'%s': need exactly one merge base with branch"), + options.upstream_name); + else + die(_("'%s': need exactly one merge base"), + options.onto_name); + } options.onto = lookup_commit_or_die(&merge_base, options.onto_name); } else { diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 976e4a6548..f9dc431a39 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -2027,7 +2027,7 @@ _git_rebase () --autosquash --no-autosquash --fork-point --no-fork-point --autostash --no-autostash - --verify --no-verify + --verify --no-verify --keep-base --keep-empty --root --force-rebase --no-ff --rerere-autoupdate --exec diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh index ddf2f64853..9c2548423b 100755 --- a/t/t3416-rebase-onto-threedots.sh +++ b/t/t3416-rebase-onto-threedots.sh @@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' ' git checkout side && git reset --hard K && + set_fake_editor && test_must_fail git rebase -i --onto master...side J ' +test_expect_success 'rebase --keep-base --onto incompatible' ' + test_must_fail git rebase --keep-base --onto master... +' + +test_expect_success 'rebase --keep-base --root incompatible' ' + test_must_fail git rebase --keep-base --root +' + +test_expect_success 'rebase --keep-base master from topic' ' + git reset --hard && + git checkout topic && + git reset --hard G && + + git rebase --keep-base master && + git rev-parse C >base.expect && + git merge-base master HEAD >base.actual && + test_cmp base.expect base.actual && + + git rev-parse HEAD~2 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase --keep-base master from side' ' + git reset --hard && + git checkout side && + git reset --hard K && + + test_must_fail git rebase --keep-base master +' + +test_expect_success 'rebase -i --keep-base master from topic' ' + git reset --hard && + git checkout topic && + git reset --hard G && + + set_fake_editor && + EXPECT_COUNT=2 git rebase -i --keep-base master && + git rev-parse C >base.expect && + git merge-base master HEAD >base.actual && + test_cmp base.expect base.actual && + + git rev-parse HEAD~2 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase -i --keep-base master from side' ' + git reset --hard && + git checkout side && + git reset --hard K && + + set_fake_editor && + test_must_fail git rebase -i --keep-base master +' + test_done diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh index 9b517d87a3..f848946f5a 100755 --- a/t/t3431-rebase-fork-point.sh +++ b/t/t3431-rebase-fork-point.sh @@ -43,11 +43,15 @@ test_rebase() { test_rebase 'G F E D B A' test_rebase 'G F D B A' --onto D +test_rebase 'G F B A' --keep-base test_rebase 'G F C E D B A' --no-fork-point test_rebase 'G F C D B A' --no-fork-point --onto D +test_rebase 'G F C B A' --no-fork-point --keep-base test_rebase 'G F E D B A' --fork-point refs/heads/master test_rebase 'G F D B A' --fork-point --onto D refs/heads/master +test_rebase 'G F B A' --fork-point --keep-base refs/heads/master test_rebase 'G F C E D B A' refs/heads/master test_rebase 'G F C D B A' --onto D refs/heads/master +test_rebase 'G F C B A' --keep-base refs/heads/master test_done diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index 1cb2896fb4..1ba02cc95f 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -33,11 +33,15 @@ test_rebase_same_head success master test_rebase_same_head success --onto B B test_rebase_same_head success --onto B... B test_rebase_same_head success --onto master... master +test_rebase_same_head success --keep-base master +test_rebase_same_head success --keep-base test_rebase_same_head success --no-fork-point +test_rebase_same_head success --keep-base --no-fork-point test_rebase_same_head success --fork-point master test_rebase_same_head success --fork-point --onto B B test_rebase_same_head success --fork-point --onto B... B test_rebase_same_head success --fork-point --onto master... master +test_rebase_same_head success --fork-point --keep-base master test_expect_success 'add work to side' ' test_commit E @@ -49,11 +53,15 @@ test_rebase_same_head success master test_rebase_same_head success --onto B B test_rebase_same_head success --onto B... B test_rebase_same_head success --onto master... master +test_rebase_same_head success --keep-base master +test_rebase_same_head success --keep-base test_rebase_same_head success --no-fork-point +test_rebase_same_head success --keep-base --no-fork-point test_rebase_same_head success --fork-point master test_rebase_same_head success --fork-point --onto B B test_rebase_same_head success --fork-point --onto B... B test_rebase_same_head success --fork-point --onto master... master +test_rebase_same_head success --fork-point --keep-base master test_expect_success 'add work to upstream' ' git checkout master && @@ -65,8 +73,11 @@ changes='our and their changes' test_rebase_same_head success --onto B B test_rebase_same_head success --onto B... B test_rebase_same_head success --onto master... master +test_rebase_same_head success --keep-base master +test_rebase_same_head success --keep-base test_rebase_same_head success --fork-point --onto B B test_rebase_same_head success --fork-point --onto B... B test_rebase_same_head success --fork-point --onto master... master +test_rebase_same_head success --fork-point --keep-base master test_done -- 2.21.0.967.gf85e14fd49 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [RFC WIP PATCH v8 00/13] learn --keep-base & more 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu ` (5 preceding siblings ...) 2019-04-21 8:11 ` [PATCH v7 6/6] rebase: teach rebase --keep-base Denton Liu @ 2019-05-08 0:12 ` Ævar Arnfjörð Bjarmason 2019-05-08 3:57 ` Junio C Hamano 2019-08-25 9:11 ` [PATCH v9 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour Denton Liu 2019-05-08 0:12 ` [RFC WIP PATCH v8 01/13] t3431: add rebase --fork-point tests Ævar Arnfjörð Bjarmason ` (12 subsequent siblings) 19 siblings, 2 replies; 123+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 UTC (permalink / raw) To: git Cc: Junio C Hamano, Denton Liu, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason This is a WIP series I have that I figured I'd send out as-is for comment since Junio said he'd be merging dl/rebase-i-keep-base down. So I wanted to test it, and as seen early in this series in 3/13 and 4/13 we had significant blindspots in our tests, i.e. there were no tests for whether --no-ff bypassed the amended logic as it should. As seen from those tests we may have some bugs here, either existing or new, needs more poking at it. We are also inconsistent with whether a --no-ff yields a different SHA-1 after the forced rebase, surely it always should, no? Then in 9/13 and 10/13 I re-added the incomplete patches I had in https://public-inbox.org/git/20190221214059.9195-1-avarab@gmail.com/ to see if my tests passed with Denton's --fork-point code, they do. Yay! Left them there because I was wondering if I needed to port some/all of the tests over, and maybe amend a commit message to reword some of my findings in https://public-inbox.org/git/871s3z6a4q.fsf@evledraar.gmail.com/ Then I have 11/13 and 12/13 which seem pretty sensible to me as-is, and finally I wanted --preserve-merges and --rebase-merges to also benefit from this logic, so 13/13 is a WIP patch for that. The code should be done (although maybe there's a better way to do it...), but it needs a better commit message & tests. Denton Liu (6): t3431: add rebase --fork-point tests t3432: test rebase fast-forward behavior rebase: refactor can_fast_forward into goto tower rebase: fast-forward --onto in more cases rebase: fast-forward --fork-point in more cases rebase: teach rebase --keep-base Ævar Arnfjörð Bjarmason (7): t3432: distinguish "noop-same" v.s. "work-same" in "same head" tests t3432: test for --no-ff's interaction with fast-forward rebase tests: test linear branch topology rebase: don't rebase linear topology with --fork-point rebase: eliminate side-effects from can_fast_forward() rebase: add a should_fast_forward() utility function WIP: can_fast_forward() support for --preserve-merges and --rebase-merges Documentation/git-rebase.txt | 30 +++++- builtin/rebase.c | 117 +++++++++++++++++----- contrib/completion/git-completion.bash | 2 +- t/t3400-rebase.sh | 2 +- t/t3404-rebase-interactive.sh | 2 +- t/t3416-rebase-onto-threedots.sh | 57 +++++++++++ t/t3421-rebase-topology-linear.sh | 44 +++++++++ t/t3431-rebase-fork-point.sh | 57 +++++++++++ t/t3432-rebase-fast-forward.sh | 128 +++++++++++++++++++++++++ 9 files changed, 407 insertions(+), 32 deletions(-) create mode 100755 t/t3431-rebase-fork-point.sh create mode 100755 t/t3432-rebase-fast-forward.sh Range-diff: 1: aececab3b4 = 1: 0c931f6275 t3431: add rebase --fork-point tests 2: 238bf1db83 = 2: b5bdca58db t3432: test rebase fast-forward behavior -: ---------- > 3: 5d057a1240 t3432: distinguish "noop-same" v.s. "work-same" in "same head" tests -: ---------- > 4: 06084d9891 t3432: test for --no-ff's interaction with fast-forward 3: 526c03b5a9 = 5: f5a2172398 rebase: refactor can_fast_forward into goto tower 4: 10572de16f ! 6: ea90934368 rebase: fast-forward --onto in more cases @@ -146,13 +146,13 @@ +++ b/t/t3432-rebase-fast-forward.sh @@ changes='our and their changes' - test_rebase_same_head success --onto B B - test_rebase_same_head success --onto B... B --test_rebase_same_head failure --onto master... master -+test_rebase_same_head success --onto master... master - test_rebase_same_head failure --fork-point --onto B B - test_rebase_same_head failure --fork-point --onto B... B --test_rebase_same_head failure --fork-point --onto master... master -+test_rebase_same_head success --fork-point --onto master... 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 failure work same success work diff --onto master... master ++test_rebase_same_head success noop same success work diff --onto master... master + 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 failure work same success work diff --fork-point --onto master... master ++test_rebase_same_head success noop same success work diff --fork-point --onto master... master test_done 5: 446985193c ! 7: 46660cefe0 rebase: fast-forward --fork-point in more cases @@ -55,35 +55,45 @@ --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ - test_rebase_same_head success --onto master... master - test_rebase_same_head success --no-fork-point - test_rebase_same_head success --fork-point master --test_rebase_same_head failure --fork-point --onto B B --test_rebase_same_head failure --fork-point --onto B... B -+test_rebase_same_head success --fork-point --onto B B -+test_rebase_same_head success --fork-point --onto B... B - test_rebase_same_head success --fork-point --onto master... master + } - test_expect_success 'add work to side' ' -@@ - test_rebase_same_head success --onto master... master - test_rebase_same_head success --no-fork-point - test_rebase_same_head success --fork-point master --test_rebase_same_head failure --fork-point --onto B B --test_rebase_same_head failure --fork-point --onto B... B -+test_rebase_same_head success --fork-point --onto B B -+test_rebase_same_head success --fork-point --onto B... B - test_rebase_same_head success --fork-point --onto master... master + changes='no changes' +-test_rebase_same_head success work same success work same ++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 --no-fork-point +-test_rebase_same_head success work same success work same --fork-point master +-test_rebase_same_head failure noop 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 same --fork-point --onto master... master ++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 ++test_rebase_same_head success noop same success work same --fork-point --onto master... master - test_expect_success 'add work to upstream' ' -@@ - test_rebase_same_head success --onto B B - test_rebase_same_head success --onto B... B - test_rebase_same_head success --onto master... master --test_rebase_same_head failure --fork-point --onto B B --test_rebase_same_head failure --fork-point --onto B... B -+test_rebase_same_head success --fork-point --onto B B -+test_rebase_same_head success --fork-point --onto B... B - test_rebase_same_head success --fork-point --onto master... master + test_expect_success 'add work same to side' ' + test_commit E + ' + + changes='our changes' +-test_rebase_same_head success work same success work same ++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 --no-fork-point +-test_rebase_same_head success work same success work same --fork-point master +-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 same --fork-point --onto master... master ++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 ++test_rebase_same_head success noop same success work same --fork-point --onto master... master - test_done + test_expect_success 'add work same to upstream' ' + git checkout master && 6: 88096495c2 ! 8: a59ff76704 rebase: teach rebase --keep-base @@ -149,8 +149,8 @@ N_("allow pre-rebase hook to run")), OPT_NEGBIT('q', "quiet", &options.flags, @@ - usage_with_options(builtin_rebase_usage, - builtin_rebase_options); + warning(_("git rebase --preserve-merges is deprecated. " + "Use --rebase-merges instead.")); + if (keep_base) { + if (options.onto_name) @@ -296,46 +296,46 @@ --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ - test_rebase_same_head success --onto B B - test_rebase_same_head success --onto B... B - test_rebase_same_head success --onto master... master -+test_rebase_same_head success --keep-base master -+test_rebase_same_head success --keep-base - test_rebase_same_head success --no-fork-point -+test_rebase_same_head success --keep-base --no-fork-point - test_rebase_same_head success --fork-point master - test_rebase_same_head success --fork-point --onto B B - test_rebase_same_head success --fork-point --onto B... B - test_rebase_same_head success --fork-point --onto master... master -+test_rebase_same_head success --fork-point --keep-base 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 --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 + test_rebase_same_head success noop same success work same --fork-point --onto master... master ++test_rebase_same_head success noop same success work same --keep-base --keep-base master - test_expect_success 'add work to side' ' + test_expect_success 'add work same to side' ' test_commit E @@ - test_rebase_same_head success --onto B B - test_rebase_same_head success --onto B... B - test_rebase_same_head success --onto master... master -+test_rebase_same_head success --keep-base master -+test_rebase_same_head success --keep-base - test_rebase_same_head success --no-fork-point -+test_rebase_same_head success --keep-base --no-fork-point - test_rebase_same_head success --fork-point master - test_rebase_same_head success --fork-point --onto B B - test_rebase_same_head success --fork-point --onto B... B - test_rebase_same_head success --fork-point --onto master... master -+test_rebase_same_head success --fork-point --keep-base 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 --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 + test_rebase_same_head success noop same success work same --fork-point --onto master... master ++test_rebase_same_head success noop same success work same --fork-point --keep-base master - test_expect_success 'add work to upstream' ' + test_expect_success 'add work same to upstream' ' git checkout master && @@ - test_rebase_same_head success --onto B B - test_rebase_same_head success --onto B... B - test_rebase_same_head success --onto master... master -+test_rebase_same_head success --keep-base master -+test_rebase_same_head success --keep-base - test_rebase_same_head success --fork-point --onto B B - test_rebase_same_head success --fork-point --onto B... B - test_rebase_same_head success --fork-point --onto master... master -+test_rebase_same_head success --fork-point --keep-base 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 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 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 test_done -: ---------- > 9: 46ccfca308 rebase tests: test linear branch topology -: ---------- > 10: b06e84c6d6 rebase: don't rebase linear topology with --fork-point -: ---------- > 11: bf13aa8a80 rebase: eliminate side-effects from can_fast_forward() -: ---------- > 12: 2ffbb6c342 rebase: add a should_fast_forward() utility function -: ---------- > 13: 20c38b78c7 WIP: can_fast_forward() support for --preserve-merges and --rebase-merges -- 2.21.0.1020.gf2820cf01a ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [RFC WIP PATCH v8 00/13] learn --keep-base & more 2019-05-08 0:12 ` [RFC WIP PATCH v8 00/13] learn --keep-base & more Ævar Arnfjörð Bjarmason @ 2019-05-08 3:57 ` Junio C Hamano 2019-07-19 19:14 ` Junio C Hamano 2019-08-25 9:11 ` [PATCH v9 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour Denton Liu 1 sibling, 1 reply; 123+ messages in thread From: Junio C Hamano @ 2019-05-08 3:57 UTC (permalink / raw) To: Ævar Arnfjörð Bjarmason Cc: git, Denton Liu, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes: > This is a WIP series I have that I figured I'd send out as-is for > comment since Junio said he'd be merging dl/rebase-i-keep-base down. > > So I wanted to test it, and as seen early in this series in 3/13 and > 4/13 we had significant blindspots in our tests, i.e. there were no > tests for whether --no-ff bypassed the amended logic as it should. > > As seen from those tests we may have some bugs here, either existing > or new, needs more poking at it. Thanks. I am actually OK to keep dl/rebase-i-keep-base out of 'next' to iron out the kinks. It's not like we are in a hurry to deliber an important fix to our users---the topic is adding a new feature and attempting to fix a minor irritation (i.e. lost opportunity to fast-forward). > Then in 9/13 and 10/13 I re-added the incomplete patches I had in > https://public-inbox.org/git/20190221214059.9195-1-avarab@gmail.com/ > to see if my tests passed with Denton's --fork-point code, they > do. Yay! > > Left them there because I was wondering if I needed to port some/all > of the tests over, and maybe amend a commit message to reword some of > my findings in > https://public-inbox.org/git/871s3z6a4q.fsf@evledraar.gmail.com/ > > Then I have 11/13 and 12/13 which seem pretty sensible to me as-is, > and finally I wanted --preserve-merges and --rebase-merges to also > benefit from this logic, so 13/13 is a WIP patch for that. The code > should be done (although maybe there's a better way to do it...), but > it needs a better commit message & tests. ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [RFC WIP PATCH v8 00/13] learn --keep-base & more 2019-05-08 3:57 ` Junio C Hamano @ 2019-07-19 19:14 ` Junio C Hamano 2019-07-19 21:01 ` Denton Liu 0 siblings, 1 reply; 123+ messages in thread From: Junio C Hamano @ 2019-07-19 19:14 UTC (permalink / raw) To: Ævar Arnfjörð Bjarmason Cc: git, Denton Liu, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor Junio C Hamano <gitster@pobox.com> writes: > Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes: > >> This is a WIP series I have that I figured I'd send out as-is for >> comment since Junio said he'd be merging dl/rebase-i-keep-base down. >> >> So I wanted to test it, and as seen early in this series in 3/13 and >> 4/13 we had significant blindspots in our tests, i.e. there were no >> tests for whether --no-ff bypassed the amended logic as it should. >> >> As seen from those tests we may have some bugs here, either existing >> or new, needs more poking at it. > > Thanks. I am actually OK to keep dl/rebase-i-keep-base out of > 'next' to iron out the kinks. It's not like we are in a hurry to > deliber an important fix to our users---the topic is adding a new > feature and attempting to fix a minor irritation (i.e. lost > opportunity to fast-forward). The 'next' branch has thinned down sufficiently that it may be a good time to resurrect topics like this one that we postponed. If there is (still) interest in them, that is, which I do not know ;-) ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [RFC WIP PATCH v8 00/13] learn --keep-base & more 2019-07-19 19:14 ` Junio C Hamano @ 2019-07-19 21:01 ` Denton Liu 0 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-07-19 21:01 UTC (permalink / raw) To: Junio C Hamano Cc: Ævar Arnfjörð Bjarmason, git, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor On Fri, Jul 19, 2019 at 12:14:49PM -0700, Junio C Hamano wrote: > Junio C Hamano <gitster@pobox.com> writes: > > > Ævar Arnfjörð Bjarmason <avarab@gmail.com> writes: > > > >> This is a WIP series I have that I figured I'd send out as-is for > >> comment since Junio said he'd be merging dl/rebase-i-keep-base down. > >> > >> So I wanted to test it, and as seen early in this series in 3/13 and > >> 4/13 we had significant blindspots in our tests, i.e. there were no > >> tests for whether --no-ff bypassed the amended logic as it should. > >> > >> As seen from those tests we may have some bugs here, either existing > >> or new, needs more poking at it. > > > > Thanks. I am actually OK to keep dl/rebase-i-keep-base out of > > 'next' to iron out the kinks. It's not like we are in a hurry to > > deliber an important fix to our users---the topic is adding a new > > feature and attempting to fix a minor irritation (i.e. lost > > opportunity to fast-forward). > > The 'next' branch has thinned down sufficiently that it may be a > good time to resurrect topics like this one that we postponed. If > there is (still) interest in them, that is, which I do not know ;-) > I'm definitely still interested in this topic but I haven't had much time to contribute since school's been busy lately. If Ævar doesn't get to it by around a month from now, I'll polish up the RFC and resubmit. ^ permalink raw reply [flat|nested] 123+ messages in thread
* [PATCH v9 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour 2019-05-08 0:12 ` [RFC WIP PATCH v8 00/13] learn --keep-base & more Ævar Arnfjörð Bjarmason 2019-05-08 3:57 ` Junio C Hamano @ 2019-08-25 9:11 ` Denton Liu 2019-08-25 9:11 ` [PATCH v9 1/9] t3431: add rebase --fork-point tests Denton Liu ` (10 more replies) 1 sibling, 11 replies; 123+ messages in thread From: Denton Liu @ 2019-08-25 9:11 UTC (permalink / raw) To: Git Mailing List Cc: Junio C Hamano, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason Hi all, it's been a while but I guess now's a good time as any to resurrect this topic. This is basically a resubmission of Ævar's WIP v8 but I fixed a couple of minor whitespace issues. In addition, I opted to drop patches 9-13 from v8 since I don't think I can do a good enough job polishing them up and I don't really understand the intricacies of this part of the rebase code. Hopefully, Ævar will pick them up at a later time. Changes since v1: * Squashed old set into one patch * Fixed indentation style and dangling else * Added more documentation after discussion with Ævar Changes since v2: * Add testing for rebase --fork-point behaviour * Add testing for rebase fast-forward behaviour * Make rebase --onto fast-forward in more cases * Update documentation to include use-case Changes since v3: * Fix tests failing on bash 4.2 * Fix typo in t3431 comment Changes since v4: * Make rebase --fork-point fast-forward in more cases Changes since v5: * Fix graph illustrations so that the "branch off" is visually in the correct place * Refactor if-else in can_fast_forward into one-level-deep ifs to increase clarity Changes since v6: * Remove redundant braces around if * Update comment around can_fast_forward call * Add completion for rebase Changes since v7: * Ævar sent in his WIP patchset Changes since v8: * Drop patches 9-13 * Fix some minor whitespace issues from v7 [1]: https://public-inbox.org/git/a1b4b74b9167e279dae4cd8c58fb28d8a714a66a.1553537656.git.gitgitgadget@gmail.com/ Denton Liu (6): t3431: add rebase --fork-point tests t3432: test rebase fast-forward behavior rebase: refactor can_fast_forward into goto tower rebase: fast-forward --onto in more cases rebase: fast-forward --fork-point in more cases rebase: teach rebase --keep-base Ævar Arnfjörð Bjarmason (3): t3432: distinguish "noop-same" v.s. "work-same" in "same head" tests t3432: test for --no-ff's interaction with fast-forward rebase tests: test linear branch topology Documentation/git-rebase.txt | 30 +++++- builtin/rebase.c | 85 ++++++++++++----- contrib/completion/git-completion.bash | 2 +- t/t3400-rebase.sh | 2 +- t/t3404-rebase-interactive.sh | 2 +- t/t3416-rebase-onto-threedots.sh | 57 +++++++++++ t/t3421-rebase-topology-linear.sh | 29 ++++++ t/t3431-rebase-fork-point.sh | 57 +++++++++++ t/t3432-rebase-fast-forward.sh | 125 +++++++++++++++++++++++++ 9 files changed, 361 insertions(+), 28 deletions(-) create mode 100755 t/t3431-rebase-fork-point.sh create mode 100755 t/t3432-rebase-fast-forward.sh Range-diff against v8: 1: eb64f6c91d ! 1: 03f769d410 t3431: add rebase --fork-point tests @@ t/t3431-rebase-fork-point.sh (new) + test_commit G +' + -+test_rebase() { ++test_rebase () { + expected="$1" && + shift && + test_expect_success "git rebase $*" " 2: 4c087ec041 ! 2: bc8998079d t3432: test rebase fast-forward behavior @@ t/t3432-rebase-fast-forward.sh (new) + git checkout -t -b side +' + -+test_rebase_same_head() { ++test_rebase_same_head () { + status="$1" && + shift && + test_expect_$status "git rebase $* with $changes is no-op" " -: ---------- > 3: 5c08e2b81f t3432: distinguish "noop-same" v.s. "work-same" in "same head" tests -: ---------- > 4: 48b4e41a17 t3432: test for --no-ff's interaction with fast-forward 3: 3d348d2c37 = 5: 9bd34b4a40 rebase: refactor can_fast_forward into goto tower 4: 27cbcfaeae ! 6: becb924232 rebase: fast-forward --onto in more cases @@ Commit message Add '-f' to test cases that failed as a result of this change because they were not expecting a fast-forward so that a rebase is forced. - While we're at it, remove a trailing whitespace from rebase.c. - Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk> Signed-off-by: Denton Liu <liu.denton@gmail.com> @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix int flag; if (!(options.flags & REBASE_FORCE)) { -@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix) - strbuf_addf(&msg, "%s: checkout %s", - getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name); - if (reset_head(&options.onto->object.oid, "checkout", NULL, -- RESET_HEAD_DETACH | RESET_ORIG_HEAD | -+ RESET_HEAD_DETACH | RESET_ORIG_HEAD | - RESET_HEAD_RUN_POST_CHECKOUT_HOOK, - NULL, msg.buf)) - die(_("Could not detach HEAD")); ## t/t3400-rebase.sh ## -@@ t/t3400-rebase.sh: test_expect_success 'rebase--am.sh and --show-current-patch' ' +@@ t/t3400-rebase.sh: test_expect_success 'rebase --am and --show-current-patch' ' echo two >>init.t && git commit -a -m two && git tag two && @@ t/t3404-rebase-interactive.sh: test_expect_success C_LOCALE_OUTPUT 'rebase --edi ' ## t/t3432-rebase-fast-forward.sh ## -@@ t/t3432-rebase-fast-forward.sh: test_expect_success 'add work to upstream' ' +@@ t/t3432-rebase-fast-forward.sh: test_expect_success 'add work same to upstream' ' changes='our and their changes' - test_rebase_same_head success --onto B B - test_rebase_same_head success --onto B... B --test_rebase_same_head failure --onto master... master -+test_rebase_same_head success --onto master... master - test_rebase_same_head failure --fork-point --onto B B - test_rebase_same_head failure --fork-point --onto B... B --test_rebase_same_head failure --fork-point --onto master... master -+test_rebase_same_head success --fork-point --onto master... 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 failure work same success work diff --onto master... master ++test_rebase_same_head success noop same success work diff --onto master... master + 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 failure work same success work diff --fork-point --onto master... master ++test_rebase_same_head success noop same success work diff --fork-point --onto master... master test_done 5: 8254730810 ! 7: 4dab5847cb rebase: fast-forward --fork-point in more cases @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix if (!(options.flags & REBASE_FORCE)) { ## t/t3432-rebase-fast-forward.sh ## -@@ t/t3432-rebase-fast-forward.sh: test_rebase_same_head success --onto B... B - test_rebase_same_head success --onto master... master - test_rebase_same_head success --no-fork-point - test_rebase_same_head success --fork-point master --test_rebase_same_head failure --fork-point --onto B B --test_rebase_same_head failure --fork-point --onto B... B -+test_rebase_same_head success --fork-point --onto B B -+test_rebase_same_head success --fork-point --onto B... B - test_rebase_same_head success --fork-point --onto master... master +@@ t/t3432-rebase-fast-forward.sh: test_rebase_same_head_ () { + } + + changes='no changes' +-test_rebase_same_head success work same success work same ++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 --no-fork-point +-test_rebase_same_head success work same success work same --fork-point master +-test_rebase_same_head failure noop 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 same --fork-point --onto master... master ++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 ++test_rebase_same_head success noop same success work same --fork-point --onto master... master - test_expect_success 'add work to side' ' -@@ t/t3432-rebase-fast-forward.sh: test_rebase_same_head success --onto B... B - test_rebase_same_head success --onto master... master - test_rebase_same_head success --no-fork-point - test_rebase_same_head success --fork-point master --test_rebase_same_head failure --fork-point --onto B B --test_rebase_same_head failure --fork-point --onto B... B -+test_rebase_same_head success --fork-point --onto B B -+test_rebase_same_head success --fork-point --onto B... B - test_rebase_same_head success --fork-point --onto master... master + test_expect_success 'add work same to side' ' + test_commit E + ' - test_expect_success 'add work to upstream' ' -@@ t/t3432-rebase-fast-forward.sh: changes='our and their changes' - test_rebase_same_head success --onto B B - test_rebase_same_head success --onto B... B - test_rebase_same_head success --onto master... master --test_rebase_same_head failure --fork-point --onto B B --test_rebase_same_head failure --fork-point --onto B... B -+test_rebase_same_head success --fork-point --onto B B -+test_rebase_same_head success --fork-point --onto B... B - test_rebase_same_head success --fork-point --onto master... master + changes='our changes' +-test_rebase_same_head success work same success work same ++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 --no-fork-point +-test_rebase_same_head success work same success work same --fork-point master +-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 same --fork-point --onto master... master ++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 ++test_rebase_same_head success noop same success work same --fork-point --onto master... master - test_done + test_expect_success 'add work same to upstream' ' + git checkout master && -: ---------- > 8: 4699a90993 rebase tests: test linear branch topology 6: f18d3ee3fa ! 9: 6927aba617 rebase: teach rebase --keep-base @@ Commit message in order to preserve the merge base. This is useful when contributing a patch series to the Git mailing list, one often starts on top of the current 'master'. However, while developing the patches, 'master' is - also developed further and it is sometimes not the bst idea to keep + also developed further and it is sometimes not the best idea to keep rebasing on top of 'master', but to keep the base commit as-is. Alternatively, a user wishing to test individual commits in a topic @@ Documentation/git-rebase.txt: git-rebase - Reapply commits on top of another bas + [--onto <newbase> | --keep-base] [<upstream> [<branch>]] 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] --root [<branch>] - 'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch + 'git rebase' (--continue | --skip | --abort | --quit | --edit-todo | --show-current-patch) @@ Documentation/git-rebase.txt: As a special case, you may use "A\...B" as a shortcut for the merge base of A and B if there is exactly one merge base. You can leave out at most one of A and B, in which case it defaults to HEAD. @@ Documentation/git-rebase.txt: NOTE: While an "easy case recovery" sometimes appe ## builtin/rebase.c ## @@ - #include "branch.h" + #include "rebase-interactive.h" static char const * const builtin_rebase_usage[] = { - N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " @@ builtin/rebase.c "--root [<branch>]"), N_("git rebase --continue | --abort | --skip | --edit-todo"), @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix) - }; + struct rebase_options options = REBASE_OPTIONS_INIT; const char *branch_name; int ret, flags, total_argc, in_progress = 0; + int keep_base = 0; @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix N_("allow pre-rebase hook to run")), OPT_NEGBIT('q', "quiet", &options.flags, @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix) - usage_with_options(builtin_rebase_usage, - builtin_rebase_options); + warning(_("git rebase --preserve-merges is deprecated. " + "Use --rebase-merges instead.")); + if (keep_base) { + if (options.onto_name) @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix + die(_("cannot combine '--keep-base' with '--root'")); + } + - if (action != NO_ACTION && !in_progress) + if (action != ACTION_NONE && !in_progress) die(_("No rebase in progress?")); setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0); @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix) @@ t/t3416-rebase-onto-threedots.sh: test_expect_success 'rebase -i --onto master.. test_done ## t/t3431-rebase-fork-point.sh ## -@@ t/t3431-rebase-fork-point.sh: test_rebase() { +@@ t/t3431-rebase-fork-point.sh: test_rebase () { test_rebase 'G F E D B A' test_rebase 'G F D B A' --onto D @@ t/t3431-rebase-fork-point.sh: test_rebase() { test_done ## t/t3432-rebase-fast-forward.sh ## -@@ t/t3432-rebase-fast-forward.sh: test_rebase_same_head success master - test_rebase_same_head success --onto B B - test_rebase_same_head success --onto B... B - test_rebase_same_head success --onto master... master -+test_rebase_same_head success --keep-base master -+test_rebase_same_head success --keep-base - test_rebase_same_head success --no-fork-point -+test_rebase_same_head success --keep-base --no-fork-point - test_rebase_same_head success --fork-point master - test_rebase_same_head success --fork-point --onto B B - test_rebase_same_head success --fork-point --onto B... B - test_rebase_same_head success --fork-point --onto master... master -+test_rebase_same_head success --fork-point --keep-base master +@@ t/t3432-rebase-fast-forward.sh: 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 --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 + test_rebase_same_head success noop same success work same --fork-point --onto master... master ++test_rebase_same_head success noop same success work same --keep-base --keep-base master - test_expect_success 'add work to side' ' + test_expect_success 'add work same to side' ' test_commit E -@@ t/t3432-rebase-fast-forward.sh: test_rebase_same_head success master - test_rebase_same_head success --onto B B - test_rebase_same_head success --onto B... B - test_rebase_same_head success --onto master... master -+test_rebase_same_head success --keep-base master -+test_rebase_same_head success --keep-base - test_rebase_same_head success --no-fork-point -+test_rebase_same_head success --keep-base --no-fork-point - test_rebase_same_head success --fork-point master - test_rebase_same_head success --fork-point --onto B B - test_rebase_same_head success --fork-point --onto B... B - test_rebase_same_head success --fork-point --onto master... master -+test_rebase_same_head success --fork-point --keep-base master +@@ t/t3432-rebase-fast-forward.sh: 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 --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 + test_rebase_same_head success noop same success work same --fork-point --onto master... master ++test_rebase_same_head success noop same success work same --fork-point --keep-base master - test_expect_success 'add work to upstream' ' + test_expect_success 'add work same to upstream' ' git checkout master && @@ t/t3432-rebase-fast-forward.sh: changes='our and their changes' - test_rebase_same_head success --onto B B - test_rebase_same_head success --onto B... B - test_rebase_same_head success --onto master... master -+test_rebase_same_head success --keep-base master -+test_rebase_same_head success --keep-base - test_rebase_same_head success --fork-point --onto B B - test_rebase_same_head success --fork-point --onto B... B - test_rebase_same_head success --fork-point --onto master... master -+test_rebase_same_head success --fork-point --keep-base 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 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 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 test_done -- 2.23.0.248.g3a9dd8fb08 ^ permalink raw reply [flat|nested] 123+ messages in thread
* [PATCH v9 1/9] t3431: add rebase --fork-point tests 2019-08-25 9:11 ` [PATCH v9 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour Denton Liu @ 2019-08-25 9:11 ` Denton Liu 2019-08-25 9:12 ` [PATCH v9 2/9] t3432: test rebase fast-forward behavior Denton Liu ` (9 subsequent siblings) 10 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-08-25 9:11 UTC (permalink / raw) To: Git Mailing List Cc: Junio C Hamano, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason Signed-off-by: Denton Liu <liu.denton@gmail.com> --- t/t3431-rebase-fork-point.sh | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100755 t/t3431-rebase-fork-point.sh diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh new file mode 100755 index 0000000000..2d5c6e641e --- /dev/null +++ b/t/t3431-rebase-fork-point.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# +# Copyright (c) 2019 Denton Liu +# + +test_description='git rebase --fork-point test' + +. ./test-lib.sh + +# A---B---D---E (master) +# \ +# C*---F---G (side) +# +# C was formerly part of master but master was rewound to remove C +# +test_expect_success setup ' + test_commit A && + test_commit B && + test_commit C && + git branch -t side && + git reset --hard HEAD^ && + test_commit D && + test_commit E && + git checkout side && + test_commit F && + test_commit G +' + +test_rebase () { + expected="$1" && + shift && + test_expect_success "git rebase $*" " + git checkout master && + git reset --hard E && + git checkout side && + git reset --hard G && + git rebase $* && + test_write_lines $expected >expect && + git log --pretty=%s >actual && + test_cmp expect actual + " +} + +test_rebase 'G F E D B A' +test_rebase 'G F D B A' --onto D +test_rebase 'G F C E D B A' --no-fork-point +test_rebase 'G F C D B A' --no-fork-point --onto D +test_rebase 'G F E D B A' --fork-point refs/heads/master +test_rebase 'G F D B A' --fork-point --onto D refs/heads/master +test_rebase 'G F C E D B A' refs/heads/master +test_rebase 'G F C D B A' --onto D refs/heads/master + +test_done -- 2.23.0.248.g3a9dd8fb08 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v9 2/9] t3432: test rebase fast-forward behavior 2019-08-25 9:11 ` [PATCH v9 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour Denton Liu 2019-08-25 9:11 ` [PATCH v9 1/9] t3431: add rebase --fork-point tests Denton Liu @ 2019-08-25 9:12 ` Denton Liu 2019-08-25 9:12 ` [PATCH v9 3/9] t3432: distinguish "noop-same" v.s. "work-same" in "same head" tests Denton Liu ` (8 subsequent siblings) 10 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-08-25 9:12 UTC (permalink / raw) To: Git Mailing List Cc: Junio C Hamano, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason When rebase is run on a branch that can be fast-forwarded, this should automatically be done. Create test to ensure this behavior happens. There are some cases that currently don't pass. The first case is where a feature and master have diverged, running "git rebase master... master" causes a full rebase to happen even though a fast-forward should happen. The second case is when we are doing "git rebase --fork-point" and a fork-point commit is found. Once again, a full rebase happens even though a fast-forward should happen. Mark these cases as failure so we can fix it later. Signed-off-by: Denton Liu <liu.denton@gmail.com> --- t/t3432-rebase-fast-forward.sh | 72 ++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100755 t/t3432-rebase-fast-forward.sh diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh new file mode 100755 index 0000000000..f49af274e0 --- /dev/null +++ b/t/t3432-rebase-fast-forward.sh @@ -0,0 +1,72 @@ +#!/bin/sh +# +# Copyright (c) 2019 Denton Liu +# + +test_description='ensure rebase fast-forwards commits when possible' + +. ./test-lib.sh + +test_expect_success setup ' + test_commit A && + test_commit B && + test_commit C && + test_commit D && + git checkout -t -b side +' + +test_rebase_same_head () { + status="$1" && + shift && + test_expect_$status "git rebase $* with $changes is no-op" " + oldhead=\$(git rev-parse HEAD) && + test_when_finished 'git reset --hard \$oldhead' && + git rebase $* && + newhead=\$(git rev-parse HEAD) && + test_cmp_rev \$oldhead \$newhead + " +} + +changes='no changes' +test_rebase_same_head success +test_rebase_same_head success master +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head success --onto master... master +test_rebase_same_head success --no-fork-point +test_rebase_same_head success --fork-point master +test_rebase_same_head failure --fork-point --onto B B +test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head success --fork-point --onto master... master + +test_expect_success 'add work to side' ' + test_commit E +' + +changes='our changes' +test_rebase_same_head success +test_rebase_same_head success master +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head success --onto master... master +test_rebase_same_head success --no-fork-point +test_rebase_same_head success --fork-point master +test_rebase_same_head failure --fork-point --onto B B +test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head success --fork-point --onto master... master + +test_expect_success 'add work to upstream' ' + git checkout master && + test_commit F && + git checkout side +' + +changes='our and their changes' +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head failure --onto master... master +test_rebase_same_head failure --fork-point --onto B B +test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head failure --fork-point --onto master... master + +test_done -- 2.23.0.248.g3a9dd8fb08 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v9 3/9] t3432: distinguish "noop-same" v.s. "work-same" in "same head" tests 2019-08-25 9:11 ` [PATCH v9 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour Denton Liu 2019-08-25 9:11 ` [PATCH v9 1/9] t3431: add rebase --fork-point tests Denton Liu 2019-08-25 9:12 ` [PATCH v9 2/9] t3432: test rebase fast-forward behavior Denton Liu @ 2019-08-25 9:12 ` Denton Liu 2019-08-25 9:12 ` [PATCH v9 4/9] t3432: test for --no-ff's interaction with fast-forward Denton Liu ` (7 subsequent siblings) 10 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-08-25 9:12 UTC (permalink / raw) To: Git Mailing List Cc: Junio C Hamano, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason From: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Change "same head" introduced in the preceding commit to check whether the rebase.c code lands in the can_fast_forward() case in, and thus prints out an "is up to date" and aborts early. In some of these cases we make it past that and to "rewinding head", then do a rebase, only to find out there's nothing to change so HEAD stays at the same OID. These tests presumed these two cases were the same thing. In terms of where HEAD ends up they are, but we're not only interested in rebase semantics, but also whether or not we're needlessly doing work when we could avoid it entirely. I'm adding "same" and "diff" here because I'll follow-up and add --no-ff tests, where some of those will be "diff"-erent, so add the "diff" code already. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- t/t3432-rebase-fast-forward.sh | 79 +++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index f49af274e0..d9f20fa07c 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -18,55 +18,72 @@ test_expect_success setup ' test_rebase_same_head () { status="$1" && shift && - test_expect_$status "git rebase $* with $changes is no-op" " + what="$1" && + shift && + cmp="$1" && + shift && + test_expect_$status "git rebase $* with $changes is $what" " oldhead=\$(git rev-parse HEAD) && test_when_finished 'git reset --hard \$oldhead' && - git rebase $* && + git rebase $* >stdout && + if test $what = work + then + test_i18ngrep 'rewinding head' stdout + elif test $what = noop + then + test_i18ngrep 'is up to date' stdout + fi && newhead=\$(git rev-parse HEAD) && - test_cmp_rev \$oldhead \$newhead + if test $cmp = same + then + test_cmp_rev \$oldhead \$newhead + elif test $cmp = diff + then + ! test_cmp_rev \$oldhead \$newhead + fi " } changes='no changes' -test_rebase_same_head success -test_rebase_same_head success master -test_rebase_same_head success --onto B B -test_rebase_same_head success --onto B... B -test_rebase_same_head success --onto master... master -test_rebase_same_head success --no-fork-point -test_rebase_same_head success --fork-point master -test_rebase_same_head failure --fork-point --onto B B -test_rebase_same_head failure --fork-point --onto B... B -test_rebase_same_head success --fork-point --onto master... master +test_rebase_same_head success work same +test_rebase_same_head success noop same master +test_rebase_same_head success noop same --onto B B +test_rebase_same_head success noop same --onto B... B +test_rebase_same_head success noop same --onto master... master +test_rebase_same_head success noop same --no-fork-point +test_rebase_same_head success work same --fork-point master +test_rebase_same_head failure noop same --fork-point --onto B B +test_rebase_same_head failure work same --fork-point --onto B... B +test_rebase_same_head success work same --fork-point --onto master... master -test_expect_success 'add work to side' ' +test_expect_success 'add work same to side' ' test_commit E ' changes='our changes' -test_rebase_same_head success -test_rebase_same_head success master -test_rebase_same_head success --onto B B -test_rebase_same_head success --onto B... B -test_rebase_same_head success --onto master... master -test_rebase_same_head success --no-fork-point -test_rebase_same_head success --fork-point master -test_rebase_same_head failure --fork-point --onto B B -test_rebase_same_head failure --fork-point --onto B... B -test_rebase_same_head success --fork-point --onto master... master +test_rebase_same_head success work same +test_rebase_same_head success noop same master +test_rebase_same_head success noop same --onto B B +test_rebase_same_head success noop same --onto B... B +test_rebase_same_head success noop same --onto master... master +test_rebase_same_head success noop same --no-fork-point +test_rebase_same_head success work same --fork-point master +test_rebase_same_head failure work same --fork-point --onto B B +test_rebase_same_head failure work same --fork-point --onto B... B +test_rebase_same_head success work same --fork-point --onto master... master -test_expect_success 'add work to upstream' ' +test_expect_success 'add work same to upstream' ' git checkout master && test_commit F && git checkout side ' changes='our and their changes' -test_rebase_same_head success --onto B B -test_rebase_same_head success --onto B... B -test_rebase_same_head failure --onto master... master -test_rebase_same_head failure --fork-point --onto B B -test_rebase_same_head failure --fork-point --onto B... B -test_rebase_same_head failure --fork-point --onto master... master +test_rebase_same_head success noop same --onto B B +test_rebase_same_head success noop same --onto B... B +test_rebase_same_head failure work same --onto master... master +test_rebase_same_head failure work same --fork-point --onto B B +test_rebase_same_head failure work same --fork-point --onto B... B +test_rebase_same_head failure work same --fork-point --onto master... master test_done -- 2.23.0.248.g3a9dd8fb08 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v9 4/9] t3432: test for --no-ff's interaction with fast-forward 2019-08-25 9:11 ` [PATCH v9 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour Denton Liu ` (2 preceding siblings ...) 2019-08-25 9:12 ` [PATCH v9 3/9] t3432: distinguish "noop-same" v.s. "work-same" in "same head" tests Denton Liu @ 2019-08-25 9:12 ` Denton Liu 2019-08-25 9:12 ` [PATCH v9 5/9] rebase: refactor can_fast_forward into goto tower Denton Liu ` (6 subsequent siblings) 10 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-08-25 9:12 UTC (permalink / raw) To: Git Mailing List Cc: Junio C Hamano, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason From: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Add more stress tests for the can_fast_forward() case in rebase.c. These tests are getting rather verbose, but now we can see under --ff and --no-ff whether we skip work, or whether we're forced to run the rebase. These tests aren't supposed to endorse the status quo, just test for what we're currently doing. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- t/t3432-rebase-fast-forward.sh | 83 ++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 29 deletions(-) diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index d9f20fa07c..02b2516595 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -16,22 +16,47 @@ test_expect_success setup ' ' test_rebase_same_head () { + status_n="$1" && + shift && + what_n="$1" && + shift && + cmp_n="$1" && + shift && + status_f="$1" && + shift && + what_f="$1" && + 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="$1" && shift && what="$1" && shift && cmp="$1" && shift && - test_expect_$status "git rebase $* with $changes is $what" " + flag="$1" + shift && + 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' && - git rebase $* >stdout && + 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 elif test $what = noop then - test_i18ngrep 'is up to date' stdout + 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 fi && newhead=\$(git rev-parse HEAD) && if test $cmp = same @@ -45,32 +70,32 @@ test_rebase_same_head () { } changes='no changes' -test_rebase_same_head success work same -test_rebase_same_head success noop same master -test_rebase_same_head success noop same --onto B B -test_rebase_same_head success noop same --onto B... B -test_rebase_same_head success noop same --onto master... master -test_rebase_same_head success noop same --no-fork-point -test_rebase_same_head success work same --fork-point master -test_rebase_same_head failure noop same --fork-point --onto B B -test_rebase_same_head failure work same --fork-point --onto B... B -test_rebase_same_head success work same --fork-point --onto master... master +test_rebase_same_head success work 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 --no-fork-point +test_rebase_same_head success work same success work same --fork-point master +test_rebase_same_head failure noop 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 same --fork-point --onto master... master test_expect_success 'add work same to side' ' test_commit E ' changes='our changes' -test_rebase_same_head success work same -test_rebase_same_head success noop same master -test_rebase_same_head success noop same --onto B B -test_rebase_same_head success noop same --onto B... B -test_rebase_same_head success noop same --onto master... master -test_rebase_same_head success noop same --no-fork-point -test_rebase_same_head success work same --fork-point master -test_rebase_same_head failure work same --fork-point --onto B B -test_rebase_same_head failure work same --fork-point --onto B... B -test_rebase_same_head success work same --fork-point --onto master... master +test_rebase_same_head success work 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 --no-fork-point +test_rebase_same_head success work same success work same --fork-point master +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 same --fork-point --onto master... master test_expect_success 'add work same to upstream' ' git checkout master && @@ -79,11 +104,11 @@ test_expect_success 'add work same to upstream' ' ' changes='our and their changes' -test_rebase_same_head success noop same --onto B B -test_rebase_same_head success noop same --onto B... B -test_rebase_same_head failure work same --onto master... master -test_rebase_same_head failure work same --fork-point --onto B B -test_rebase_same_head failure work same --fork-point --onto B... B -test_rebase_same_head failure work same --fork-point --onto master... 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 failure work same success work diff --onto master... master +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 failure work same success work diff --fork-point --onto master... master test_done -- 2.23.0.248.g3a9dd8fb08 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v9 5/9] rebase: refactor can_fast_forward into goto tower 2019-08-25 9:11 ` [PATCH v9 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour Denton Liu ` (3 preceding siblings ...) 2019-08-25 9:12 ` [PATCH v9 4/9] t3432: test for --no-ff's interaction with fast-forward Denton Liu @ 2019-08-25 9:12 ` Denton Liu 2019-08-25 13:17 ` Pratyush Yadav 2019-08-25 9:12 ` [PATCH v9 6/9] rebase: fast-forward --onto in more cases Denton Liu ` (5 subsequent siblings) 10 siblings, 1 reply; 123+ messages in thread From: Denton Liu @ 2019-08-25 9:12 UTC (permalink / raw) To: Git Mailing List Cc: Junio C Hamano, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason Before, can_fast_forward was written with an if-else statement. However, in the future, we may be adding more termination cases which would lead to deeply nested if statements. Refactor to use a goto tower so that future cases can be easily inserted. Signed-off-by: Denton Liu <liu.denton@gmail.com> --- builtin/rebase.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 670096c065..22c4f1ff93 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1264,21 +1264,27 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, struct object_id *merge_base) { struct commit *head = lookup_commit(the_repository, head_oid); - struct commit_list *merge_bases; - int res; + struct commit_list *merge_bases = NULL; + int res = 0; if (!head) - return 0; + goto done; merge_bases = get_merge_bases(onto, head); - if (merge_bases && !merge_bases->next) { - oidcpy(merge_base, &merge_bases->item->object.oid); - res = oideq(merge_base, &onto->object.oid); - } else { + if (!merge_bases || merge_bases->next) { oidcpy(merge_base, &null_oid); - res = 0; + goto done; } - free_commit_list(merge_bases); + + oidcpy(merge_base, &merge_bases->item->object.oid); + if (!oideq(merge_base, &onto->object.oid)) + goto done; + + res = 1; + +done: + if (merge_bases) + free_commit_list(merge_bases); return res && is_linear_history(onto, head); } -- 2.23.0.248.g3a9dd8fb08 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* Re: [PATCH v9 5/9] rebase: refactor can_fast_forward into goto tower 2019-08-25 9:12 ` [PATCH v9 5/9] rebase: refactor can_fast_forward into goto tower Denton Liu @ 2019-08-25 13:17 ` Pratyush Yadav 2019-08-26 23:17 ` Denton Liu 0 siblings, 1 reply; 123+ messages in thread From: Pratyush Yadav @ 2019-08-25 13:17 UTC (permalink / raw) To: Denton Liu Cc: Git Mailing List, Junio C Hamano, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason On 25/08/19 05:12AM, Denton Liu wrote: > Before, can_fast_forward was written with an if-else statement. However, > in the future, we may be adding more termination cases which would lead > to deeply nested if statements. > > Refactor to use a goto tower so that future cases can be easily > inserted. > > Signed-off-by: Denton Liu <liu.denton@gmail.com> > --- > builtin/rebase.c | 24 +++++++++++++++--------- > 1 file changed, 15 insertions(+), 9 deletions(-) > > diff --git a/builtin/rebase.c b/builtin/rebase.c > index 670096c065..22c4f1ff93 100644 > --- a/builtin/rebase.c > +++ b/builtin/rebase.c > @@ -1264,21 +1264,27 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, > struct object_id *merge_base) > { > struct commit *head = lookup_commit(the_repository, head_oid); > - struct commit_list *merge_bases; > - int res; > + struct commit_list *merge_bases = NULL; > + int res = 0; > > if (!head) > - return 0; > + goto done; > > merge_bases = get_merge_bases(onto, head); > - if (merge_bases && !merge_bases->next) { > - oidcpy(merge_base, &merge_bases->item->object.oid); > - res = oideq(merge_base, &onto->object.oid); > - } else { > + if (!merge_bases || merge_bases->next) { > oidcpy(merge_base, &null_oid); > - res = 0; > + goto done; > } > - free_commit_list(merge_bases); > + > + oidcpy(merge_base, &merge_bases->item->object.oid); > + if (!oideq(merge_base, &onto->object.oid)) > + goto done; > + > + res = 1; > + > +done: > + if (merge_bases) > + free_commit_list(merge_bases); free_commit_list() returns immediately when a NULL pointer is passed in, so I'm not sure if this check is really necessary. I think it is a reasonable assumption to make that free* functions work well with NULL inputs. > return res && is_linear_history(onto, head); > } > Out of curiosity, since you are going with a goto tower, why not do something like: done_merge_bases: free_commit_list(merge_bases); done: return res && is_linear_history(onto, head); You jump to done_merge_bases after you have initialized merge_bases, and directly to done before initializing it. I'm not advocating for either way, just curious if there is a specific reason to do it your way. Anyway, if you drop the if (merge_bases), then it doesn't really matter I suppose. -- Regards, Pratyush Yadav ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH v9 5/9] rebase: refactor can_fast_forward into goto tower 2019-08-25 13:17 ` Pratyush Yadav @ 2019-08-26 23:17 ` Denton Liu 0 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-08-26 23:17 UTC (permalink / raw) To: Pratyush Yadav Cc: Git Mailing List, Junio C Hamano, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason On Sun, Aug 25, 2019 at 06:47:02PM +0530, Pratyush Yadav wrote: > On 25/08/19 05:12AM, Denton Liu wrote: > > Before, can_fast_forward was written with an if-else statement. However, > > in the future, we may be adding more termination cases which would lead > > to deeply nested if statements. > > > > Refactor to use a goto tower so that future cases can be easily > > inserted. > > > > Signed-off-by: Denton Liu <liu.denton@gmail.com> > > --- > > builtin/rebase.c | 24 +++++++++++++++--------- > > 1 file changed, 15 insertions(+), 9 deletions(-) > > > > diff --git a/builtin/rebase.c b/builtin/rebase.c > > index 670096c065..22c4f1ff93 100644 > > --- a/builtin/rebase.c > > +++ b/builtin/rebase.c > > @@ -1264,21 +1264,27 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, > > struct object_id *merge_base) > > { > > struct commit *head = lookup_commit(the_repository, head_oid); > > - struct commit_list *merge_bases; > > - int res; > > + struct commit_list *merge_bases = NULL; > > + int res = 0; > > > > if (!head) > > - return 0; > > + goto done; > > > > merge_bases = get_merge_bases(onto, head); > > - if (merge_bases && !merge_bases->next) { > > - oidcpy(merge_base, &merge_bases->item->object.oid); > > - res = oideq(merge_base, &onto->object.oid); > > - } else { > > + if (!merge_bases || merge_bases->next) { > > oidcpy(merge_base, &null_oid); > > - res = 0; > > + goto done; > > } > > - free_commit_list(merge_bases); > > + > > + oidcpy(merge_base, &merge_bases->item->object.oid); > > + if (!oideq(merge_base, &onto->object.oid)) > > + goto done; > > + > > + res = 1; > > + > > +done: > > + if (merge_bases) > > + free_commit_list(merge_bases); > > free_commit_list() returns immediately when a NULL pointer is passed in, > so I'm not sure if this check is really necessary. I think it is a > reasonable assumption to make that free* functions work well with NULL > inputs. I didn't realise that free_commit_list() would freely accept a NULL pointer without segfaulting. I'll remove the surrounding if. > > > return res && is_linear_history(onto, head); > > } > > > > Out of curiosity, since you are going with a goto tower, why not do > something like: > > done_merge_bases: > free_commit_list(merge_bases); > done: > return res && is_linear_history(onto, head); > > You jump to done_merge_bases after you have initialized merge_bases, and > directly to done before initializing it. I opted to do it this way since I figured it was less complexity to have a common jump target at the end. Also, in case in the future we decided to add more logic before merge_bases was initialised, we wouldn't have to worry about figuring out which target to jump to. > > I'm not advocating for either way, just curious if there is a specific > reason to do it your way. Anyway, if you drop the if (merge_bases), then > it doesn't really matter I suppose. I'm pretty impartial as well. I'll drop the if and leave the rest as is unless someone else feels strongly about this. > > -- > Regards, > Pratyush Yadav ^ permalink raw reply [flat|nested] 123+ messages in thread
* [PATCH v9 6/9] rebase: fast-forward --onto in more cases 2019-08-25 9:11 ` [PATCH v9 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour Denton Liu ` (4 preceding siblings ...) 2019-08-25 9:12 ` [PATCH v9 5/9] rebase: refactor can_fast_forward into goto tower Denton Liu @ 2019-08-25 9:12 ` Denton Liu 2019-08-25 9:12 ` [PATCH v9 7/9] rebase: fast-forward --fork-point " Denton Liu ` (4 subsequent siblings) 10 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-08-25 9:12 UTC (permalink / raw) To: Git Mailing List Cc: Junio C Hamano, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason Before, when we had the following graph, A---B---C (master) \ D (side) running 'git rebase --onto master... master side' would result in D being always rebased, no matter what. However, the desired behavior is that rebase should notice that this is fast-forwardable and do that instead. Add detection to `can_fast_forward` so that this case can be detected and a fast-forward will be performed. First of all, rewrite the function to use gotos which simplifies the logic. Next, since the options.upstream && !oidcmp(&options.upstream->object.oid, &options.onto->object.oid) conditions were removed in `cmd_rebase`, we reintroduce a substitute in `can_fast_forward`. In particular, checking the merge bases of `upstream` and `head` fixes a failing case in t3416. The abbreviated graph for t3416 is as follows: F---G topic / A---B---C---D---E master and the failing command was git rebase --onto master...topic F topic Before, Git would see that there was one merge base (C), and the merge and onto were the same so it would incorrectly return 1, indicating that we could fast-forward. This would cause the rebased graph to be 'ABCFG' when we were expecting 'ABCG'. With the additional logic, we detect that upstream and head's merge base is F. Since onto isn't F, it means we're not rebasing the full set of commits from master..topic. Since we're excluding some commits, a fast-forward cannot be performed and so we correctly return 0. Add '-f' to test cases that failed as a result of this change because they were not expecting a fast-forward so that a rebase is forced. Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- builtin/rebase.c | 27 +++++++++++++++++++-------- t/t3400-rebase.sh | 2 +- t/t3404-rebase-interactive.sh | 2 +- t/t3432-rebase-fast-forward.sh | 4 ++-- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 22c4f1ff93..bdb1d922a4 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1260,8 +1260,8 @@ static int is_linear_history(struct commit *from, struct commit *to) return 1; } -static int can_fast_forward(struct commit *onto, struct object_id *head_oid, - struct object_id *merge_base) +static int can_fast_forward(struct commit *onto, struct commit *upstream, + struct object_id *head_oid, struct object_id *merge_base) { struct commit *head = lookup_commit(the_repository, head_oid); struct commit_list *merge_bases = NULL; @@ -1280,6 +1280,17 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, if (!oideq(merge_base, &onto->object.oid)) goto done; + if (!upstream) + goto done; + + free_commit_list(merge_bases); + merge_bases = get_merge_bases(upstream, head); + if (!merge_bases || merge_bases->next) + goto done; + + if (!oideq(&onto->object.oid, &merge_bases->item->object.oid)) + goto done; + res = 1; done: @@ -2028,13 +2039,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) /* * Check if we are already based on onto with linear history, - * but this should be done only when upstream and onto are the same - * and if this is not an interactive rebase. + * 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. */ - if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && - !is_interactive(&options) && !options.restrict_revision && - options.upstream && - !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { + if (can_fast_forward(options.onto, options.upstream, &options.orig_head, + &merge_base) && + !is_interactive(&options) && !options.restrict_revision) { int flag; if (!(options.flags & REBASE_FORCE)) { diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 80b23fd326..d7c724bea3 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -295,7 +295,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 --onto init HEAD^ && + test_must_fail git rebase -f --onto init HEAD^ && GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && grep "show.*$(git rev-parse two)" stderr ) diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 461dd539ff..3cc9052f10 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1058,7 +1058,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int git reset --hard && git checkout conflict-branch && set_fake_editor && - test_must_fail git rebase --onto HEAD~2 HEAD~ && + test_must_fail git rebase -f --onto HEAD~2 HEAD~ && test_must_fail git rebase --edit-todo && git rebase --abort ' diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index 02b2516595..d9957e5f1e 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -106,9 +106,9 @@ 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 failure work same success work diff --onto master... master +test_rebase_same_head success noop same success work diff --onto master... master 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 failure work same success work diff --fork-point --onto master... master +test_rebase_same_head success noop same success work diff --fork-point --onto master... master test_done -- 2.23.0.248.g3a9dd8fb08 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v9 7/9] rebase: fast-forward --fork-point in more cases 2019-08-25 9:11 ` [PATCH v9 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour Denton Liu ` (5 preceding siblings ...) 2019-08-25 9:12 ` [PATCH v9 6/9] rebase: fast-forward --onto in more cases Denton Liu @ 2019-08-25 9:12 ` Denton Liu 2019-08-25 9:12 ` [PATCH v9 8/9] rebase tests: test linear branch topology Denton Liu ` (3 subsequent siblings) 10 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-08-25 9:12 UTC (permalink / raw) To: Git Mailing List Cc: Junio C Hamano, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason Before, when we rebased with a --fork-point invocation where the fork-point wasn't empty, we would be setting options.restrict_revision. The fast-forward logic would automatically declare that the rebase was not fast-forwardable if it was set. However, this was painting with a very broad brush. Refine the logic so that we can fast-forward in the case where the restricted revision is equal to the merge base, since we stop rebasing at the merge base anyway. Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- builtin/rebase.c | 10 +++++++--- t/t3432-rebase-fast-forward.sh | 20 ++++++++++---------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index bdb1d922a4..b78bd92002 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1261,6 +1261,7 @@ static int is_linear_history(struct commit *from, struct commit *to) } static int can_fast_forward(struct commit *onto, struct commit *upstream, + struct commit *restrict_revision, struct object_id *head_oid, struct object_id *merge_base) { struct commit *head = lookup_commit(the_repository, head_oid); @@ -1280,6 +1281,9 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream, if (!oideq(merge_base, &onto->object.oid)) goto done; + if (restrict_revision && !oideq(&restrict_revision->object.oid, merge_base)) + goto done; + if (!upstream) goto done; @@ -2043,9 +2047,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) * with new commits recreated by replaying their changes. This * optimization must not be done if this is an interactive rebase. */ - if (can_fast_forward(options.onto, options.upstream, &options.orig_head, - &merge_base) && - !is_interactive(&options) && !options.restrict_revision) { + if (can_fast_forward(options.onto, options.upstream, options.restrict_revision, + &options.orig_head, &merge_base) && + !is_interactive(&options)) { int flag; if (!(options.flags & REBASE_FORCE)) { diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index d9957e5f1e..fbedfe7b4a 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -70,32 +70,32 @@ test_rebase_same_head_ () { } changes='no changes' -test_rebase_same_head success work same success work same +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 --no-fork-point -test_rebase_same_head success work same success work same --fork-point master -test_rebase_same_head failure noop 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 same --fork-point --onto master... master +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 +test_rebase_same_head success noop same success work same --fork-point --onto master... master test_expect_success 'add work same to side' ' test_commit E ' changes='our changes' -test_rebase_same_head success work same success work same +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 --no-fork-point -test_rebase_same_head success work same success work same --fork-point master -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 same --fork-point --onto master... master +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 +test_rebase_same_head success noop same success work same --fork-point --onto master... master test_expect_success 'add work same to upstream' ' git checkout master && -- 2.23.0.248.g3a9dd8fb08 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v9 8/9] rebase tests: test linear branch topology 2019-08-25 9:11 ` [PATCH v9 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour Denton Liu ` (6 preceding siblings ...) 2019-08-25 9:12 ` [PATCH v9 7/9] rebase: fast-forward --fork-point " Denton Liu @ 2019-08-25 9:12 ` Denton Liu 2019-08-25 9:12 ` [PATCH v9 9/9] rebase: teach rebase --keep-base Denton Liu ` (2 subsequent siblings) 10 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-08-25 9:12 UTC (permalink / raw) To: Git Mailing List Cc: Junio C Hamano, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason From: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Add tests rebasing a linear branch topology to linear rebase tests added in 2aad7cace2 ("add simple tests of consistency across rebase types", 2013-06-06). These tests are duplicates of two surrounding tests that do the same with tags pointing to the same objects. Right now there's no change in behavior being introduced, but as we'll see in a subsequent change rebase can have different behaviors when working implicitly with remote tracking branches. While I'm at it add a --fork-point test, strictly speaking this is redundant to the existing '' test, as no argument to rebase implies --fork-point. But now it's easier to grep for tests that explicitly stress --fork-point. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- t/t3421-rebase-topology-linear.sh | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh index 7274dca40b..b847064f91 100755 --- a/t/t3421-rebase-topology-linear.sh +++ b/t/t3421-rebase-topology-linear.sh @@ -31,6 +31,16 @@ test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p +test_expect_success 'setup branches and remote tracking' ' + git tag -l >tags && + for tag in $(cat tags) + do + git branch branch-$tag $tag || return 1 + done && + git remote add origin "file://$PWD" && + git fetch origin +' + test_run_rebase () { result=$1 shift @@ -57,10 +67,28 @@ test_run_rebase () { " } test_run_rebase success '' +test_run_rebase success --fork-point test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase failure -p +test_run_rebase () { + result=$1 + shift + test_expect_$result "rebase $* -f rewrites even if remote upstream is an ancestor" " + reset_rebase && + git rebase $* -f branch-b branch-e && + ! test_cmp_rev branch-e origin/branch-e && + test_cmp_rev branch-b HEAD~2 && + test_linear_range 'd e' branch-b.. + " +} +test_run_rebase success '' +test_run_rebase success --fork-point +test_run_rebase success -m +test_run_rebase success -i +test_have_prereq !REBASE_P || test_run_rebase success -p + test_run_rebase () { result=$1 shift @@ -71,6 +99,7 @@ test_run_rebase () { " } test_run_rebase success '' +test_run_rebase success --fork-point test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p -- 2.23.0.248.g3a9dd8fb08 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v9 9/9] rebase: teach rebase --keep-base 2019-08-25 9:11 ` [PATCH v9 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour Denton Liu ` (7 preceding siblings ...) 2019-08-25 9:12 ` [PATCH v9 8/9] rebase tests: test linear branch topology Denton Liu @ 2019-08-25 9:12 ` Denton Liu 2019-08-25 22:59 ` Philip Oakley 2019-08-26 19:37 ` [PATCH v9 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour Junio C Hamano 2019-08-27 5:37 ` [PATCH v10 " Denton Liu 10 siblings, 1 reply; 123+ messages in thread From: Denton Liu @ 2019-08-25 9:12 UTC (permalink / raw) To: Git Mailing List Cc: Junio C Hamano, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason A common scenario is if a user is working on a topic branch and they wish to make some changes to intermediate commits or autosquash, they would run something such as git rebase -i --onto master... master in order to preserve the merge base. This is useful when contributing a patch series to the Git mailing list, one often starts on top of the current 'master'. However, while developing the patches, 'master' is also developed further and it is sometimes not the best idea to keep rebasing on top of 'master', but to keep the base commit as-is. Alternatively, a user wishing to test individual commits in a topic branch without changing anything may run git rebase -x ./test.sh master... master Since rebasing onto the merge base of the branch and the upstream is such a common case, introduce the --keep-base option as a shortcut. This allows us to rewrite the above as git rebase -i --keep-base master and git rebase -x ./test.sh --keep-base master respectively. Add tests to ensure --keep-base works correctly in the normal case and fails when there are multiple merge bases, both in regular and interactive mode. Also, test to make sure conflicting options cause rebase to fail. While we're adding test cases, add a missing set_fake_editor call to 'rebase -i --onto master...side'. While we're documenting the --keep-base option, change an instance of "merge-base" to "merge base", which is the consistent spelling. Helped-by: Eric Sunshine <sunshine@sunshineco.com> Helped-by: Junio C Hamano <gitster@pobox.com> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- Documentation/git-rebase.txt | 30 ++++++++++++-- builtin/rebase.c | 32 ++++++++++++--- contrib/completion/git-completion.bash | 2 +- t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++ t/t3431-rebase-fork-point.sh | 4 ++ t/t3432-rebase-fast-forward.sh | 11 +++++ 6 files changed, 126 insertions(+), 10 deletions(-) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 6156609cf7..3146c1592d 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip SYNOPSIS -------- [verse] -'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] - [<upstream> [<branch>]] +'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] + [--onto <newbase> | --keep-base] [<upstream> [<branch>]] 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] --root [<branch>] 'git rebase' (--continue | --skip | --abort | --quit | --edit-todo | --show-current-patch) @@ -217,6 +217,24 @@ As a special case, you may use "A\...B" as a shortcut for the merge base of A and B if there is exactly one merge base. You can leave out at most one of A and B, in which case it defaults to HEAD. +--keep-base:: + Set the starting point at which to create the new commits to the + merge base of <upstream> <branch>. Running + 'git rebase --keep-base <upstream> <branch>' is equivalent to + running 'git rebase --onto <upstream>... <upstream>'. ++ +This option is useful in the case where one is developing a feature on +top of an upstream branch. While the feature is being worked on, the +upstream branch may advance and it may not be the best idea to keep +rebasing on top of the upstream but to keep the base commit as-is. ++ +Although both this option and --fork-point find the merge base between +<upstream> and <branch>, this option uses the merge base as the _starting +point_ on which new commits will be created, whereas --fork-point uses +the merge base to determine the _set of commits_ which will be rebased. ++ +See also INCOMPATIBLE OPTIONS below. + <upstream>:: Upstream branch to compare against. May be any valid commit, not just an existing branch name. Defaults to the configured @@ -369,6 +387,10 @@ ends up being empty, the <upstream> will be used as a fallback. + If either <upstream> or --root is given on the command line, then the default is `--no-fork-point`, otherwise the default is `--fork-point`. ++ +If your branch was based on <upstream> but <upstream> was rewound and +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:: --whitespace=<option>:: @@ -545,6 +567,8 @@ In addition, the following pairs of options are incompatible: * --preserve-merges and --rebase-merges * --rebase-merges and --strategy * --rebase-merges and --strategy-option + * --keep-base and --onto + * --keep-base and --root BEHAVIORAL DIFFERENCES ----------------------- @@ -870,7 +894,7 @@ NOTE: While an "easy case recovery" sometimes appears to be successful --interactive` will be **resurrected**! The idea is to manually tell 'git rebase' "where the old 'subsystem' -ended and your 'topic' began", that is, what the old merge-base +ended and your 'topic' began", that is, what the old merge base between them was. You will have to find a way to name the last commit of the old 'subsystem', for example: diff --git a/builtin/rebase.c b/builtin/rebase.c index b78bd92002..1a49060973 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -29,8 +29,8 @@ #include "rebase-interactive.h" static char const * const builtin_rebase_usage[] = { - N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " - "[<upstream>] [<branch>]"), + N_("git rebase [-i] [options] [--exec <cmd>] " + "[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"), N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " "--root [<branch>]"), N_("git rebase --continue | --abort | --skip | --edit-todo"), @@ -1397,6 +1397,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) struct rebase_options options = REBASE_OPTIONS_INIT; const char *branch_name; int ret, flags, total_argc, in_progress = 0; + int keep_base = 0; int ok_to_skip_pre_rebase = 0; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; @@ -1415,6 +1416,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_STRING(0, "onto", &options.onto_name, N_("revision"), N_("rebase onto given branch instead of upstream")), + OPT_BOOL(0, "keep-base", &keep_base, + N_("use the merge-base of upstream and branch as the current base")), OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase, N_("allow pre-rebase hook to run")), OPT_NEGBIT('q', "quiet", &options.flags, @@ -1568,6 +1571,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) warning(_("git rebase --preserve-merges is deprecated. " "Use --rebase-merges instead.")); + if (keep_base) { + if (options.onto_name) + die(_("cannot combine '--keep-base' with '--onto'")); + if (options.root) + die(_("cannot combine '--keep-base' with '--root'")); + } + if (action != ACTION_NONE && !in_progress) die(_("No rebase in progress?")); setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0); @@ -1903,12 +1913,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } /* Make sure the branch to rebase onto is valid. */ - if (!options.onto_name) + if (keep_base) { + strbuf_reset(&buf); + strbuf_addstr(&buf, options.upstream_name); + strbuf_addstr(&buf, "..."); + options.onto_name = xstrdup(buf.buf); + } else if (!options.onto_name) options.onto_name = options.upstream_name; if (strstr(options.onto_name, "...")) { - if (get_oid_mb(options.onto_name, &merge_base) < 0) - die(_("'%s': need exactly one merge base"), - options.onto_name); + if (get_oid_mb(options.onto_name, &merge_base) < 0) { + if (keep_base) + die(_("'%s': need exactly one merge base with branch"), + options.upstream_name); + else + die(_("'%s': need exactly one merge base"), + options.onto_name); + } options.onto = lookup_commit_or_die(&merge_base, options.onto_name); } else { diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index e087c4bf00..71e7159694 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -2030,7 +2030,7 @@ _git_rebase () --autosquash --no-autosquash --fork-point --no-fork-point --autostash --no-autostash - --verify --no-verify + --verify --no-verify --keep-base --keep-empty --root --force-rebase --no-ff --rerere-autoupdate --exec diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh index ddf2f64853..9c2548423b 100755 --- a/t/t3416-rebase-onto-threedots.sh +++ b/t/t3416-rebase-onto-threedots.sh @@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' ' git checkout side && git reset --hard K && + set_fake_editor && test_must_fail git rebase -i --onto master...side J ' +test_expect_success 'rebase --keep-base --onto incompatible' ' + test_must_fail git rebase --keep-base --onto master... +' + +test_expect_success 'rebase --keep-base --root incompatible' ' + test_must_fail git rebase --keep-base --root +' + +test_expect_success 'rebase --keep-base master from topic' ' + git reset --hard && + git checkout topic && + git reset --hard G && + + git rebase --keep-base master && + git rev-parse C >base.expect && + git merge-base master HEAD >base.actual && + test_cmp base.expect base.actual && + + git rev-parse HEAD~2 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase --keep-base master from side' ' + git reset --hard && + git checkout side && + git reset --hard K && + + test_must_fail git rebase --keep-base master +' + +test_expect_success 'rebase -i --keep-base master from topic' ' + git reset --hard && + git checkout topic && + git reset --hard G && + + set_fake_editor && + EXPECT_COUNT=2 git rebase -i --keep-base master && + git rev-parse C >base.expect && + git merge-base master HEAD >base.actual && + test_cmp base.expect base.actual && + + git rev-parse HEAD~2 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase -i --keep-base master from side' ' + git reset --hard && + git checkout side && + git reset --hard K && + + set_fake_editor && + test_must_fail git rebase -i --keep-base master +' + test_done diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh index 2d5c6e641e..78851b9a2a 100755 --- a/t/t3431-rebase-fork-point.sh +++ b/t/t3431-rebase-fork-point.sh @@ -43,11 +43,15 @@ test_rebase () { test_rebase 'G F E D B A' test_rebase 'G F D B A' --onto D +test_rebase 'G F B A' --keep-base test_rebase 'G F C E D B A' --no-fork-point test_rebase 'G F C D B A' --no-fork-point --onto D +test_rebase 'G F C B A' --no-fork-point --keep-base test_rebase 'G F E D B A' --fork-point refs/heads/master test_rebase 'G F D B A' --fork-point --onto D refs/heads/master +test_rebase 'G F B A' --fork-point --keep-base refs/heads/master test_rebase 'G F C E D B A' refs/heads/master test_rebase 'G F C D B A' --onto D refs/heads/master +test_rebase 'G F C B A' --keep-base refs/heads/master test_done diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index fbedfe7b4a..2f9f82be10 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -75,11 +75,15 @@ 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 --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 test_rebase_same_head success noop same success work same --fork-point --onto master... master +test_rebase_same_head success noop same success work same --keep-base --keep-base master test_expect_success 'add work same to side' ' test_commit E @@ -91,11 +95,15 @@ 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 --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 test_rebase_same_head success noop same success work same --fork-point --onto master... master +test_rebase_same_head success noop same success work same --fork-point --keep-base master test_expect_success 'add work same to upstream' ' git checkout master && @@ -107,8 +115,11 @@ 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 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 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 test_done -- 2.23.0.248.g3a9dd8fb08 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* Re: [PATCH v9 9/9] rebase: teach rebase --keep-base 2019-08-25 9:12 ` [PATCH v9 9/9] rebase: teach rebase --keep-base Denton Liu @ 2019-08-25 22:59 ` Philip Oakley 0 siblings, 0 replies; 123+ messages in thread From: Philip Oakley @ 2019-08-25 22:59 UTC (permalink / raw) To: Denton Liu, Git Mailing List Cc: Junio C Hamano, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason On 25/08/2019 10:12, Denton Liu wrote: > A common scenario is if a user is working on a topic branch and they > wish to make some changes to intermediate commits or autosquash, they > would run something such as > > git rebase -i --onto master... master > > in order to preserve the merge base. This is useful when contributing a > patch series to the Git mailing list, one often starts on top of the > current 'master'. This 'However' part doesn't fit the flow. Do you mean 'while initially developing...', or is the choice of 'However' wrong? > However, while developing the patches, 'master' is > also developed further and it is sometimes not the best idea to keep > rebasing on top of 'master', but to keep the base commit as-is. > This 'Alternatively' appears to be part of the first set of common scenarios. Maybe it's just 3 common scenarios, all justifying the same improvement. > Alternatively, a user wishing to test individual commits in a topic > branch without changing anything may run > > git rebase -x ./test.sh master... master > > Since rebasing onto the merge base of the branch and the upstream is > such a common case, introduce the --keep-base option as a shortcut. > > This allows us to rewrite the above as > > git rebase -i --keep-base master > > and > > git rebase -x ./test.sh --keep-base master > > respectively. [remainder snipped] [also Eric Sunshine dropped due to temporary sending difficulties] Philip ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH v9 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour 2019-08-25 9:11 ` [PATCH v9 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour Denton Liu ` (8 preceding siblings ...) 2019-08-25 9:12 ` [PATCH v9 9/9] rebase: teach rebase --keep-base Denton Liu @ 2019-08-26 19:37 ` Junio C Hamano 2019-08-27 5:37 ` [PATCH v10 " Denton Liu 10 siblings, 0 replies; 123+ messages in thread From: Junio C Hamano @ 2019-08-26 19:37 UTC (permalink / raw) To: Denton Liu Cc: Git Mailing List, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason Denton Liu <liu.denton@gmail.com> writes: > Hi all, it's been a while but I guess now's a good time as any to > resurrect this topic. This is basically a resubmission of Ævar's WIP v8 > but I fixed a couple of minor whitespace issues. > > In addition, I opted to drop patches 9-13 from v8 since I don't think I > can do a good enough job polishing them up and I don't really understand > the intricacies of this part of the rebase code. Hopefully, Ævar will > pick them up at a later time. Thanks; let's queue this with an updated base (the tip of master) as we are now in a new cycle. ^ permalink raw reply [flat|nested] 123+ messages in thread
* [PATCH v10 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour 2019-08-25 9:11 ` [PATCH v9 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour Denton Liu ` (9 preceding siblings ...) 2019-08-26 19:37 ` [PATCH v9 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour Junio C Hamano @ 2019-08-27 5:37 ` Denton Liu 2019-08-27 5:37 ` [PATCH v10 1/9] t3431: add rebase --fork-point tests Denton Liu ` (8 more replies) 10 siblings, 9 replies; 123+ messages in thread From: Denton Liu @ 2019-08-27 5:37 UTC (permalink / raw) To: Git Mailing List Cc: Junio C Hamano, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Philip Oakley, Pratyush Yadav Thanks for your suggestions, Pratyush and Philip. I've incorporated both of them into this reroll. Changes since v1: * Squashed old set into one patch * Fixed indentation style and dangling else * Added more documentation after discussion with Ævar Changes since v2: * Add testing for rebase --fork-point behaviour * Add testing for rebase fast-forward behaviour * Make rebase --onto fast-forward in more cases * Update documentation to include use-case Changes since v3: * Fix tests failing on bash 4.2 * Fix typo in t3431 comment Changes since v4: * Make rebase --fork-point fast-forward in more cases Changes since v5: * Fix graph illustrations so that the "branch off" is visually in the correct place * Refactor if-else in can_fast_forward into one-level-deep ifs to increase clarity Changes since v6: * Remove redundant braces around if * Update comment around can_fast_forward call * Add completion for rebase Changes since v7: * Ævar sent in his WIP patchset Changes since v8: * Drop patches 9-13 * Fix some minor whitespace issues from v7 Changes since v9: * Remove unnecessary if-statement in patch 6/9 * Update commit message for patch 9/9 for clarity according to Philip's suggestions [1]: https://public-inbox.org/git/a1b4b74b9167e279dae4cd8c58fb28d8a714a66a.1553537656.git.gitgitgadget@gmail.com/ Denton Liu (6): t3431: add rebase --fork-point tests t3432: test rebase fast-forward behavior rebase: refactor can_fast_forward into goto tower rebase: fast-forward --onto in more cases rebase: fast-forward --fork-point in more cases rebase: teach rebase --keep-base Ævar Arnfjörð Bjarmason (3): t3432: distinguish "noop-same" v.s. "work-same" in "same head" tests t3432: test for --no-ff's interaction with fast-forward rebase tests: test linear branch topology Documentation/git-rebase.txt | 30 +++++- builtin/rebase.c | 84 ++++++++++++----- contrib/completion/git-completion.bash | 2 +- t/t3400-rebase.sh | 2 +- t/t3404-rebase-interactive.sh | 2 +- t/t3416-rebase-onto-threedots.sh | 57 +++++++++++ t/t3421-rebase-topology-linear.sh | 29 ++++++ t/t3431-rebase-fork-point.sh | 57 +++++++++++ t/t3432-rebase-fast-forward.sh | 125 +++++++++++++++++++++++++ 9 files changed, 360 insertions(+), 28 deletions(-) create mode 100755 t/t3431-rebase-fork-point.sh create mode 100755 t/t3432-rebase-fast-forward.sh Range-diff against v9: 1: 03f769d410 = 1: 03f769d410 t3431: add rebase --fork-point tests 2: bc8998079d = 2: bc8998079d t3432: test rebase fast-forward behavior 3: 5c08e2b81f = 3: 5c08e2b81f t3432: distinguish "noop-same" v.s. "work-same" in "same head" tests 4: 48b4e41a17 = 4: 48b4e41a17 t3432: test for --no-ff's interaction with fast-forward 5: 9bd34b4a40 ! 5: 9acce7c911 rebase: refactor can_fast_forward into goto tower @@ builtin/rebase.c: static int can_fast_forward(struct commit *onto, struct object - res = 0; + goto done; } -- free_commit_list(merge_bases); + + oidcpy(merge_base, &merge_bases->item->object.oid); + if (!oideq(merge_base, &onto->object.oid)) @@ builtin/rebase.c: static int can_fast_forward(struct commit *onto, struct object + res = 1; + +done: -+ if (merge_bases) -+ free_commit_list(merge_bases); + free_commit_list(merge_bases); return res && is_linear_history(onto, head); } - 6: becb924232 = 6: 3f208421d6 rebase: fast-forward --onto in more cases 7: 4dab5847cb = 7: 126c20a95d rebase: fast-forward --fork-point in more cases 8: 4699a90993 = 8: bf6cc6a610 rebase tests: test linear branch topology 9: 6927aba617 ! 9: 6bc7423ac1 rebase: teach rebase --keep-base @@ Commit message in order to preserve the merge base. This is useful when contributing a patch series to the Git mailing list, one often starts on top of the - current 'master'. However, while developing the patches, 'master' is - also developed further and it is sometimes not the best idea to keep - rebasing on top of 'master', but to keep the base commit as-is. + current 'master'. While developing the patches, 'master' is also + developed further and it is sometimes not the best idea to keep rebasing + on top of 'master', but to keep the base commit as-is. - Alternatively, a user wishing to test individual commits in a topic - branch without changing anything may run + In addition to this, a user wishing to test individual commits in a + topic branch without changing anything may run git rebase -x ./test.sh master... master -- 2.23.0.248.g3a9dd8fb08 ^ permalink raw reply [flat|nested] 123+ messages in thread
* [PATCH v10 1/9] t3431: add rebase --fork-point tests 2019-08-27 5:37 ` [PATCH v10 " Denton Liu @ 2019-08-27 5:37 ` Denton Liu 2019-08-27 5:37 ` [PATCH v10 2/9] t3432: test rebase fast-forward behavior Denton Liu ` (7 subsequent siblings) 8 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-08-27 5:37 UTC (permalink / raw) To: Git Mailing List Cc: Junio C Hamano, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Philip Oakley, Pratyush Yadav Signed-off-by: Denton Liu <liu.denton@gmail.com> --- t/t3431-rebase-fork-point.sh | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100755 t/t3431-rebase-fork-point.sh diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh new file mode 100755 index 0000000000..2d5c6e641e --- /dev/null +++ b/t/t3431-rebase-fork-point.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# +# Copyright (c) 2019 Denton Liu +# + +test_description='git rebase --fork-point test' + +. ./test-lib.sh + +# A---B---D---E (master) +# \ +# C*---F---G (side) +# +# C was formerly part of master but master was rewound to remove C +# +test_expect_success setup ' + test_commit A && + test_commit B && + test_commit C && + git branch -t side && + git reset --hard HEAD^ && + test_commit D && + test_commit E && + git checkout side && + test_commit F && + test_commit G +' + +test_rebase () { + expected="$1" && + shift && + test_expect_success "git rebase $*" " + git checkout master && + git reset --hard E && + git checkout side && + git reset --hard G && + git rebase $* && + test_write_lines $expected >expect && + git log --pretty=%s >actual && + test_cmp expect actual + " +} + +test_rebase 'G F E D B A' +test_rebase 'G F D B A' --onto D +test_rebase 'G F C E D B A' --no-fork-point +test_rebase 'G F C D B A' --no-fork-point --onto D +test_rebase 'G F E D B A' --fork-point refs/heads/master +test_rebase 'G F D B A' --fork-point --onto D refs/heads/master +test_rebase 'G F C E D B A' refs/heads/master +test_rebase 'G F C D B A' --onto D refs/heads/master + +test_done -- 2.23.0.248.g3a9dd8fb08 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v10 2/9] t3432: test rebase fast-forward behavior 2019-08-27 5:37 ` [PATCH v10 " Denton Liu 2019-08-27 5:37 ` [PATCH v10 1/9] t3431: add rebase --fork-point tests Denton Liu @ 2019-08-27 5:37 ` Denton Liu 2019-08-27 5:37 ` [PATCH v10 3/9] t3432: distinguish "noop-same" v.s. "work-same" in "same head" tests Denton Liu ` (6 subsequent siblings) 8 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-08-27 5:37 UTC (permalink / raw) To: Git Mailing List Cc: Junio C Hamano, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Philip Oakley, Pratyush Yadav When rebase is run on a branch that can be fast-forwarded, this should automatically be done. Create test to ensure this behavior happens. There are some cases that currently don't pass. The first case is where a feature and master have diverged, running "git rebase master... master" causes a full rebase to happen even though a fast-forward should happen. The second case is when we are doing "git rebase --fork-point" and a fork-point commit is found. Once again, a full rebase happens even though a fast-forward should happen. Mark these cases as failure so we can fix it later. Signed-off-by: Denton Liu <liu.denton@gmail.com> --- t/t3432-rebase-fast-forward.sh | 72 ++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100755 t/t3432-rebase-fast-forward.sh diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh new file mode 100755 index 0000000000..f49af274e0 --- /dev/null +++ b/t/t3432-rebase-fast-forward.sh @@ -0,0 +1,72 @@ +#!/bin/sh +# +# Copyright (c) 2019 Denton Liu +# + +test_description='ensure rebase fast-forwards commits when possible' + +. ./test-lib.sh + +test_expect_success setup ' + test_commit A && + test_commit B && + test_commit C && + test_commit D && + git checkout -t -b side +' + +test_rebase_same_head () { + status="$1" && + shift && + test_expect_$status "git rebase $* with $changes is no-op" " + oldhead=\$(git rev-parse HEAD) && + test_when_finished 'git reset --hard \$oldhead' && + git rebase $* && + newhead=\$(git rev-parse HEAD) && + test_cmp_rev \$oldhead \$newhead + " +} + +changes='no changes' +test_rebase_same_head success +test_rebase_same_head success master +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head success --onto master... master +test_rebase_same_head success --no-fork-point +test_rebase_same_head success --fork-point master +test_rebase_same_head failure --fork-point --onto B B +test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head success --fork-point --onto master... master + +test_expect_success 'add work to side' ' + test_commit E +' + +changes='our changes' +test_rebase_same_head success +test_rebase_same_head success master +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head success --onto master... master +test_rebase_same_head success --no-fork-point +test_rebase_same_head success --fork-point master +test_rebase_same_head failure --fork-point --onto B B +test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head success --fork-point --onto master... master + +test_expect_success 'add work to upstream' ' + git checkout master && + test_commit F && + git checkout side +' + +changes='our and their changes' +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head failure --onto master... master +test_rebase_same_head failure --fork-point --onto B B +test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head failure --fork-point --onto master... master + +test_done -- 2.23.0.248.g3a9dd8fb08 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v10 3/9] t3432: distinguish "noop-same" v.s. "work-same" in "same head" tests 2019-08-27 5:37 ` [PATCH v10 " Denton Liu 2019-08-27 5:37 ` [PATCH v10 1/9] t3431: add rebase --fork-point tests Denton Liu 2019-08-27 5:37 ` [PATCH v10 2/9] t3432: test rebase fast-forward behavior Denton Liu @ 2019-08-27 5:37 ` Denton Liu 2019-08-27 5:37 ` [PATCH v10 4/9] t3432: test for --no-ff's interaction with fast-forward Denton Liu ` (5 subsequent siblings) 8 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-08-27 5:37 UTC (permalink / raw) To: Git Mailing List Cc: Junio C Hamano, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Philip Oakley, Pratyush Yadav From: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Change "same head" introduced in the preceding commit to check whether the rebase.c code lands in the can_fast_forward() case in, and thus prints out an "is up to date" and aborts early. In some of these cases we make it past that and to "rewinding head", then do a rebase, only to find out there's nothing to change so HEAD stays at the same OID. These tests presumed these two cases were the same thing. In terms of where HEAD ends up they are, but we're not only interested in rebase semantics, but also whether or not we're needlessly doing work when we could avoid it entirely. I'm adding "same" and "diff" here because I'll follow-up and add --no-ff tests, where some of those will be "diff"-erent, so add the "diff" code already. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- t/t3432-rebase-fast-forward.sh | 79 +++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index f49af274e0..d9f20fa07c 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -18,55 +18,72 @@ test_expect_success setup ' test_rebase_same_head () { status="$1" && shift && - test_expect_$status "git rebase $* with $changes is no-op" " + what="$1" && + shift && + cmp="$1" && + shift && + test_expect_$status "git rebase $* with $changes is $what" " oldhead=\$(git rev-parse HEAD) && test_when_finished 'git reset --hard \$oldhead' && - git rebase $* && + git rebase $* >stdout && + if test $what = work + then + test_i18ngrep 'rewinding head' stdout + elif test $what = noop + then + test_i18ngrep 'is up to date' stdout + fi && newhead=\$(git rev-parse HEAD) && - test_cmp_rev \$oldhead \$newhead + if test $cmp = same + then + test_cmp_rev \$oldhead \$newhead + elif test $cmp = diff + then + ! test_cmp_rev \$oldhead \$newhead + fi " } changes='no changes' -test_rebase_same_head success -test_rebase_same_head success master -test_rebase_same_head success --onto B B -test_rebase_same_head success --onto B... B -test_rebase_same_head success --onto master... master -test_rebase_same_head success --no-fork-point -test_rebase_same_head success --fork-point master -test_rebase_same_head failure --fork-point --onto B B -test_rebase_same_head failure --fork-point --onto B... B -test_rebase_same_head success --fork-point --onto master... master +test_rebase_same_head success work same +test_rebase_same_head success noop same master +test_rebase_same_head success noop same --onto B B +test_rebase_same_head success noop same --onto B... B +test_rebase_same_head success noop same --onto master... master +test_rebase_same_head success noop same --no-fork-point +test_rebase_same_head success work same --fork-point master +test_rebase_same_head failure noop same --fork-point --onto B B +test_rebase_same_head failure work same --fork-point --onto B... B +test_rebase_same_head success work same --fork-point --onto master... master -test_expect_success 'add work to side' ' +test_expect_success 'add work same to side' ' test_commit E ' changes='our changes' -test_rebase_same_head success -test_rebase_same_head success master -test_rebase_same_head success --onto B B -test_rebase_same_head success --onto B... B -test_rebase_same_head success --onto master... master -test_rebase_same_head success --no-fork-point -test_rebase_same_head success --fork-point master -test_rebase_same_head failure --fork-point --onto B B -test_rebase_same_head failure --fork-point --onto B... B -test_rebase_same_head success --fork-point --onto master... master +test_rebase_same_head success work same +test_rebase_same_head success noop same master +test_rebase_same_head success noop same --onto B B +test_rebase_same_head success noop same --onto B... B +test_rebase_same_head success noop same --onto master... master +test_rebase_same_head success noop same --no-fork-point +test_rebase_same_head success work same --fork-point master +test_rebase_same_head failure work same --fork-point --onto B B +test_rebase_same_head failure work same --fork-point --onto B... B +test_rebase_same_head success work same --fork-point --onto master... master -test_expect_success 'add work to upstream' ' +test_expect_success 'add work same to upstream' ' git checkout master && test_commit F && git checkout side ' changes='our and their changes' -test_rebase_same_head success --onto B B -test_rebase_same_head success --onto B... B -test_rebase_same_head failure --onto master... master -test_rebase_same_head failure --fork-point --onto B B -test_rebase_same_head failure --fork-point --onto B... B -test_rebase_same_head failure --fork-point --onto master... master +test_rebase_same_head success noop same --onto B B +test_rebase_same_head success noop same --onto B... B +test_rebase_same_head failure work same --onto master... master +test_rebase_same_head failure work same --fork-point --onto B B +test_rebase_same_head failure work same --fork-point --onto B... B +test_rebase_same_head failure work same --fork-point --onto master... master test_done -- 2.23.0.248.g3a9dd8fb08 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v10 4/9] t3432: test for --no-ff's interaction with fast-forward 2019-08-27 5:37 ` [PATCH v10 " Denton Liu ` (2 preceding siblings ...) 2019-08-27 5:37 ` [PATCH v10 3/9] t3432: distinguish "noop-same" v.s. "work-same" in "same head" tests Denton Liu @ 2019-08-27 5:37 ` Denton Liu 2019-08-27 8:11 ` SZEDER Gábor 2019-08-27 5:37 ` [PATCH v10 5/9] rebase: refactor can_fast_forward into goto tower Denton Liu ` (4 subsequent siblings) 8 siblings, 1 reply; 123+ messages in thread From: Denton Liu @ 2019-08-27 5:37 UTC (permalink / raw) To: Git Mailing List Cc: Junio C Hamano, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Philip Oakley, Pratyush Yadav From: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Add more stress tests for the can_fast_forward() case in rebase.c. These tests are getting rather verbose, but now we can see under --ff and --no-ff whether we skip work, or whether we're forced to run the rebase. These tests aren't supposed to endorse the status quo, just test for what we're currently doing. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- t/t3432-rebase-fast-forward.sh | 83 ++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 29 deletions(-) diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index d9f20fa07c..02b2516595 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -16,22 +16,47 @@ test_expect_success setup ' ' test_rebase_same_head () { + status_n="$1" && + shift && + what_n="$1" && + shift && + cmp_n="$1" && + shift && + status_f="$1" && + shift && + what_f="$1" && + 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="$1" && shift && what="$1" && shift && cmp="$1" && shift && - test_expect_$status "git rebase $* with $changes is $what" " + flag="$1" + shift && + 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' && - git rebase $* >stdout && + 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 elif test $what = noop then - test_i18ngrep 'is up to date' stdout + 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 fi && newhead=\$(git rev-parse HEAD) && if test $cmp = same @@ -45,32 +70,32 @@ test_rebase_same_head () { } changes='no changes' -test_rebase_same_head success work same -test_rebase_same_head success noop same master -test_rebase_same_head success noop same --onto B B -test_rebase_same_head success noop same --onto B... B -test_rebase_same_head success noop same --onto master... master -test_rebase_same_head success noop same --no-fork-point -test_rebase_same_head success work same --fork-point master -test_rebase_same_head failure noop same --fork-point --onto B B -test_rebase_same_head failure work same --fork-point --onto B... B -test_rebase_same_head success work same --fork-point --onto master... master +test_rebase_same_head success work 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 --no-fork-point +test_rebase_same_head success work same success work same --fork-point master +test_rebase_same_head failure noop 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 same --fork-point --onto master... master test_expect_success 'add work same to side' ' test_commit E ' changes='our changes' -test_rebase_same_head success work same -test_rebase_same_head success noop same master -test_rebase_same_head success noop same --onto B B -test_rebase_same_head success noop same --onto B... B -test_rebase_same_head success noop same --onto master... master -test_rebase_same_head success noop same --no-fork-point -test_rebase_same_head success work same --fork-point master -test_rebase_same_head failure work same --fork-point --onto B B -test_rebase_same_head failure work same --fork-point --onto B... B -test_rebase_same_head success work same --fork-point --onto master... master +test_rebase_same_head success work 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 --no-fork-point +test_rebase_same_head success work same success work same --fork-point master +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 same --fork-point --onto master... master test_expect_success 'add work same to upstream' ' git checkout master && @@ -79,11 +104,11 @@ test_expect_success 'add work same to upstream' ' ' changes='our and their changes' -test_rebase_same_head success noop same --onto B B -test_rebase_same_head success noop same --onto B... B -test_rebase_same_head failure work same --onto master... master -test_rebase_same_head failure work same --fork-point --onto B B -test_rebase_same_head failure work same --fork-point --onto B... B -test_rebase_same_head failure work same --fork-point --onto master... 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 failure work same success work diff --onto master... master +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 failure work same success work diff --fork-point --onto master... master test_done -- 2.23.0.248.g3a9dd8fb08 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* Re: [PATCH v10 4/9] t3432: test for --no-ff's interaction with fast-forward 2019-08-27 5:37 ` [PATCH v10 4/9] t3432: test for --no-ff's interaction with fast-forward Denton Liu @ 2019-08-27 8:11 ` SZEDER Gábor 0 siblings, 0 replies; 123+ messages in thread From: SZEDER Gábor @ 2019-08-27 8:11 UTC (permalink / raw) To: Denton Liu Cc: Git Mailing List, Junio C Hamano, Eric Sunshine, Johannes Schindelin, Johannes Sixt, Ævar Arnfjörð Bjarmason, Philip Oakley, Pratyush Yadav On Tue, Aug 27, 2019 at 01:37:53AM -0400, Denton Liu wrote: > From: Ævar Arnfjörð Bjarmason <avarab@gmail.com> > + 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' && > - git rebase $* >stdout && > + 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 > elif test $what = noop > then > - test_i18ngrep 'is up to date' stdout > + test_i18ngrep 'is up to date' stdout && > + ! test_i18ngrep 'rebase forced' stdout This must be written as 'test_i18ngrep ! ....'. When running the test with GIT_TEST_GETTEXT_POISON=true, then 'test_i18ngrep' is basically a noop and always returns with success, the leading ! turns that into a failure, which then fails the test. ^ permalink raw reply [flat|nested] 123+ messages in thread
* [PATCH v10 5/9] rebase: refactor can_fast_forward into goto tower 2019-08-27 5:37 ` [PATCH v10 " Denton Liu ` (3 preceding siblings ...) 2019-08-27 5:37 ` [PATCH v10 4/9] t3432: test for --no-ff's interaction with fast-forward Denton Liu @ 2019-08-27 5:37 ` Denton Liu 2019-08-27 5:37 ` [PATCH v10 6/9] rebase: fast-forward --onto in more cases Denton Liu ` (3 subsequent siblings) 8 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-08-27 5:37 UTC (permalink / raw) To: Git Mailing List Cc: Junio C Hamano, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Philip Oakley, Pratyush Yadav Before, can_fast_forward was written with an if-else statement. However, in the future, we may be adding more termination cases which would lead to deeply nested if statements. Refactor to use a goto tower so that future cases can be easily inserted. Signed-off-by: Denton Liu <liu.denton@gmail.com> --- builtin/rebase.c | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 670096c065..1ddad46126 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1264,20 +1264,25 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, struct object_id *merge_base) { struct commit *head = lookup_commit(the_repository, head_oid); - struct commit_list *merge_bases; - int res; + struct commit_list *merge_bases = NULL; + int res = 0; if (!head) - return 0; + goto done; merge_bases = get_merge_bases(onto, head); - if (merge_bases && !merge_bases->next) { - oidcpy(merge_base, &merge_bases->item->object.oid); - res = oideq(merge_base, &onto->object.oid); - } else { + if (!merge_bases || merge_bases->next) { oidcpy(merge_base, &null_oid); - res = 0; + goto done; } + + oidcpy(merge_base, &merge_bases->item->object.oid); + if (!oideq(merge_base, &onto->object.oid)) + goto done; + + res = 1; + +done: free_commit_list(merge_bases); return res && is_linear_history(onto, head); } -- 2.23.0.248.g3a9dd8fb08 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v10 6/9] rebase: fast-forward --onto in more cases 2019-08-27 5:37 ` [PATCH v10 " Denton Liu ` (4 preceding siblings ...) 2019-08-27 5:37 ` [PATCH v10 5/9] rebase: refactor can_fast_forward into goto tower Denton Liu @ 2019-08-27 5:37 ` Denton Liu 2019-08-27 5:38 ` [PATCH v10 7/9] rebase: fast-forward --fork-point " Denton Liu ` (2 subsequent siblings) 8 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-08-27 5:37 UTC (permalink / raw) To: Git Mailing List Cc: Junio C Hamano, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Philip Oakley, Pratyush Yadav Before, when we had the following graph, A---B---C (master) \ D (side) running 'git rebase --onto master... master side' would result in D being always rebased, no matter what. However, the desired behavior is that rebase should notice that this is fast-forwardable and do that instead. Add detection to `can_fast_forward` so that this case can be detected and a fast-forward will be performed. First of all, rewrite the function to use gotos which simplifies the logic. Next, since the options.upstream && !oidcmp(&options.upstream->object.oid, &options.onto->object.oid) conditions were removed in `cmd_rebase`, we reintroduce a substitute in `can_fast_forward`. In particular, checking the merge bases of `upstream` and `head` fixes a failing case in t3416. The abbreviated graph for t3416 is as follows: F---G topic / A---B---C---D---E master and the failing command was git rebase --onto master...topic F topic Before, Git would see that there was one merge base (C), and the merge and onto were the same so it would incorrectly return 1, indicating that we could fast-forward. This would cause the rebased graph to be 'ABCFG' when we were expecting 'ABCG'. With the additional logic, we detect that upstream and head's merge base is F. Since onto isn't F, it means we're not rebasing the full set of commits from master..topic. Since we're excluding some commits, a fast-forward cannot be performed and so we correctly return 0. Add '-f' to test cases that failed as a result of this change because they were not expecting a fast-forward so that a rebase is forced. Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- builtin/rebase.c | 27 +++++++++++++++++++-------- t/t3400-rebase.sh | 2 +- t/t3404-rebase-interactive.sh | 2 +- t/t3432-rebase-fast-forward.sh | 4 ++-- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 1ddad46126..1e1406c8ba 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1260,8 +1260,8 @@ static int is_linear_history(struct commit *from, struct commit *to) return 1; } -static int can_fast_forward(struct commit *onto, struct object_id *head_oid, - struct object_id *merge_base) +static int can_fast_forward(struct commit *onto, struct commit *upstream, + struct object_id *head_oid, struct object_id *merge_base) { struct commit *head = lookup_commit(the_repository, head_oid); struct commit_list *merge_bases = NULL; @@ -1280,6 +1280,17 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, if (!oideq(merge_base, &onto->object.oid)) goto done; + if (!upstream) + goto done; + + free_commit_list(merge_bases); + merge_bases = get_merge_bases(upstream, head); + if (!merge_bases || merge_bases->next) + goto done; + + if (!oideq(&onto->object.oid, &merge_bases->item->object.oid)) + goto done; + res = 1; done: @@ -2027,13 +2038,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) /* * Check if we are already based on onto with linear history, - * but this should be done only when upstream and onto are the same - * and if this is not an interactive rebase. + * 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. */ - if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && - !is_interactive(&options) && !options.restrict_revision && - options.upstream && - !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { + if (can_fast_forward(options.onto, options.upstream, &options.orig_head, + &merge_base) && + !is_interactive(&options) && !options.restrict_revision) { int flag; if (!(options.flags & REBASE_FORCE)) { diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 80b23fd326..d7c724bea3 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -295,7 +295,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 --onto init HEAD^ && + test_must_fail git rebase -f --onto init HEAD^ && GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && grep "show.*$(git rev-parse two)" stderr ) diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 461dd539ff..3cc9052f10 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1058,7 +1058,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int git reset --hard && git checkout conflict-branch && set_fake_editor && - test_must_fail git rebase --onto HEAD~2 HEAD~ && + test_must_fail git rebase -f --onto HEAD~2 HEAD~ && test_must_fail git rebase --edit-todo && git rebase --abort ' diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index 02b2516595..d9957e5f1e 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -106,9 +106,9 @@ 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 failure work same success work diff --onto master... master +test_rebase_same_head success noop same success work diff --onto master... master 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 failure work same success work diff --fork-point --onto master... master +test_rebase_same_head success noop same success work diff --fork-point --onto master... master test_done -- 2.23.0.248.g3a9dd8fb08 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v10 7/9] rebase: fast-forward --fork-point in more cases 2019-08-27 5:37 ` [PATCH v10 " Denton Liu ` (5 preceding siblings ...) 2019-08-27 5:37 ` [PATCH v10 6/9] rebase: fast-forward --onto in more cases Denton Liu @ 2019-08-27 5:38 ` Denton Liu 2019-08-27 5:38 ` [PATCH v10 8/9] rebase tests: test linear branch topology Denton Liu 2019-08-27 5:38 ` [PATCH v10 9/9] rebase: teach rebase --keep-base Denton Liu 8 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-08-27 5:38 UTC (permalink / raw) To: Git Mailing List Cc: Junio C Hamano, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Philip Oakley, Pratyush Yadav Before, when we rebased with a --fork-point invocation where the fork-point wasn't empty, we would be setting options.restrict_revision. The fast-forward logic would automatically declare that the rebase was not fast-forwardable if it was set. However, this was painting with a very broad brush. Refine the logic so that we can fast-forward in the case where the restricted revision is equal to the merge base, since we stop rebasing at the merge base anyway. Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- builtin/rebase.c | 10 +++++++--- t/t3432-rebase-fast-forward.sh | 20 ++++++++++---------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 1e1406c8ba..7ef9095e7c 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -1261,6 +1261,7 @@ static int is_linear_history(struct commit *from, struct commit *to) } static int can_fast_forward(struct commit *onto, struct commit *upstream, + struct commit *restrict_revision, struct object_id *head_oid, struct object_id *merge_base) { struct commit *head = lookup_commit(the_repository, head_oid); @@ -1280,6 +1281,9 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream, if (!oideq(merge_base, &onto->object.oid)) goto done; + if (restrict_revision && !oideq(&restrict_revision->object.oid, merge_base)) + goto done; + if (!upstream) goto done; @@ -2042,9 +2046,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) * with new commits recreated by replaying their changes. This * optimization must not be done if this is an interactive rebase. */ - if (can_fast_forward(options.onto, options.upstream, &options.orig_head, - &merge_base) && - !is_interactive(&options) && !options.restrict_revision) { + if (can_fast_forward(options.onto, options.upstream, options.restrict_revision, + &options.orig_head, &merge_base) && + !is_interactive(&options)) { int flag; if (!(options.flags & REBASE_FORCE)) { diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index d9957e5f1e..fbedfe7b4a 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -70,32 +70,32 @@ test_rebase_same_head_ () { } changes='no changes' -test_rebase_same_head success work same success work same +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 --no-fork-point -test_rebase_same_head success work same success work same --fork-point master -test_rebase_same_head failure noop 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 same --fork-point --onto master... master +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 +test_rebase_same_head success noop same success work same --fork-point --onto master... master test_expect_success 'add work same to side' ' test_commit E ' changes='our changes' -test_rebase_same_head success work same success work same +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 --no-fork-point -test_rebase_same_head success work same success work same --fork-point master -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 same --fork-point --onto master... master +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 +test_rebase_same_head success noop same success work same --fork-point --onto master... master test_expect_success 'add work same to upstream' ' git checkout master && -- 2.23.0.248.g3a9dd8fb08 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v10 8/9] rebase tests: test linear branch topology 2019-08-27 5:37 ` [PATCH v10 " Denton Liu ` (6 preceding siblings ...) 2019-08-27 5:38 ` [PATCH v10 7/9] rebase: fast-forward --fork-point " Denton Liu @ 2019-08-27 5:38 ` Denton Liu 2019-08-27 5:38 ` [PATCH v10 9/9] rebase: teach rebase --keep-base Denton Liu 8 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-08-27 5:38 UTC (permalink / raw) To: Git Mailing List Cc: Junio C Hamano, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Philip Oakley, Pratyush Yadav From: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Add tests rebasing a linear branch topology to linear rebase tests added in 2aad7cace2 ("add simple tests of consistency across rebase types", 2013-06-06). These tests are duplicates of two surrounding tests that do the same with tags pointing to the same objects. Right now there's no change in behavior being introduced, but as we'll see in a subsequent change rebase can have different behaviors when working implicitly with remote tracking branches. While I'm at it add a --fork-point test, strictly speaking this is redundant to the existing '' test, as no argument to rebase implies --fork-point. But now it's easier to grep for tests that explicitly stress --fork-point. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- t/t3421-rebase-topology-linear.sh | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh index 7274dca40b..b847064f91 100755 --- a/t/t3421-rebase-topology-linear.sh +++ b/t/t3421-rebase-topology-linear.sh @@ -31,6 +31,16 @@ test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p +test_expect_success 'setup branches and remote tracking' ' + git tag -l >tags && + for tag in $(cat tags) + do + git branch branch-$tag $tag || return 1 + done && + git remote add origin "file://$PWD" && + git fetch origin +' + test_run_rebase () { result=$1 shift @@ -57,10 +67,28 @@ test_run_rebase () { " } test_run_rebase success '' +test_run_rebase success --fork-point test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase failure -p +test_run_rebase () { + result=$1 + shift + test_expect_$result "rebase $* -f rewrites even if remote upstream is an ancestor" " + reset_rebase && + git rebase $* -f branch-b branch-e && + ! test_cmp_rev branch-e origin/branch-e && + test_cmp_rev branch-b HEAD~2 && + test_linear_range 'd e' branch-b.. + " +} +test_run_rebase success '' +test_run_rebase success --fork-point +test_run_rebase success -m +test_run_rebase success -i +test_have_prereq !REBASE_P || test_run_rebase success -p + test_run_rebase () { result=$1 shift @@ -71,6 +99,7 @@ test_run_rebase () { " } test_run_rebase success '' +test_run_rebase success --fork-point test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p -- 2.23.0.248.g3a9dd8fb08 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [PATCH v10 9/9] rebase: teach rebase --keep-base 2019-08-27 5:37 ` [PATCH v10 " Denton Liu ` (7 preceding siblings ...) 2019-08-27 5:38 ` [PATCH v10 8/9] rebase tests: test linear branch topology Denton Liu @ 2019-08-27 5:38 ` Denton Liu 8 siblings, 0 replies; 123+ messages in thread From: Denton Liu @ 2019-08-27 5:38 UTC (permalink / raw) To: Git Mailing List Cc: Junio C Hamano, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason, Philip Oakley, Pratyush Yadav A common scenario is if a user is working on a topic branch and they wish to make some changes to intermediate commits or autosquash, they would run something such as git rebase -i --onto master... master in order to preserve the merge base. This is useful when contributing a patch series to the Git mailing list, one often starts on top of the current 'master'. While developing the patches, 'master' is also developed further and it is sometimes not the best idea to keep rebasing on top of 'master', but to keep the base commit as-is. In addition to this, a user wishing to test individual commits in a topic branch without changing anything may run git rebase -x ./test.sh master... master Since rebasing onto the merge base of the branch and the upstream is such a common case, introduce the --keep-base option as a shortcut. This allows us to rewrite the above as git rebase -i --keep-base master and git rebase -x ./test.sh --keep-base master respectively. Add tests to ensure --keep-base works correctly in the normal case and fails when there are multiple merge bases, both in regular and interactive mode. Also, test to make sure conflicting options cause rebase to fail. While we're adding test cases, add a missing set_fake_editor call to 'rebase -i --onto master...side'. While we're documenting the --keep-base option, change an instance of "merge-base" to "merge base", which is the consistent spelling. Helped-by: Eric Sunshine <sunshine@sunshineco.com> Helped-by: Junio C Hamano <gitster@pobox.com> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Denton Liu <liu.denton@gmail.com> --- Documentation/git-rebase.txt | 30 ++++++++++++-- builtin/rebase.c | 32 ++++++++++++--- contrib/completion/git-completion.bash | 2 +- t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++ t/t3431-rebase-fork-point.sh | 4 ++ t/t3432-rebase-fast-forward.sh | 11 +++++ 6 files changed, 126 insertions(+), 10 deletions(-) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index 6156609cf7..3146c1592d 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip SYNOPSIS -------- [verse] -'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] - [<upstream> [<branch>]] +'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] + [--onto <newbase> | --keep-base] [<upstream> [<branch>]] 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] --root [<branch>] 'git rebase' (--continue | --skip | --abort | --quit | --edit-todo | --show-current-patch) @@ -217,6 +217,24 @@ As a special case, you may use "A\...B" as a shortcut for the merge base of A and B if there is exactly one merge base. You can leave out at most one of A and B, in which case it defaults to HEAD. +--keep-base:: + Set the starting point at which to create the new commits to the + merge base of <upstream> <branch>. Running + 'git rebase --keep-base <upstream> <branch>' is equivalent to + running 'git rebase --onto <upstream>... <upstream>'. ++ +This option is useful in the case where one is developing a feature on +top of an upstream branch. While the feature is being worked on, the +upstream branch may advance and it may not be the best idea to keep +rebasing on top of the upstream but to keep the base commit as-is. ++ +Although both this option and --fork-point find the merge base between +<upstream> and <branch>, this option uses the merge base as the _starting +point_ on which new commits will be created, whereas --fork-point uses +the merge base to determine the _set of commits_ which will be rebased. ++ +See also INCOMPATIBLE OPTIONS below. + <upstream>:: Upstream branch to compare against. May be any valid commit, not just an existing branch name. Defaults to the configured @@ -369,6 +387,10 @@ ends up being empty, the <upstream> will be used as a fallback. + If either <upstream> or --root is given on the command line, then the default is `--no-fork-point`, otherwise the default is `--fork-point`. ++ +If your branch was based on <upstream> but <upstream> was rewound and +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:: --whitespace=<option>:: @@ -545,6 +567,8 @@ In addition, the following pairs of options are incompatible: * --preserve-merges and --rebase-merges * --rebase-merges and --strategy * --rebase-merges and --strategy-option + * --keep-base and --onto + * --keep-base and --root BEHAVIORAL DIFFERENCES ----------------------- @@ -870,7 +894,7 @@ NOTE: While an "easy case recovery" sometimes appears to be successful --interactive` will be **resurrected**! The idea is to manually tell 'git rebase' "where the old 'subsystem' -ended and your 'topic' began", that is, what the old merge-base +ended and your 'topic' began", that is, what the old merge base between them was. You will have to find a way to name the last commit of the old 'subsystem', for example: diff --git a/builtin/rebase.c b/builtin/rebase.c index 7ef9095e7c..27d5c4e19d 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -29,8 +29,8 @@ #include "rebase-interactive.h" static char const * const builtin_rebase_usage[] = { - N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " - "[<upstream>] [<branch>]"), + N_("git rebase [-i] [options] [--exec <cmd>] " + "[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"), N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " "--root [<branch>]"), N_("git rebase --continue | --abort | --skip | --edit-todo"), @@ -1396,6 +1396,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) struct rebase_options options = REBASE_OPTIONS_INIT; const char *branch_name; int ret, flags, total_argc, in_progress = 0; + int keep_base = 0; int ok_to_skip_pre_rebase = 0; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; @@ -1414,6 +1415,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_STRING(0, "onto", &options.onto_name, N_("revision"), N_("rebase onto given branch instead of upstream")), + OPT_BOOL(0, "keep-base", &keep_base, + N_("use the merge-base of upstream and branch as the current base")), OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase, N_("allow pre-rebase hook to run")), OPT_NEGBIT('q', "quiet", &options.flags, @@ -1567,6 +1570,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) warning(_("git rebase --preserve-merges is deprecated. " "Use --rebase-merges instead.")); + if (keep_base) { + if (options.onto_name) + die(_("cannot combine '--keep-base' with '--onto'")); + if (options.root) + die(_("cannot combine '--keep-base' with '--root'")); + } + if (action != ACTION_NONE && !in_progress) die(_("No rebase in progress?")); setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0); @@ -1902,12 +1912,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } /* Make sure the branch to rebase onto is valid. */ - if (!options.onto_name) + if (keep_base) { + strbuf_reset(&buf); + strbuf_addstr(&buf, options.upstream_name); + strbuf_addstr(&buf, "..."); + options.onto_name = xstrdup(buf.buf); + } else if (!options.onto_name) options.onto_name = options.upstream_name; if (strstr(options.onto_name, "...")) { - if (get_oid_mb(options.onto_name, &merge_base) < 0) - die(_("'%s': need exactly one merge base"), - options.onto_name); + if (get_oid_mb(options.onto_name, &merge_base) < 0) { + if (keep_base) + die(_("'%s': need exactly one merge base with branch"), + options.upstream_name); + else + die(_("'%s': need exactly one merge base"), + options.onto_name); + } options.onto = lookup_commit_or_die(&merge_base, options.onto_name); } else { diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index e087c4bf00..71e7159694 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -2030,7 +2030,7 @@ _git_rebase () --autosquash --no-autosquash --fork-point --no-fork-point --autostash --no-autostash - --verify --no-verify + --verify --no-verify --keep-base --keep-empty --root --force-rebase --no-ff --rerere-autoupdate --exec diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh index ddf2f64853..9c2548423b 100755 --- a/t/t3416-rebase-onto-threedots.sh +++ b/t/t3416-rebase-onto-threedots.sh @@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' ' git checkout side && git reset --hard K && + set_fake_editor && test_must_fail git rebase -i --onto master...side J ' +test_expect_success 'rebase --keep-base --onto incompatible' ' + test_must_fail git rebase --keep-base --onto master... +' + +test_expect_success 'rebase --keep-base --root incompatible' ' + test_must_fail git rebase --keep-base --root +' + +test_expect_success 'rebase --keep-base master from topic' ' + git reset --hard && + git checkout topic && + git reset --hard G && + + git rebase --keep-base master && + git rev-parse C >base.expect && + git merge-base master HEAD >base.actual && + test_cmp base.expect base.actual && + + git rev-parse HEAD~2 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase --keep-base master from side' ' + git reset --hard && + git checkout side && + git reset --hard K && + + test_must_fail git rebase --keep-base master +' + +test_expect_success 'rebase -i --keep-base master from topic' ' + git reset --hard && + git checkout topic && + git reset --hard G && + + set_fake_editor && + EXPECT_COUNT=2 git rebase -i --keep-base master && + git rev-parse C >base.expect && + git merge-base master HEAD >base.actual && + test_cmp base.expect base.actual && + + git rev-parse HEAD~2 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase -i --keep-base master from side' ' + git reset --hard && + git checkout side && + git reset --hard K && + + set_fake_editor && + test_must_fail git rebase -i --keep-base master +' + test_done diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh index 2d5c6e641e..78851b9a2a 100755 --- a/t/t3431-rebase-fork-point.sh +++ b/t/t3431-rebase-fork-point.sh @@ -43,11 +43,15 @@ test_rebase () { test_rebase 'G F E D B A' test_rebase 'G F D B A' --onto D +test_rebase 'G F B A' --keep-base test_rebase 'G F C E D B A' --no-fork-point test_rebase 'G F C D B A' --no-fork-point --onto D +test_rebase 'G F C B A' --no-fork-point --keep-base test_rebase 'G F E D B A' --fork-point refs/heads/master test_rebase 'G F D B A' --fork-point --onto D refs/heads/master +test_rebase 'G F B A' --fork-point --keep-base refs/heads/master test_rebase 'G F C E D B A' refs/heads/master test_rebase 'G F C D B A' --onto D refs/heads/master +test_rebase 'G F C B A' --keep-base refs/heads/master test_done diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index fbedfe7b4a..2f9f82be10 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -75,11 +75,15 @@ 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 --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 test_rebase_same_head success noop same success work same --fork-point --onto master... master +test_rebase_same_head success noop same success work same --keep-base --keep-base master test_expect_success 'add work same to side' ' test_commit E @@ -91,11 +95,15 @@ 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 --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 test_rebase_same_head success noop same success work same --fork-point --onto master... master +test_rebase_same_head success noop same success work same --fork-point --keep-base master test_expect_success 'add work same to upstream' ' git checkout master && @@ -107,8 +115,11 @@ 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 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 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 test_done -- 2.23.0.248.g3a9dd8fb08 ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [RFC WIP PATCH v8 01/13] t3431: add rebase --fork-point tests 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu ` (6 preceding siblings ...) 2019-05-08 0:12 ` [RFC WIP PATCH v8 00/13] learn --keep-base & more Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 ` Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 02/13] t3432: test rebase fast-forward behavior Ævar Arnfjörð Bjarmason ` (11 subsequent siblings) 19 siblings, 0 replies; 123+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 UTC (permalink / raw) To: git Cc: Junio C Hamano, Denton Liu, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor From: Denton Liu <liu.denton@gmail.com> Signed-off-by: Denton Liu <liu.denton@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com> --- t/t3431-rebase-fork-point.sh | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100755 t/t3431-rebase-fork-point.sh diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh new file mode 100755 index 0000000000..2d5c6e641e --- /dev/null +++ b/t/t3431-rebase-fork-point.sh @@ -0,0 +1,53 @@ +#!/bin/sh +# +# Copyright (c) 2019 Denton Liu +# + +test_description='git rebase --fork-point test' + +. ./test-lib.sh + +# A---B---D---E (master) +# \ +# C*---F---G (side) +# +# C was formerly part of master but master was rewound to remove C +# +test_expect_success setup ' + test_commit A && + test_commit B && + test_commit C && + git branch -t side && + git reset --hard HEAD^ && + test_commit D && + test_commit E && + git checkout side && + test_commit F && + test_commit G +' + +test_rebase () { + expected="$1" && + shift && + test_expect_success "git rebase $*" " + git checkout master && + git reset --hard E && + git checkout side && + git reset --hard G && + git rebase $* && + test_write_lines $expected >expect && + git log --pretty=%s >actual && + test_cmp expect actual + " +} + +test_rebase 'G F E D B A' +test_rebase 'G F D B A' --onto D +test_rebase 'G F C E D B A' --no-fork-point +test_rebase 'G F C D B A' --no-fork-point --onto D +test_rebase 'G F E D B A' --fork-point refs/heads/master +test_rebase 'G F D B A' --fork-point --onto D refs/heads/master +test_rebase 'G F C E D B A' refs/heads/master +test_rebase 'G F C D B A' --onto D refs/heads/master + +test_done -- 2.21.0.1020.gf2820cf01a ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [RFC WIP PATCH v8 02/13] t3432: test rebase fast-forward behavior 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu ` (7 preceding siblings ...) 2019-05-08 0:12 ` [RFC WIP PATCH v8 01/13] t3431: add rebase --fork-point tests Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 ` Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 03/13] t3432: distinguish "noop-same" v.s. "work-same" in "same head" tests Ævar Arnfjörð Bjarmason ` (10 subsequent siblings) 19 siblings, 0 replies; 123+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 UTC (permalink / raw) To: git Cc: Junio C Hamano, Denton Liu, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor From: Denton Liu <liu.denton@gmail.com> When rebase is run on a branch that can be fast-forwarded, this should automatically be done. Create test to ensure this behavior happens. There are some cases that currently don't pass. The first case is where a feature and master have diverged, running "git rebase master... master" causes a full rebase to happen even though a fast-forward should happen. The second case is when we are doing "git rebase --fork-point" and a fork-point commit is found. Once again, a full rebase happens even though a fast-forward should happen. Mark these cases as failure so we can fix it later. Signed-off-by: Denton Liu <liu.denton@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com> --- t/t3432-rebase-fast-forward.sh | 72 ++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100755 t/t3432-rebase-fast-forward.sh diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh new file mode 100755 index 0000000000..f49af274e0 --- /dev/null +++ b/t/t3432-rebase-fast-forward.sh @@ -0,0 +1,72 @@ +#!/bin/sh +# +# Copyright (c) 2019 Denton Liu +# + +test_description='ensure rebase fast-forwards commits when possible' + +. ./test-lib.sh + +test_expect_success setup ' + test_commit A && + test_commit B && + test_commit C && + test_commit D && + git checkout -t -b side +' + +test_rebase_same_head () { + status="$1" && + shift && + test_expect_$status "git rebase $* with $changes is no-op" " + oldhead=\$(git rev-parse HEAD) && + test_when_finished 'git reset --hard \$oldhead' && + git rebase $* && + newhead=\$(git rev-parse HEAD) && + test_cmp_rev \$oldhead \$newhead + " +} + +changes='no changes' +test_rebase_same_head success +test_rebase_same_head success master +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head success --onto master... master +test_rebase_same_head success --no-fork-point +test_rebase_same_head success --fork-point master +test_rebase_same_head failure --fork-point --onto B B +test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head success --fork-point --onto master... master + +test_expect_success 'add work to side' ' + test_commit E +' + +changes='our changes' +test_rebase_same_head success +test_rebase_same_head success master +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head success --onto master... master +test_rebase_same_head success --no-fork-point +test_rebase_same_head success --fork-point master +test_rebase_same_head failure --fork-point --onto B B +test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head success --fork-point --onto master... master + +test_expect_success 'add work to upstream' ' + git checkout master && + test_commit F && + git checkout side +' + +changes='our and their changes' +test_rebase_same_head success --onto B B +test_rebase_same_head success --onto B... B +test_rebase_same_head failure --onto master... master +test_rebase_same_head failure --fork-point --onto B B +test_rebase_same_head failure --fork-point --onto B... B +test_rebase_same_head failure --fork-point --onto master... master + +test_done -- 2.21.0.1020.gf2820cf01a ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [RFC WIP PATCH v8 03/13] t3432: distinguish "noop-same" v.s. "work-same" in "same head" tests 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu ` (8 preceding siblings ...) 2019-05-08 0:12 ` [RFC WIP PATCH v8 02/13] t3432: test rebase fast-forward behavior Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 ` Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 04/13] t3432: test for --no-ff's interaction with fast-forward Ævar Arnfjörð Bjarmason ` (9 subsequent siblings) 19 siblings, 0 replies; 123+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 UTC (permalink / raw) To: git Cc: Junio C Hamano, Denton Liu, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason Change "same head" introduced in the preceding commit to check whether the rebase.c code lands in the can_fast_forward() case in, and thus prints out an "is up to date" and aborts early. In some of these cases we make it past that and to "rewinding head", then do a rebase, only to find out there's nothing to change so HEAD stays at the same OID. These tests presumed these two cases were the same thing. In terms of where HEAD ends up they are, but we're not only interested in rebase semantics, but also whether or not we're needlessly doing work when we could avoid it entirely. I'm adding "same" and "diff" here because I'll follow-up and add --no-ff tests, where some of those will be "diff"-erent, so add the "diff" code already. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> --- t/t3432-rebase-fast-forward.sh | 79 +++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index f49af274e0..dd51e28b56 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -18,55 +18,72 @@ test_expect_success setup ' test_rebase_same_head () { status="$1" && shift && - test_expect_$status "git rebase $* with $changes is no-op" " + what="$1" && + shift && + cmp="$1" && + shift && + test_expect_$status "git rebase $* with $changes is $what" " oldhead=\$(git rev-parse HEAD) && test_when_finished 'git reset --hard \$oldhead' && - git rebase $* && + git rebase $* >stdout && + if test $what = work + then + test_i18ngrep 'rewinding head' stdout + elif test $what = noop + then + test_i18ngrep 'is up to date' stdout + fi && newhead=\$(git rev-parse HEAD) && - test_cmp_rev \$oldhead \$newhead + if test $cmp = same + then + test_cmp_rev \$oldhead \$newhead + elif test $cmp = diff + then + ! test_cmp_rev \$oldhead \$newhead + fi " } changes='no changes' -test_rebase_same_head success -test_rebase_same_head success master -test_rebase_same_head success --onto B B -test_rebase_same_head success --onto B... B -test_rebase_same_head success --onto master... master -test_rebase_same_head success --no-fork-point -test_rebase_same_head success --fork-point master -test_rebase_same_head failure --fork-point --onto B B -test_rebase_same_head failure --fork-point --onto B... B -test_rebase_same_head success --fork-point --onto master... master +test_rebase_same_head success work same +test_rebase_same_head success noop same master +test_rebase_same_head success noop same --onto B B +test_rebase_same_head success noop same --onto B... B +test_rebase_same_head success noop same --onto master... master +test_rebase_same_head success noop same --no-fork-point +test_rebase_same_head success work same --fork-point master +test_rebase_same_head failure noop same --fork-point --onto B B +test_rebase_same_head failure work same --fork-point --onto B... B +test_rebase_same_head success work same --fork-point --onto master... master -test_expect_success 'add work to side' ' +test_expect_success 'add work same to side' ' test_commit E ' changes='our changes' -test_rebase_same_head success -test_rebase_same_head success master -test_rebase_same_head success --onto B B -test_rebase_same_head success --onto B... B -test_rebase_same_head success --onto master... master -test_rebase_same_head success --no-fork-point -test_rebase_same_head success --fork-point master -test_rebase_same_head failure --fork-point --onto B B -test_rebase_same_head failure --fork-point --onto B... B -test_rebase_same_head success --fork-point --onto master... master +test_rebase_same_head success work same +test_rebase_same_head success noop same master +test_rebase_same_head success noop same --onto B B +test_rebase_same_head success noop same --onto B... B +test_rebase_same_head success noop same --onto master... master +test_rebase_same_head success noop same --no-fork-point +test_rebase_same_head success work same --fork-point master +test_rebase_same_head failure work same --fork-point --onto B B +test_rebase_same_head failure work same --fork-point --onto B... B +test_rebase_same_head success work same --fork-point --onto master... master -test_expect_success 'add work to upstream' ' +test_expect_success 'add work same to upstream' ' git checkout master && test_commit F && git checkout side ' changes='our and their changes' -test_rebase_same_head success --onto B B -test_rebase_same_head success --onto B... B -test_rebase_same_head failure --onto master... master -test_rebase_same_head failure --fork-point --onto B B -test_rebase_same_head failure --fork-point --onto B... B -test_rebase_same_head failure --fork-point --onto master... master +test_rebase_same_head success noop same --onto B B +test_rebase_same_head success noop same --onto B... B +test_rebase_same_head failure work same --onto master... master +test_rebase_same_head failure work same --fork-point --onto B B +test_rebase_same_head failure work same --fork-point --onto B... B +test_rebase_same_head failure work same --fork-point --onto master... master test_done -- 2.21.0.1020.gf2820cf01a ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [RFC WIP PATCH v8 04/13] t3432: test for --no-ff's interaction with fast-forward 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu ` (9 preceding siblings ...) 2019-05-08 0:12 ` [RFC WIP PATCH v8 03/13] t3432: distinguish "noop-same" v.s. "work-same" in "same head" tests Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 ` Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 05/13] rebase: refactor can_fast_forward into goto tower Ævar Arnfjörð Bjarmason ` (8 subsequent siblings) 19 siblings, 0 replies; 123+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 UTC (permalink / raw) To: git Cc: Junio C Hamano, Denton Liu, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason Add more stress tests for the can_fast_forward() case in rebase.c. These tests are getting rather verbose, but now we can see under --ff and --no-ff whether we skip work, or whether we're forced to run the rebase. These tests aren't supposed to endorse the status quo, just test for what we're currently doing. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> --- t/t3432-rebase-fast-forward.sh | 83 ++++++++++++++++++++++------------ 1 file changed, 54 insertions(+), 29 deletions(-) diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index dd51e28b56..e20a8ab9c4 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -16,22 +16,47 @@ test_expect_success setup ' ' test_rebase_same_head () { + status_n="$1" && + shift && + what_n="$1" && + shift && + cmp_n="$1" && + shift && + status_f="$1" && + shift && + what_f="$1" && + 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="$1" && shift && what="$1" && shift && cmp="$1" && shift && - test_expect_$status "git rebase $* with $changes is $what" " + flag="$1" + shift && + 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' && - git rebase $* >stdout && + 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 elif test $what = noop then - test_i18ngrep 'is up to date' stdout + 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 fi && newhead=\$(git rev-parse HEAD) && if test $cmp = same @@ -45,32 +70,32 @@ test_rebase_same_head () { } changes='no changes' -test_rebase_same_head success work same -test_rebase_same_head success noop same master -test_rebase_same_head success noop same --onto B B -test_rebase_same_head success noop same --onto B... B -test_rebase_same_head success noop same --onto master... master -test_rebase_same_head success noop same --no-fork-point -test_rebase_same_head success work same --fork-point master -test_rebase_same_head failure noop same --fork-point --onto B B -test_rebase_same_head failure work same --fork-point --onto B... B -test_rebase_same_head success work same --fork-point --onto master... master +test_rebase_same_head success work 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 --no-fork-point +test_rebase_same_head success work same success work same --fork-point master +test_rebase_same_head failure noop 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 same --fork-point --onto master... master test_expect_success 'add work same to side' ' test_commit E ' changes='our changes' -test_rebase_same_head success work same -test_rebase_same_head success noop same master -test_rebase_same_head success noop same --onto B B -test_rebase_same_head success noop same --onto B... B -test_rebase_same_head success noop same --onto master... master -test_rebase_same_head success noop same --no-fork-point -test_rebase_same_head success work same --fork-point master -test_rebase_same_head failure work same --fork-point --onto B B -test_rebase_same_head failure work same --fork-point --onto B... B -test_rebase_same_head success work same --fork-point --onto master... master +test_rebase_same_head success work 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 --no-fork-point +test_rebase_same_head success work same success work same --fork-point master +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 same --fork-point --onto master... master test_expect_success 'add work same to upstream' ' git checkout master && @@ -79,11 +104,11 @@ test_expect_success 'add work same to upstream' ' ' changes='our and their changes' -test_rebase_same_head success noop same --onto B B -test_rebase_same_head success noop same --onto B... B -test_rebase_same_head failure work same --onto master... master -test_rebase_same_head failure work same --fork-point --onto B B -test_rebase_same_head failure work same --fork-point --onto B... B -test_rebase_same_head failure work same --fork-point --onto master... 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 failure work same success work diff --onto master... master +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 failure work same success work diff --fork-point --onto master... master test_done -- 2.21.0.1020.gf2820cf01a ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [RFC WIP PATCH v8 05/13] rebase: refactor can_fast_forward into goto tower 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu ` (10 preceding siblings ...) 2019-05-08 0:12 ` [RFC WIP PATCH v8 04/13] t3432: test for --no-ff's interaction with fast-forward Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 ` Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 06/13] rebase: fast-forward --onto in more cases Ævar Arnfjörð Bjarmason ` (7 subsequent siblings) 19 siblings, 0 replies; 123+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 UTC (permalink / raw) To: git Cc: Junio C Hamano, Denton Liu, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor From: Denton Liu <liu.denton@gmail.com> Before, can_fast_forward was written with an if-else statement. However, in the future, we may be adding more termination cases which would lead to deeply nested if statements. Refactor to use a goto tower so that future cases can be easily inserted. Signed-off-by: Denton Liu <liu.denton@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com> --- builtin/rebase.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 2e41ad5644..f7008b10af 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -882,21 +882,27 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, struct object_id *merge_base) { struct commit *head = lookup_commit(the_repository, head_oid); - struct commit_list *merge_bases; - int res; + struct commit_list *merge_bases = NULL; + int res = 0; if (!head) - return 0; + goto done; merge_bases = get_merge_bases(onto, head); - if (merge_bases && !merge_bases->next) { - oidcpy(merge_base, &merge_bases->item->object.oid); - res = oideq(merge_base, &onto->object.oid); - } else { + if (!merge_bases || merge_bases->next) { oidcpy(merge_base, &null_oid); - res = 0; + goto done; } - free_commit_list(merge_bases); + + oidcpy(merge_base, &merge_bases->item->object.oid); + if (!oideq(merge_base, &onto->object.oid)) + goto done; + + res = 1; + +done: + if (merge_bases) + free_commit_list(merge_bases); return res && is_linear_history(onto, head); } -- 2.21.0.1020.gf2820cf01a ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [RFC WIP PATCH v8 06/13] rebase: fast-forward --onto in more cases 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu ` (11 preceding siblings ...) 2019-05-08 0:12 ` [RFC WIP PATCH v8 05/13] rebase: refactor can_fast_forward into goto tower Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 ` Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 07/13] rebase: fast-forward --fork-point " Ævar Arnfjörð Bjarmason ` (6 subsequent siblings) 19 siblings, 0 replies; 123+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 UTC (permalink / raw) To: git Cc: Junio C Hamano, Denton Liu, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Phillip Wood From: Denton Liu <liu.denton@gmail.com> Before, when we had the following graph, A---B---C (master) \ D (side) running 'git rebase --onto master... master side' would result in D being always rebased, no matter what. However, the desired behavior is that rebase should notice that this is fast-forwardable and do that instead. Add detection to `can_fast_forward` so that this case can be detected and a fast-forward will be performed. First of all, rewrite the function to use gotos which simplifies the logic. Next, since the options.upstream && !oidcmp(&options.upstream->object.oid, &options.onto->object.oid) conditions were removed in `cmd_rebase`, we reintroduce a substitute in `can_fast_forward`. In particular, checking the merge bases of `upstream` and `head` fixes a failing case in t3416. The abbreviated graph for t3416 is as follows: F---G topic / A---B---C---D---E master and the failing command was git rebase --onto master...topic F topic Before, Git would see that there was one merge base (C), and the merge and onto were the same so it would incorrectly return 1, indicating that we could fast-forward. This would cause the rebased graph to be 'ABCFG' when we were expecting 'ABCG'. With the additional logic, we detect that upstream and head's merge base is F. Since onto isn't F, it means we're not rebasing the full set of commits from master..topic. Since we're excluding some commits, a fast-forward cannot be performed and so we correctly return 0. Add '-f' to test cases that failed as a result of this change because they were not expecting a fast-forward so that a rebase is forced. While we're at it, remove a trailing whitespace from rebase.c. Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk> Signed-off-by: Denton Liu <liu.denton@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com> --- builtin/rebase.c | 29 ++++++++++++++++++++--------- t/t3400-rebase.sh | 2 +- t/t3404-rebase-interactive.sh | 2 +- t/t3432-rebase-fast-forward.sh | 4 ++-- 4 files changed, 24 insertions(+), 13 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index f7008b10af..c1000c159d 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -878,8 +878,8 @@ static int is_linear_history(struct commit *from, struct commit *to) return 1; } -static int can_fast_forward(struct commit *onto, struct object_id *head_oid, - struct object_id *merge_base) +static int can_fast_forward(struct commit *onto, struct commit *upstream, + struct object_id *head_oid, struct object_id *merge_base) { struct commit *head = lookup_commit(the_repository, head_oid); struct commit_list *merge_bases = NULL; @@ -898,6 +898,17 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid, if (!oideq(merge_base, &onto->object.oid)) goto done; + if (!upstream) + goto done; + + free_commit_list(merge_bases); + merge_bases = get_merge_bases(upstream, head); + if (!merge_bases || merge_bases->next) + goto done; + + if (!oideq(&onto->object.oid, &merge_bases->item->object.oid)) + goto done; + res = 1; done: @@ -1664,13 +1675,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) /* * Check if we are already based on onto with linear history, - * but this should be done only when upstream and onto are the same - * and if this is not an interactive rebase. + * 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. */ - if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && - !is_interactive(&options) && !options.restrict_revision && - options.upstream && - !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { + if (can_fast_forward(options.onto, options.upstream, &options.orig_head, + &merge_base) && + !is_interactive(&options) && !options.restrict_revision) { int flag; if (!(options.flags & REBASE_FORCE)) { @@ -1764,7 +1775,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&msg, "%s: checkout %s", getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name); if (reset_head(&options.onto->object.oid, "checkout", NULL, - RESET_HEAD_DETACH | RESET_ORIG_HEAD | + RESET_HEAD_DETACH | RESET_ORIG_HEAD | RESET_HEAD_RUN_POST_CHECKOUT_HOOK, NULL, msg.buf)) die(_("Could not detach HEAD")); diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh index 42f147858d..45f1f65c7b 100755 --- a/t/t3400-rebase.sh +++ b/t/t3400-rebase.sh @@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' ' echo two >>init.t && git commit -a -m two && git tag two && - test_must_fail git rebase --onto init HEAD^ && + test_must_fail git rebase -f --onto init HEAD^ && GIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr && grep "show.*$(git rev-parse two)" stderr ) diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh index 1723e1a858..c7dbbd16db 100755 --- a/t/t3404-rebase-interactive.sh +++ b/t/t3404-rebase-interactive.sh @@ -1063,7 +1063,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int git reset --hard && git checkout conflict-branch && set_fake_editor && - test_must_fail git rebase --onto HEAD~2 HEAD~ && + test_must_fail git rebase -f --onto HEAD~2 HEAD~ && test_must_fail git rebase --edit-todo && git rebase --abort ' diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index e20a8ab9c4..59cd437301 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -106,9 +106,9 @@ 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 failure work same success work diff --onto master... master +test_rebase_same_head success noop same success work diff --onto master... master 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 failure work same success work diff --fork-point --onto master... master +test_rebase_same_head success noop same success work diff --fork-point --onto master... master test_done -- 2.21.0.1020.gf2820cf01a ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [RFC WIP PATCH v8 07/13] rebase: fast-forward --fork-point in more cases 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu ` (12 preceding siblings ...) 2019-05-08 0:12 ` [RFC WIP PATCH v8 06/13] rebase: fast-forward --onto in more cases Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 ` Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 08/13] rebase: teach rebase --keep-base Ævar Arnfjörð Bjarmason ` (5 subsequent siblings) 19 siblings, 0 replies; 123+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 UTC (permalink / raw) To: git Cc: Junio C Hamano, Denton Liu, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason From: Denton Liu <liu.denton@gmail.com> Before, when we rebased with a --fork-point invocation where the fork-point wasn't empty, we would be setting options.restrict_revision. The fast-forward logic would automatically declare that the rebase was not fast-forwardable if it was set. However, this was painting with a very broad brush. Refine the logic so that we can fast-forward in the case where the restricted revision is equal to the merge base, since we stop rebasing at the merge base anyway. Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Denton Liu <liu.denton@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com> --- builtin/rebase.c | 10 +++++++--- t/t3432-rebase-fast-forward.sh | 20 ++++++++++---------- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index c1000c159d..5f9beda46b 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -879,6 +879,7 @@ static int is_linear_history(struct commit *from, struct commit *to) } static int can_fast_forward(struct commit *onto, struct commit *upstream, + struct commit *restrict_revision, struct object_id *head_oid, struct object_id *merge_base) { struct commit *head = lookup_commit(the_repository, head_oid); @@ -898,6 +899,9 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream, if (!oideq(merge_base, &onto->object.oid)) goto done; + if (restrict_revision && !oideq(&restrict_revision->object.oid, merge_base)) + goto done; + if (!upstream) goto done; @@ -1679,9 +1683,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) * with new commits recreated by replaying their changes. This * optimization must not be done if this is an interactive rebase. */ - if (can_fast_forward(options.onto, options.upstream, &options.orig_head, - &merge_base) && - !is_interactive(&options) && !options.restrict_revision) { + if (can_fast_forward(options.onto, options.upstream, options.restrict_revision, + &options.orig_head, &merge_base) && + !is_interactive(&options)) { int flag; if (!(options.flags & REBASE_FORCE)) { diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index 59cd437301..8f4996e476 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -70,32 +70,32 @@ test_rebase_same_head_ () { } changes='no changes' -test_rebase_same_head success work same success work same +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 --no-fork-point -test_rebase_same_head success work same success work same --fork-point master -test_rebase_same_head failure noop 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 same --fork-point --onto master... master +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 +test_rebase_same_head success noop same success work same --fork-point --onto master... master test_expect_success 'add work same to side' ' test_commit E ' changes='our changes' -test_rebase_same_head success work same success work same +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 --no-fork-point -test_rebase_same_head success work same success work same --fork-point master -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 same --fork-point --onto master... master +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 +test_rebase_same_head success noop same success work same --fork-point --onto master... master test_expect_success 'add work same to upstream' ' git checkout master && -- 2.21.0.1020.gf2820cf01a ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [RFC WIP PATCH v8 08/13] rebase: teach rebase --keep-base 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu ` (13 preceding siblings ...) 2019-05-08 0:12 ` [RFC WIP PATCH v8 07/13] rebase: fast-forward --fork-point " Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 ` Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 09/13] rebase tests: test linear branch topology Ævar Arnfjörð Bjarmason ` (4 subsequent siblings) 19 siblings, 0 replies; 123+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 UTC (permalink / raw) To: git Cc: Junio C Hamano, Denton Liu, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason From: Denton Liu <liu.denton@gmail.com> A common scenario is if a user is working on a topic branch and they wish to make some changes to intermediate commits or autosquash, they would run something such as git rebase -i --onto master... master in order to preserve the merge base. This is useful when contributing a patch series to the Git mailing list, one often starts on top of the current 'master'. However, while developing the patches, 'master' is also developed further and it is sometimes not the bst idea to keep rebasing on top of 'master', but to keep the base commit as-is. Alternatively, a user wishing to test individual commits in a topic branch without changing anything may run git rebase -x ./test.sh master... master Since rebasing onto the merge base of the branch and the upstream is such a common case, introduce the --keep-base option as a shortcut. This allows us to rewrite the above as git rebase -i --keep-base master and git rebase -x ./test.sh --keep-base master respectively. Add tests to ensure --keep-base works correctly in the normal case and fails when there are multiple merge bases, both in regular and interactive mode. Also, test to make sure conflicting options cause rebase to fail. While we're adding test cases, add a missing set_fake_editor call to 'rebase -i --onto master...side'. While we're documenting the --keep-base option, change an instance of "merge-base" to "merge base", which is the consistent spelling. Helped-by: Eric Sunshine <sunshine@sunshineco.com> Helped-by: Junio C Hamano <gitster@pobox.com> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Helped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de> Signed-off-by: Denton Liu <liu.denton@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com> --- Documentation/git-rebase.txt | 30 ++++++++++++-- builtin/rebase.c | 32 ++++++++++++--- contrib/completion/git-completion.bash | 2 +- t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++ t/t3431-rebase-fork-point.sh | 4 ++ t/t3432-rebase-fast-forward.sh | 11 +++++ 6 files changed, 126 insertions(+), 10 deletions(-) diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt index f5e6ae3907..fafb7b4eb3 100644 --- a/Documentation/git-rebase.txt +++ b/Documentation/git-rebase.txt @@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip SYNOPSIS -------- [verse] -'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] - [<upstream> [<branch>]] +'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] + [--onto <newbase> | --keep-base] [<upstream> [<branch>]] 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>] --root [<branch>] 'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch @@ -217,6 +217,24 @@ As a special case, you may use "A\...B" as a shortcut for the merge base of A and B if there is exactly one merge base. You can leave out at most one of A and B, in which case it defaults to HEAD. +--keep-base:: + Set the starting point at which to create the new commits to the + merge base of <upstream> <branch>. Running + 'git rebase --keep-base <upstream> <branch>' is equivalent to + running 'git rebase --onto <upstream>... <upstream>'. ++ +This option is useful in the case where one is developing a feature on +top of an upstream branch. While the feature is being worked on, the +upstream branch may advance and it may not be the best idea to keep +rebasing on top of the upstream but to keep the base commit as-is. ++ +Although both this option and --fork-point find the merge base between +<upstream> and <branch>, this option uses the merge base as the _starting +point_ on which new commits will be created, whereas --fork-point uses +the merge base to determine the _set of commits_ which will be rebased. ++ +See also INCOMPATIBLE OPTIONS below. + <upstream>:: Upstream branch to compare against. May be any valid commit, not just an existing branch name. Defaults to the configured @@ -369,6 +387,10 @@ ends up being empty, the <upstream> will be used as a fallback. + If either <upstream> or --root is given on the command line, then the default is `--no-fork-point`, otherwise the default is `--fork-point`. ++ +If your branch was based on <upstream> but <upstream> was rewound and +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:: --whitespace=<option>:: @@ -545,6 +567,8 @@ In addition, the following pairs of options are incompatible: * --preserve-merges and --rebase-merges * --rebase-merges and --strategy * --rebase-merges and --strategy-option + * --keep-base and --onto + * --keep-base and --root BEHAVIORAL DIFFERENCES ----------------------- @@ -869,7 +893,7 @@ NOTE: While an "easy case recovery" sometimes appears to be successful --interactive` will be **resurrected**! The idea is to manually tell 'git rebase' "where the old 'subsystem' -ended and your 'topic' began", that is, what the old merge-base +ended and your 'topic' began", that is, what the old merge base between them was. You will have to find a way to name the last commit of the old 'subsystem', for example: diff --git a/builtin/rebase.c b/builtin/rebase.c index 5f9beda46b..ae6b9b42b8 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -27,8 +27,8 @@ #include "branch.h" static char const * const builtin_rebase_usage[] = { - N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " - "[<upstream>] [<branch>]"), + N_("git rebase [-i] [options] [--exec <cmd>] " + "[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"), N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " "--root [<branch>]"), N_("git rebase --continue | --abort | --skip | --edit-todo"), @@ -1022,6 +1022,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) }; const char *branch_name; int ret, flags, total_argc, in_progress = 0; + int keep_base = 0; int ok_to_skip_pre_rebase = 0; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; @@ -1055,6 +1056,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_STRING(0, "onto", &options.onto_name, N_("revision"), N_("rebase onto given branch instead of upstream")), + OPT_BOOL(0, "keep-base", &keep_base, + N_("use the merge-base of upstream and branch as the current base")), OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase, N_("allow pre-rebase hook to run")), OPT_NEGBIT('q', "quiet", &options.flags, @@ -1214,6 +1217,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) warning(_("git rebase --preserve-merges is deprecated. " "Use --rebase-merges instead.")); + if (keep_base) { + if (options.onto_name) + die(_("cannot combine '--keep-base' with '--onto'")); + if (options.root) + die(_("cannot combine '--keep-base' with '--root'")); + } + if (action != NO_ACTION && !in_progress) die(_("No rebase in progress?")); setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0); @@ -1538,12 +1548,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } /* Make sure the branch to rebase onto is valid. */ - if (!options.onto_name) + if (keep_base) { + strbuf_reset(&buf); + strbuf_addstr(&buf, options.upstream_name); + strbuf_addstr(&buf, "..."); + options.onto_name = xstrdup(buf.buf); + } else if (!options.onto_name) options.onto_name = options.upstream_name; if (strstr(options.onto_name, "...")) { - if (get_oid_mb(options.onto_name, &merge_base) < 0) - die(_("'%s': need exactly one merge base"), - options.onto_name); + if (get_oid_mb(options.onto_name, &merge_base) < 0) { + if (keep_base) + die(_("'%s': need exactly one merge base with branch"), + options.upstream_name); + else + die(_("'%s': need exactly one merge base"), + options.onto_name); + } options.onto = lookup_commit_or_die(&merge_base, options.onto_name); } else { diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 3eefbabdb1..fe3768c8c6 100644 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -2028,7 +2028,7 @@ _git_rebase () --autosquash --no-autosquash --fork-point --no-fork-point --autostash --no-autostash - --verify --no-verify + --verify --no-verify --keep-base --keep-empty --root --force-rebase --no-ff --rerere-autoupdate --exec diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh index ddf2f64853..9c2548423b 100755 --- a/t/t3416-rebase-onto-threedots.sh +++ b/t/t3416-rebase-onto-threedots.sh @@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' ' git checkout side && git reset --hard K && + set_fake_editor && test_must_fail git rebase -i --onto master...side J ' +test_expect_success 'rebase --keep-base --onto incompatible' ' + test_must_fail git rebase --keep-base --onto master... +' + +test_expect_success 'rebase --keep-base --root incompatible' ' + test_must_fail git rebase --keep-base --root +' + +test_expect_success 'rebase --keep-base master from topic' ' + git reset --hard && + git checkout topic && + git reset --hard G && + + git rebase --keep-base master && + git rev-parse C >base.expect && + git merge-base master HEAD >base.actual && + test_cmp base.expect base.actual && + + git rev-parse HEAD~2 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase --keep-base master from side' ' + git reset --hard && + git checkout side && + git reset --hard K && + + test_must_fail git rebase --keep-base master +' + +test_expect_success 'rebase -i --keep-base master from topic' ' + git reset --hard && + git checkout topic && + git reset --hard G && + + set_fake_editor && + EXPECT_COUNT=2 git rebase -i --keep-base master && + git rev-parse C >base.expect && + git merge-base master HEAD >base.actual && + test_cmp base.expect base.actual && + + git rev-parse HEAD~2 >actual && + git rev-parse C^0 >expect && + test_cmp expect actual +' + +test_expect_success 'rebase -i --keep-base master from side' ' + git reset --hard && + git checkout side && + git reset --hard K && + + set_fake_editor && + test_must_fail git rebase -i --keep-base master +' + test_done diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh index 2d5c6e641e..78851b9a2a 100755 --- a/t/t3431-rebase-fork-point.sh +++ b/t/t3431-rebase-fork-point.sh @@ -43,11 +43,15 @@ test_rebase () { test_rebase 'G F E D B A' test_rebase 'G F D B A' --onto D +test_rebase 'G F B A' --keep-base test_rebase 'G F C E D B A' --no-fork-point test_rebase 'G F C D B A' --no-fork-point --onto D +test_rebase 'G F C B A' --no-fork-point --keep-base test_rebase 'G F E D B A' --fork-point refs/heads/master test_rebase 'G F D B A' --fork-point --onto D refs/heads/master +test_rebase 'G F B A' --fork-point --keep-base refs/heads/master test_rebase 'G F C E D B A' refs/heads/master test_rebase 'G F C D B A' --onto D refs/heads/master +test_rebase 'G F C B A' --keep-base refs/heads/master test_done diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index 8f4996e476..e8a9bf42b6 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -75,11 +75,15 @@ 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 --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 test_rebase_same_head success noop same success work same --fork-point --onto master... master +test_rebase_same_head success noop same success work same --keep-base --keep-base master test_expect_success 'add work same to side' ' test_commit E @@ -91,11 +95,15 @@ 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 --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 test_rebase_same_head success noop same success work same --fork-point --onto master... master +test_rebase_same_head success noop same success work same --fork-point --keep-base master test_expect_success 'add work same to upstream' ' git checkout master && @@ -107,8 +115,11 @@ 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 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 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 test_done -- 2.21.0.1020.gf2820cf01a ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [RFC WIP PATCH v8 09/13] rebase tests: test linear branch topology 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu ` (14 preceding siblings ...) 2019-05-08 0:12 ` [RFC WIP PATCH v8 08/13] rebase: teach rebase --keep-base Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 ` Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 10/13] rebase: don't rebase linear topology with --fork-point Ævar Arnfjörð Bjarmason ` (3 subsequent siblings) 19 siblings, 0 replies; 123+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 UTC (permalink / raw) To: git Cc: Junio C Hamano, Denton Liu, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason Add tests rebasing a linear branch topology to linear rebase tests added in 2aad7cace2 ("add simple tests of consistency across rebase types", 2013-06-06). These tests are duplicates of two surrounding tests that do the same with tags pointing to the same objects. Right now there's no change in behavior being introduced, but as we'll see in a subsequent change rebase can have different behaviors when working implicitly with remote tracking branches. While I'm at it add a --fork-point test, strictly speaking this is redundant to the existing '' test, as no argument to rebase implies --fork-point. But now it's easier to grep for tests that explicitly stress --fork-point. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> --- t/t3421-rebase-topology-linear.sh | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh index 7274dca40b..b847064f91 100755 --- a/t/t3421-rebase-topology-linear.sh +++ b/t/t3421-rebase-topology-linear.sh @@ -31,6 +31,16 @@ test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p +test_expect_success 'setup branches and remote tracking' ' + git tag -l >tags && + for tag in $(cat tags) + do + git branch branch-$tag $tag || return 1 + done && + git remote add origin "file://$PWD" && + git fetch origin +' + test_run_rebase () { result=$1 shift @@ -57,10 +67,28 @@ test_run_rebase () { " } test_run_rebase success '' +test_run_rebase success --fork-point test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase failure -p +test_run_rebase () { + result=$1 + shift + test_expect_$result "rebase $* -f rewrites even if remote upstream is an ancestor" " + reset_rebase && + git rebase $* -f branch-b branch-e && + ! test_cmp_rev branch-e origin/branch-e && + test_cmp_rev branch-b HEAD~2 && + test_linear_range 'd e' branch-b.. + " +} +test_run_rebase success '' +test_run_rebase success --fork-point +test_run_rebase success -m +test_run_rebase success -i +test_have_prereq !REBASE_P || test_run_rebase success -p + test_run_rebase () { result=$1 shift @@ -71,6 +99,7 @@ test_run_rebase () { " } test_run_rebase success '' +test_run_rebase success --fork-point test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p -- 2.21.0.1020.gf2820cf01a ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [RFC WIP PATCH v8 10/13] rebase: don't rebase linear topology with --fork-point 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu ` (15 preceding siblings ...) 2019-05-08 0:12 ` [RFC WIP PATCH v8 09/13] rebase tests: test linear branch topology Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 ` Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 11/13] rebase: eliminate side-effects from can_fast_forward() Ævar Arnfjörð Bjarmason ` (2 subsequent siblings) 19 siblings, 0 replies; 123+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 UTC (permalink / raw) To: git Cc: Junio C Hamano, Denton Liu, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason Fix a regression introduced in 4f21454b55 ("merge-base: handle --fork-point without reflog", 2016-10-12). Before that change having a linear history on top of an upstream master would with --fork-point (aka argument-less rebase) tell us there was nothing to be done: $ git rebase Current branch master is up to date. After that change "rebase" will always redundantly find that it has work to do (it doesn't): $ git rebase First, rewinding head to replay your work on top of it... Applying: [...] Whereas equivalently running: $ git rebase @{upstream} $ git rebase $(git merge-base --fork-point @{u}) Gives us the old behavior of doing nothing. Now, why did we have this regression? Fully digging into it yields an interesting combination of causes: Way back in 1308c17b3e ("Allow rebase to run if upstream is completely merged", 2007-07-04) "rebase" learned to not do this redundant work when asked to rebase on a commit that was already an ancestor of the current commit. Then in 1e0dacdbdb ("rebase: omit patch-identical commits with --fork-point", 2014-07-16) a rebase bug was fixed for a case where the history to be rebased was divergent by entirely skipping the 2007-era logic if --fork-point was provided. But here's the critical thing, *only* if the --fork-point was divergent. At that time "git merge-base --fork-point A B" would return nothing if the two commits weren't divergent. Then in 4f21454b55 ("merge-base: handle --fork-point without reflog", 2016-10-12) which introduced the regression being fixed here, a bug fix for "git merge-base --fork-point" being run stand-alone by proxy broke this use-case git-rebase.sh was relying on, since it was still assuming that if we didn't have divergent history we'd have no output. Finally, when "rebase" was rewritten in C a combination of 9a48a615b4 ("builtin rebase: try to fast forward when possible", 2018-09-04), 103148aad8 ("merge-base --fork-point: extract libified function", 2018-09-04) and 92d0d74e8d ("builtin rebase: support `fork-point` option", 2018-09-04) faithfully re-implemented the then-buggy behavior. So let's fix this. It's easy enough, we just stop explicitly excluding --fork-point from the can_fast_forward(...) test we're doing, which as discussed above is faithfully ported over from buggy shellscript-era logic. I'm not bothering to fix this in the legacy rebase mode. As discussed in 9aea5e9286 ("rebase: fix regression in rebase.useBuiltin=false test mode", 2019-02-13) it'll be going away shortly after 2.21.0 lands. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> --- t/t3421-rebase-topology-linear.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh index b847064f91..1754537789 100755 --- a/t/t3421-rebase-topology-linear.sh +++ b/t/t3421-rebase-topology-linear.sh @@ -55,6 +55,21 @@ test_run_rebase success -m test_run_rebase success -i test_have_prereq !REBASE_P || test_run_rebase success -p +test_run_rebase () { + result=$1 + shift + test_expect_$result "rebase $* is no-op if remote upstream is an ancestor" " + reset_rebase && + GIT_TEST_REBASE_USE_BUILTIN=true git rebase $* branch-b branch-e && + test_cmp_rev e HEAD + " +} +test_run_rebase success '' +test_run_rebase success --fork-point +test_run_rebase success -m +test_run_rebase success -i +test_have_prereq !REBASE_P || test_run_rebase success -p + test_run_rebase () { result=$1 shift -- 2.21.0.1020.gf2820cf01a ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [RFC WIP PATCH v8 11/13] rebase: eliminate side-effects from can_fast_forward() 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu ` (16 preceding siblings ...) 2019-05-08 0:12 ` [RFC WIP PATCH v8 10/13] rebase: don't rebase linear topology with --fork-point Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 ` Ævar Arnfjörð Bjarmason 2019-05-17 21:16 ` Johannes Schindelin 2019-05-08 0:12 ` [RFC WIP PATCH v8 12/13] rebase: add a should_fast_forward() utility function Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 13/13] WIP: can_fast_forward() support for --preserve-merges and --rebase-merges Ævar Arnfjörð Bjarmason 19 siblings, 1 reply; 123+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 UTC (permalink / raw) To: git Cc: Junio C Hamano, Denton Liu, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason The can_fast_forward() function is potentially much more expensive than is_interactive() since it e.g. might need to call is_linear_history(). So reversing the two looks like an obvious improvement, but doing so reveals a previously hidden caveat: We need the can_fast_forward() function to populate data used later, namely the "merge_bases" variable. This has been the case since it was added in 9a48a615b4 ("builtin rebase: try to fast forward when possible", 2018-09-04). So let's refactor it into two functions. Now we'll always call populate_merge_bases(), and then only call can_fast_forward() if is_interactive() is false, making this both faster in pathological cases, and more importantly easier to follow. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> --- builtin/rebase.c | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index ae6b9b42b8..cb5d7fcb53 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -878,24 +878,30 @@ static int is_linear_history(struct commit *from, struct commit *to) return 1; } -static int can_fast_forward(struct commit *onto, struct commit *upstream, +static void populate_merge_bases(struct commit *head, struct commit *onto, + struct commit_list *merge_bases, + struct object_id *merge_base) +{ + merge_bases = get_merge_bases(onto, head); + if (!merge_bases || merge_bases->next) { + oidcpy(merge_base, &null_oid); + return; + } + oidcpy(merge_base, &merge_bases->item->object.oid); +} + +static int can_fast_forward(struct commit *head, + struct commit *onto, struct commit *upstream, struct commit *restrict_revision, - struct object_id *head_oid, struct object_id *merge_base) + struct object_id *head_oid, + struct commit_list *merge_bases, + struct object_id *merge_base) { - struct commit *head = lookup_commit(the_repository, head_oid); - struct commit_list *merge_bases = NULL; int res = 0; if (!head) goto done; - merge_bases = get_merge_bases(onto, head); - if (!merge_bases || merge_bases->next) { - oidcpy(merge_base, &null_oid); - goto done; - } - - oidcpy(merge_base, &merge_bases->item->object.oid); if (!oideq(merge_base, &onto->object.oid)) goto done; @@ -1154,6 +1160,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_END(), }; int i; + struct commit *head_commit; + struct commit_list *merge_bases = NULL; if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_rebase_usage, @@ -1703,9 +1711,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) * with new commits recreated by replaying their changes. This * optimization must not be done if this is an interactive rebase. */ - if (can_fast_forward(options.onto, options.upstream, options.restrict_revision, - &options.orig_head, &merge_base) && - !is_interactive(&options)) { + head_commit = lookup_commit(the_repository, &options.orig_head); + if (head_commit) + populate_merge_bases(head_commit, options.onto, merge_bases, + &merge_base); + if (!is_interactive(&options) && + can_fast_forward(head_commit, options.onto, options.upstream, + options.restrict_revision, &options.orig_head, + merge_bases, &merge_base)) { int flag; if (!(options.flags & REBASE_FORCE)) { -- 2.21.0.1020.gf2820cf01a ^ permalink raw reply related [flat|nested] 123+ messages in thread
* Re: [RFC WIP PATCH v8 11/13] rebase: eliminate side-effects from can_fast_forward() 2019-05-08 0:12 ` [RFC WIP PATCH v8 11/13] rebase: eliminate side-effects from can_fast_forward() Ævar Arnfjörð Bjarmason @ 2019-05-17 21:16 ` Johannes Schindelin 0 siblings, 0 replies; 123+ messages in thread From: Johannes Schindelin @ 2019-05-17 21:16 UTC (permalink / raw) To: Ævar Arnfjörð Bjarmason Cc: git, Junio C Hamano, Denton Liu, Eric Sunshine, Johannes Sixt, SZEDER Gábor [-- Attachment #1: Type: text/plain, Size: 4622 bytes --] Hi Ævar, On Wed, 8 May 2019, Ævar Arnfjörð Bjarmason wrote: > The can_fast_forward() function is potentially much more expensive > than is_interactive() since it e.g. might need to call > is_linear_history(). > > So reversing the two looks like an obvious improvement, but doing so > reveals a previously hidden caveat: We need the can_fast_forward() > function to populate data used later, namely the "merge_bases" > variable. This has been the case since it was added in > 9a48a615b4 ("builtin rebase: try to fast forward when possible", > 2018-09-04). > > So let's refactor it into two functions. Now we'll always call > populate_merge_bases(), and then only call can_fast_forward() if > is_interactive() is false, making this both faster in pathological > cases, and more importantly easier to follow. True. We might want to mention, though, what exactly that this "pathological case" is: rebasing commits onto an unrelated history. > diff --git a/builtin/rebase.c b/builtin/rebase.c > index ae6b9b42b8..cb5d7fcb53 100644 > --- a/builtin/rebase.c > +++ b/builtin/rebase.c > @@ -878,24 +878,30 @@ static int is_linear_history(struct commit *from, struct commit *to) > return 1; > } > > -static int can_fast_forward(struct commit *onto, struct commit *upstream, > +static void populate_merge_bases(struct commit *head, struct commit *onto, > + struct commit_list *merge_bases, > + struct object_id *merge_base) > +{ > + merge_bases = get_merge_bases(onto, head); Hmm. This overrides whatever was passed in via the parameter. Did you mean to make the parameter of type `struct commit_list **`, i.e. a *pointer to a pointer*? > + if (!merge_bases || merge_bases->next) { > + oidcpy(merge_base, &null_oid); > + return; > + } > + oidcpy(merge_base, &merge_bases->item->object.oid); > +} > + > +static int can_fast_forward(struct commit *head, > + struct commit *onto, struct commit *upstream, > struct commit *restrict_revision, > - struct object_id *head_oid, struct object_id *merge_base) > + struct object_id *head_oid, > + struct commit_list *merge_bases, > + struct object_id *merge_base) > { > - struct commit *head = lookup_commit(the_repository, head_oid); > - struct commit_list *merge_bases = NULL; > int res = 0; > > if (!head) > goto done; > > - merge_bases = get_merge_bases(onto, head); > - if (!merge_bases || merge_bases->next) { > - oidcpy(merge_base, &null_oid); > - goto done; > - } > - > - oidcpy(merge_base, &merge_bases->item->object.oid); Do we even use `merge_bases` after this anymore? I guess not. So we should get rid of this function parameter at this point. > if (!oideq(merge_base, &onto->object.oid)) Uh oh. With this patch, `merge_base` can be `NULL` at this point, namely when trying to rebase commits onto an unrelated history. > goto done; > > @@ -1154,6 +1160,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > OPT_END(), > }; > int i; > + struct commit *head_commit; > + struct commit_list *merge_bases = NULL; > > if (argc == 2 && !strcmp(argv[1], "-h")) > usage_with_options(builtin_rebase_usage, > @@ -1703,9 +1711,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) > * with new commits recreated by replaying their changes. This > * optimization must not be done if this is an interactive rebase. > */ > - if (can_fast_forward(options.onto, options.upstream, options.restrict_revision, > - &options.orig_head, &merge_base) && > - !is_interactive(&options)) { > + head_commit = lookup_commit(the_repository, &options.orig_head); Hmm. I do not see this line (or any equivalent code) removed in this patch. Is it possible that we do not need to (re-?)initialize `head_commit` at this point? I guess I should read the entire patch series because that `head_commit` isn't there in the worktree I am looking at (oh, if only I had an easy way to review code, but I have to review static patches instead, not even in a proper IDE... ;-)). Will try to find time to do that next week. Ciao, Dscho > + if (head_commit) > + populate_merge_bases(head_commit, options.onto, merge_bases, > + &merge_base); > + if (!is_interactive(&options) && > + can_fast_forward(head_commit, options.onto, options.upstream, > + options.restrict_revision, &options.orig_head, > + merge_bases, &merge_base)) { > int flag; > > if (!(options.flags & REBASE_FORCE)) { > -- > 2.21.0.1020.gf2820cf01a > > ^ permalink raw reply [flat|nested] 123+ messages in thread
* [RFC WIP PATCH v8 12/13] rebase: add a should_fast_forward() utility function 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu ` (17 preceding siblings ...) 2019-05-08 0:12 ` [RFC WIP PATCH v8 11/13] rebase: eliminate side-effects from can_fast_forward() Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 ` Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 13/13] WIP: can_fast_forward() support for --preserve-merges and --rebase-merges Ævar Arnfjörð Bjarmason 19 siblings, 0 replies; 123+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 UTC (permalink / raw) To: git Cc: Junio C Hamano, Denton Liu, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason The is_interactive() test gets tricky around --rebase-merges. Let's split our use of it for the purposes of whether we should try fast-forwarding a rebase into a utility function to prepare for adding more logic to that specific codepath. Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> --- builtin/rebase.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index cb5d7fcb53..167d4fcf67 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -890,6 +890,11 @@ static void populate_merge_bases(struct commit *head, struct commit *onto, oidcpy(merge_base, &merge_bases->item->object.oid); } +static int should_fast_forward(struct rebase_options *opts) +{ + return !is_interactive(opts); +} + static int can_fast_forward(struct commit *head, struct commit *onto, struct commit *upstream, struct commit *restrict_revision, @@ -1715,7 +1720,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (head_commit) populate_merge_bases(head_commit, options.onto, merge_bases, &merge_base); - if (!is_interactive(&options) && + if (should_fast_forward(&options) && can_fast_forward(head_commit, options.onto, options.upstream, options.restrict_revision, &options.orig_head, merge_bases, &merge_base)) { -- 2.21.0.1020.gf2820cf01a ^ permalink raw reply related [flat|nested] 123+ messages in thread
* [RFC WIP PATCH v8 13/13] WIP: can_fast_forward() support for --preserve-merges and --rebase-merges 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu ` (18 preceding siblings ...) 2019-05-08 0:12 ` [RFC WIP PATCH v8 12/13] rebase: add a should_fast_forward() utility function Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 ` Ævar Arnfjörð Bjarmason 2019-05-17 22:02 ` Johannes Schindelin 19 siblings, 1 reply; 123+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2019-05-08 0:12 UTC (permalink / raw) To: git Cc: Junio C Hamano, Denton Liu, Eric Sunshine, Johannes Schindelin, Johannes Sixt, SZEDER Gábor, Ævar Arnfjörð Bjarmason This seems to work, needs more tests etc... Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> --- builtin/rebase.c | 6 ++++++ t/t3432-rebase-fast-forward.sh | 7 +++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/builtin/rebase.c b/builtin/rebase.c index 167d4fcf67..de1c5cacb8 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -892,6 +892,12 @@ static void populate_merge_bases(struct commit *head, struct commit *onto, static int should_fast_forward(struct rebase_options *opts) { + if (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) { + if (opts->rebase_merges) + return 1; + if (opts->type == REBASE_PRESERVE_MERGES) + return 1; + } return !is_interactive(opts); } diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh index e8a9bf42b6..d3e1815057 100755 --- a/t/t3432-rebase-fast-forward.sh +++ b/t/t3432-rebase-fast-forward.sh @@ -44,12 +44,13 @@ 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' && - git rebase$flag $* >stdout && + git rebase$flag $* >stdout 2>stderr && 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 + test_i18ngrep 'rewinding head' stdout || + test_i18ngrep 'is up to date, rebase forced' stdout elif test $what = noop then test_i18ngrep 'is up to date' stdout && @@ -79,6 +80,8 @@ test_rebase_same_head success noop same success noop-force same --keep-base mast 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 noop-force same --preserve-merges +test_rebase_same_head success noop same success noop-force same --rebase-merges 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 -- 2.21.0.1020.gf2820cf01a ^ permalink raw reply related [flat|nested] 123+ messages in thread
* Re: [RFC WIP PATCH v8 13/13] WIP: can_fast_forward() support for --preserve-merges and --rebase-merges 2019-05-08 0:12 ` [RFC WIP PATCH v8 13/13] WIP: can_fast_forward() support for --preserve-merges and --rebase-merges Ævar Arnfjörð Bjarmason @ 2019-05-17 22:02 ` Johannes Schindelin 0 siblings, 0 replies; 123+ messages in thread From: Johannes Schindelin @ 2019-05-17 22:02 UTC (permalink / raw) To: Ævar Arnfjörð Bjarmason Cc: git, Junio C Hamano, Denton Liu, Eric Sunshine, Johannes Sixt, SZEDER Gábor [-- Attachment #1: Type: text/plain, Size: 6434 bytes --] Hi Ævar, On Wed, 8 May 2019, Ævar Arnfjörð Bjarmason wrote: > This seems to work, needs more tests etc... I can see how it works, but it is a bit limited, and at the same time overzealous. The reason why we do not enter the fast-forwarding block in the interactive case would appear to me to be that the interactive rebase *might* want to avoid fast-forwarding e.g. with --force-rebase. However, that is not the only instance where we must not simply fast-forward: Think `--exec`. There might be others, too. (I just saw that `--signoff` sets `FORCE_REBASE`, but `--exec` does not, so you cannot even use the `FORCE_REBASE` flag as indicator.) Since `git rebase -rx "make -j8"` is something I use myself, this here patch would therefore break *my* workflow. This is the "overzealous" part. Now for the "limited" part: Let's take a step back and ask how the interactive rebase handles these potentially fast-forwarding cases? Via `skip_unnecessary_picks()`. And that function is ill-prepared for rebasing merges (I specifically do *not* think about `--preserve-merges` at this point, for all I care, it is already deprecated *and* dropped). Even if this function *was* well-prepared for rebasing merges, I think that would miss the mark. Take this todo list, for example: label onto # Branch dscho reset onto pick a123 first label dscho # Branch avar reset onto pick b789 second label avar reset onto merge -C c124 dscho merge -C d314 avar Two branches, both one patch deep, both merged, one after the other. Now, if you insert `pick abc zeroth` before the first `pick`, obviously the first branch would no longer be skippable, but the second one totally would be! This is the "limited" part. To remedy this, I think what we would need is code in `pick_commits()`, right where `TODO_RESET` is handled (or more toward the beginning of that function), that would: - parse the argument (this is currently done in `do_reset()` and would have to be refactored out) and pretend that it is `HEAD`, - look at the following command: if it is - a `pick`, and if its parent agrees with `HEAD`, pretend that the `pick` was actually a `reset`, update the pretended `HEAD` and keep looking at the next command, - a `merge`, and if its option was `-C <orig-merge>` (not lower-case `-c`!), and if its parent agrees with `HEAD`, and if its merge head(s) agree with the original merge commit's (if any), pretend that it was actually a `reset <orig-merge>`, update the pretended `HEAD` and keep looking at the next command, - a `label`, perform it, but with the pretended `HEAD`, and keep looking for the next command, - a `reset`, update the `done` and `git-rebase-todo` files and start the entire spiel from the top, - otherwise perform the reset. - all while skipping, this code would need to take care of updating the `done` and `git-rebase-todo` files, - if a `reset` is necessary, and if it fails, the `done` and `git-rebase-todo` files should *not* be updated, but the original `reset` should be re-scheduled, and - since this adds quite a bit of code, it should probably be done in a separate function. Instead of marking this as a left-over bit (which I would either forget, or whose status would be hard to track), I decided to open a ticket: https://github.com/gitgitgadget/git/issues/209 (I opened GitGitGadget's issues for exactly this kind of use case, because I recently tried to find some useful left-over bits as easy project starters, and even *I* found it super hard to find those, let alone figure out whether they are being/have been addressed already, a mailing list is just not a good bug tracker, even if it is still better than trying to report a bug on Twitter, where I could not even have written this paragraph in a single Tweet.) Ciao, Dscho > Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> > --- > builtin/rebase.c | 6 ++++++ > t/t3432-rebase-fast-forward.sh | 7 +++++-- > 2 files changed, 11 insertions(+), 2 deletions(-) > > diff --git a/builtin/rebase.c b/builtin/rebase.c > index 167d4fcf67..de1c5cacb8 100644 > --- a/builtin/rebase.c > +++ b/builtin/rebase.c > @@ -892,6 +892,12 @@ static void populate_merge_bases(struct commit *head, struct commit *onto, > > static int should_fast_forward(struct rebase_options *opts) > { > + if (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) { > + if (opts->rebase_merges) > + return 1; > + if (opts->type == REBASE_PRESERVE_MERGES) > + return 1; > + } > return !is_interactive(opts); > } > > diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh > index e8a9bf42b6..d3e1815057 100755 > --- a/t/t3432-rebase-fast-forward.sh > +++ b/t/t3432-rebase-fast-forward.sh > @@ -44,12 +44,13 @@ 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' && > - git rebase$flag $* >stdout && > + git rebase$flag $* >stdout 2>stderr && > 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 > + test_i18ngrep 'rewinding head' stdout || > + test_i18ngrep 'is up to date, rebase forced' stdout > elif test $what = noop > then > test_i18ngrep 'is up to date' stdout && > @@ -79,6 +80,8 @@ test_rebase_same_head success noop same success noop-force same --keep-base mast > 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 noop-force same --preserve-merges > +test_rebase_same_head success noop same success noop-force same --rebase-merges > 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 > -- > 2.21.0.1020.gf2820cf01a > > ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH v3 0/4] rebase: teach rebase --keep-base 2019-04-01 20:51 ` [PATCH v3 0/4] " Denton Liu ` (4 preceding siblings ...) 2019-04-05 21:39 ` [PATCH v4 0/4] " Denton Liu @ 2019-04-06 19:44 ` Ævar Arnfjörð Bjarmason 2019-04-06 20:38 ` Denton Liu 5 siblings, 1 reply; 123+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2019-04-06 19:44 UTC (permalink / raw) To: Denton Liu Cc: Git Mailing List, Eric Sunshine, Junio C Hamano, Johannes Schindelin On Mon, Apr 01 2019, Denton Liu wrote: > Thanks again for your feedback, Ævar! I think we're both on the same page now. > Hopefully I've addressed all of your high-level concerns with this patchset and > we can move into a discussion on implementation detail. Late in replying to this, have been off-list. This also applies for your v4. The current version you have still doesn't explain the "Why would we redundantly rebase every time in this case..." question I had in https://public-inbox.org/git/87tvfma8yt.fsf@evledraar.gmail.com/ I *think* it's closer to "it was easier to implement this in terms of --onto, which happens to behave that way now" than "it must work this way for --keep-base", which is fair enough. Although I see when I forward-port my POC patch from that E-Mail that one test fails now, which is good, that wasn't the case before, but it looks like that might be testing something else than just the lazy behavior. It would be good to know in terms of commit message or (better) explicit tests so that if we teach these various rebase modes the same lazyness --fork-point uses in the future it's clear if that's OK or not. ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH v3 0/4] rebase: teach rebase --keep-base 2019-04-06 19:44 ` [PATCH v3 0/4] rebase: teach rebase --keep-base Ævar Arnfjörð Bjarmason @ 2019-04-06 20:38 ` Denton Liu 2019-04-13 21:10 ` Ævar Arnfjörð Bjarmason 0 siblings, 1 reply; 123+ messages in thread From: Denton Liu @ 2019-04-06 20:38 UTC (permalink / raw) To: Ævar Arnfjörð Bjarmason Cc: Git Mailing List, Eric Sunshine, Junio C Hamano, Johannes Schindelin On Sat, Apr 06, 2019 at 09:44:49PM +0200, Ævar Arnfjörð Bjarmason wrote: > > On Mon, Apr 01 2019, Denton Liu wrote: > > > Thanks again for your feedback, Ævar! I think we're both on the same page now. > > Hopefully I've addressed all of your high-level concerns with this patchset and > > we can move into a discussion on implementation detail. > > Late in replying to this, have been off-list. This also applies for your > v4. > > The current version you have still doesn't explain the "Why would we > redundantly rebase every time in this case..." question I had in > https://public-inbox.org/git/87tvfma8yt.fsf@evledraar.gmail.com/ > > I *think* it's closer to "it was easier to implement this in terms of > --onto, which happens to behave that way now" than "it must work this > way for --keep-base", which is fair enough. Correct, the reason why --keep-base was not lazy initially was because "--onto did it that way". You are correct in that --keep-base should be lazily rebasing so I changed --onto's behaviour in 3/4 because it would also benefit from laziness. Thus, now --keep-base lazily rebases because --onto also does. > > Although I see when I forward-port my POC patch from that E-Mail that > one test fails now, which is good, that wasn't the case before, but it > looks like that might be testing something else than just the lazy > behavior. The test fails because the patch disables fork_point if --keep-base is set. So, with the patch applied, C is rebased even though it is excluded when fork_point is set. > > It would be good to know in terms of commit message or (better) explicit > tests so that if we teach these various rebase modes the same lazyness > --fork-point uses in the future it's clear if that's OK or not. Sorry, could you please clarify what you mean by the "lazyness --fork-point uses"? I don't understand what laziness is introduced by using --fork-point. Also, are the tests in t3432 not sufficient for testing fast-forwarding (aka lazy) behaviour? Thanks, Denton ^ permalink raw reply [flat|nested] 123+ messages in thread
* Re: [PATCH v3 0/4] rebase: teach rebase --keep-base 2019-04-06 20:38 ` Denton Liu @ 2019-04-13 21:10 ` Ævar Arnfjörð Bjarmason 0 siblings, 0 replies; 123+ messages in thread From: Ævar Arnfjörð Bjarmason @ 2019-04-13 21:10 UTC (permalink / raw) To: Denton Liu Cc: Git Mailing List, Eric Sunshine, Junio C Hamano, Johannes Schindelin On Sat, Apr 06 2019, Denton Liu wrote: > On Sat, Apr 06, 2019 at 09:44:49PM +0200, Ævar Arnfjörð Bjarmason wrote: >> >> On Mon, Apr 01 2019, Denton Liu wrote: >> >> > Thanks again for your feedback, Ævar! I think we're both on the same page now. >> > Hopefully I've addressed all of your high-level concerns with this patchset and >> > we can move into a discussion on implementation detail. >> >> Late in replying to this, have been off-list. This also applies for your >> v4. >> >> The current version you have still doesn't explain the "Why would we >> redundantly rebase every time in this case..." question I had in >> https://public-inbox.org/git/87tvfma8yt.fsf@evledraar.gmail.com/ >> >> I *think* it's closer to "it was easier to implement this in terms of >> --onto, which happens to behave that way now" than "it must work this >> way for --keep-base", which is fair enough. > > Correct, the reason why --keep-base was not lazy initially was because > "--onto did it that way". You are correct in that --keep-base should be > lazily rebasing so I changed --onto's behaviour in 3/4 because it would > also benefit from laziness. Thus, now --keep-base lazily rebases because > --onto also does. > >> >> Although I see when I forward-port my POC patch from that E-Mail that >> one test fails now, which is good, that wasn't the case before, but it >> looks like that might be testing something else than just the lazy >> behavior. > > The test fails because the patch disables fork_point if --keep-base is > set. So, with the patch applied, C is rebased even though it is excluded > when fork_point is set. > >> >> It would be good to know in terms of commit message or (better) explicit >> tests so that if we teach these various rebase modes the same lazyness >> --fork-point uses in the future it's clear if that's OK or not. > > Sorry, could you please clarify what you mean by the "lazyness > --fork-point uses"? I don't understand what laziness is introduced by > using --fork-point. Also, are the tests in t3432 not sufficient for > testing fast-forwarding (aka lazy) behaviour? Late reply, sorry. I mean if I do e.g. on git.git: git checkout -b avar/test 041f5ea1cf I.e. branch of Junio's "The third batch" (we're now on the 4th) and then: git branch --set-upstream-to origin/master And commit a dummy change, I'm now: On branch avar/test Your branch and 'origin/master' have diverged, and have 1 and 33 different commits each, respectively. Then I run a --keep-base rebase twice: $ git log --oneline --no-decorate -2; ~/g/git/git --exec-path=$PWD rebase --keep-base; git rev-parse HEAD fc3e916c5f foo 041f5ea1cf The third batch First, rewinding head to replay your work on top of it... Applying: foo b10e672074dfee6b6e8b2901c9bb49f856a13708 $ git log --oneline --no-decorate -2; ~/g/git/git --exec-path=$PWD rebase --keep-base; git rev-parse HEAD b10e672074 foo 041f5ea1cf The third batch First, rewinding head to replay your work on top of it... Applying: foo fd3029e73b89f5a799653ff17199d00f2a6ee2e2 I.e. I'll keep on getting a new commit, even though by any criteria I can think of for this type of case there's no work to do. I.e. we're already based on 041f5ea1cf, no need to rebase again. Of course this is also currently the case with --fork-point without argument, which'll settle on (note fourth, not third): $ git log --oneline --no-decorate -2; ~/g/git/git --exec-path=$PWD rebase --fork-point; git rev-parse HEAD 85a1861cec foo e35b8cb8e2 The fourth batch First, rewinding head to replay your work on top of it... Applying: foo c6046e97af29d71bf6270080acf188c095e0cb7c $ git log --oneline --no-decorate -2; ~/g/git/git --exec-path=$PWD rebase --fork-point; git rev-parse HEAD c6046e97af foo e35b8cb8e2 The fourth batch First, rewinding head to replay your work on top of it... Applying: foo 672d22d58e9aa9b6a28054531c21e1f1b598b013 Whereas a --keep-base in *this* case should be identical to: $ git log --oneline --no-decorate -2; ~/g/git/git --exec-path=$PWD rebase $(git merge-base --fork-point origin/master HEAD); git rev-parse HEAD fc3e916c5f foo 041f5ea1cf The third batch Current branch avar/test is up to date. fc3e916c5fc707cfc83f28b3faca81046306b305 The reason I'm asking is that as noted in the thread starting at https://public-inbox.org/git/20190224101029.GA13438@sigill.intra.peff.net/ we used to in *some* cases do the same for: rebase $(git merge-base --fork-point origin/master HEAD) As for just: rebase --fork-point I.e. say "Current branch is up-to-date". I'm planning to loop back to that and fix it for the --fork-point case. So I was wondering if you could think of a reason for why --keep-base couldn't also do the same, or if there was some intrinsic difference I'm missing. Anyway. Don't worry about it. When I poke at it it'll likely shake out of the respective tests. I was just wondering if you having looked at many of the same things recently could understand my ramblings and knew if this was a case of "yup, we could add a bit more to that can_fast_forward(...) case and make it lazy too", or "no, --keep-base is special for reasons you're missing...". ^ permalink raw reply [flat|nested] 123+ messages in thread
end of thread, other threads:[~2019-08-27 8:11 UTC | newest] Thread overview: 123+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2019-03-23 15:25 [PATCH 0/3] rebase: learn --keep-base Denton Liu 2019-03-23 15:25 ` [PATCH 1/3] rebase: teach rebase --keep-base Denton Liu 2019-03-24 3:46 ` Eric Sunshine 2019-03-24 13:20 ` Junio C Hamano 2019-03-25 0:06 ` Denton Liu 2019-03-25 5:41 ` Denton Liu 2019-04-01 10:45 ` Junio C Hamano 2019-03-24 13:37 ` Junio C Hamano 2019-03-25 5:47 ` Denton Liu 2019-03-25 18:50 ` Johannes Schindelin 2019-03-25 19:29 ` Denton Liu 2019-03-26 13:27 ` Johannes Schindelin 2019-03-23 15:25 ` [PATCH 2/3] t3416: test " Denton Liu 2019-03-23 15:25 ` [PATCH 3/3] git-rebase.txt: document --keep-base option Denton Liu 2019-03-24 13:15 ` [PATCH 0/3] rebase: learn --keep-base Junio C Hamano 2019-03-25 0:04 ` Denton Liu 2019-04-01 10:45 ` Junio C Hamano 2019-03-26 14:35 ` Ævar Arnfjörð Bjarmason 2019-03-26 17:50 ` Denton Liu 2019-03-26 20:35 ` Ævar Arnfjörð Bjarmason 2019-03-26 21:30 ` Denton Liu 2019-03-27 15:39 ` Ævar Arnfjörð Bjarmason 2019-03-28 22:17 ` [PATCH v2] rebase: teach rebase --keep-base Denton Liu 2019-03-28 23:13 ` Ævar Arnfjörð Bjarmason 2019-03-29 15:47 ` Johannes Schindelin 2019-03-29 17:52 ` Denton Liu 2019-04-01 10:46 ` Junio C Hamano 2019-04-01 20:51 ` [PATCH v3 0/4] " Denton Liu 2019-04-01 20:51 ` [PATCH v3 1/4] t3431: add rebase --fork-point tests Denton Liu 2019-04-04 20:28 ` Denton Liu 2019-04-05 11:15 ` SZEDER Gábor 2019-04-08 4:38 ` Junio C Hamano 2019-04-05 14:55 ` Johannes Schindelin 2019-04-05 17:25 ` Denton Liu 2019-04-05 17:51 ` Johannes Sixt 2019-04-05 18:51 ` Johannes Schindelin 2019-04-05 20:19 ` Johannes Schindelin 2019-04-05 21:10 ` SZEDER Gábor 2019-04-01 20:51 ` [PATCH v3 2/4] t3432: test rebase fast-forward behavior Denton Liu 2019-04-01 20:52 ` [PATCH v3 3/4] rebase: fast-forward --onto in more cases Denton Liu 2019-04-02 1:25 ` Junio C Hamano 2019-04-02 1:48 ` Junio C Hamano 2019-04-02 4:44 ` Denton Liu 2019-04-01 20:52 ` [PATCH v3 4/4] rebase: teach rebase --keep-base Denton Liu 2019-04-05 21:39 ` [PATCH v4 0/4] " Denton Liu 2019-04-05 21:40 ` [PATCH v4 1/4] t3431: add rebase --fork-point tests Denton Liu 2019-04-05 21:40 ` [PATCH v4 2/4] t3432: test rebase fast-forward behavior Denton Liu 2019-04-05 21:40 ` [PATCH v4 3/4] rebase: fast-forward --onto in more cases Denton Liu 2019-04-05 21:40 ` [PATCH v4 4/4] rebase: teach rebase --keep-base Denton Liu 2019-04-15 22:29 ` [PATCH v5 0/5] " Denton Liu 2019-04-15 22:29 ` [PATCH v5 1/5] t3431: add rebase --fork-point tests Denton Liu 2019-04-15 22:29 ` [PATCH v5 2/5] t3432: test rebase fast-forward behavior Denton Liu 2019-04-15 22:29 ` [PATCH v5 3/5] rebase: fast-forward --onto in more cases Denton Liu 2019-04-16 6:26 ` Junio C Hamano 2019-04-16 13:59 ` Phillip Wood 2019-04-17 6:44 ` Denton Liu 2019-04-17 14:14 ` Phillip Wood 2019-04-19 17:08 ` Denton Liu 2019-04-15 22:29 ` [PATCH v5 4/5] rebase: fast-forward --fork-point " Denton Liu 2019-04-15 22:29 ` [PATCH v5 5/5] rebase: teach rebase --keep-base Denton Liu 2019-04-17 18:01 ` [PATCH v6 0/5] " Denton Liu 2019-04-17 18:01 ` [PATCH v6 1/6] t3431: add rebase --fork-point tests Denton Liu 2019-04-17 18:01 ` [PATCH v6 2/6] t3432: test rebase fast-forward behavior Denton Liu 2019-04-17 18:01 ` [PATCH v6 3/6] rebase: refactor can_fast_forward into goto tower Denton Liu 2019-04-17 18:01 ` [PATCH v6 4/6] rebase: fast-forward --onto in more cases Denton Liu 2019-04-17 19:59 ` Phillip Wood 2019-04-17 18:01 ` [PATCH v6 5/6] rebase: fast-forward --fork-point " Denton Liu 2019-04-17 18:01 ` [PATCH v6 6/6] rebase: teach rebase --keep-base Denton Liu 2019-04-21 8:11 ` [PATCH v7 0/6] rebase: learn --keep-base Denton Liu 2019-04-21 8:11 ` [PATCH v7 1/6] t3431: add rebase --fork-point tests Denton Liu 2019-04-23 23:12 ` Denton Liu 2019-04-21 8:11 ` [PATCH v7 2/6] t3432: test rebase fast-forward behavior Denton Liu 2019-04-21 8:11 ` [PATCH v7 3/6] rebase: refactor can_fast_forward into goto tower Denton Liu 2019-04-21 8:11 ` [PATCH v7 4/6] rebase: fast-forward --onto in more cases Denton Liu 2019-04-21 8:11 ` [PATCH v7 5/6] rebase: fast-forward --fork-point " Denton Liu 2019-04-21 8:11 ` [PATCH v7 6/6] rebase: teach rebase --keep-base Denton Liu 2019-05-08 0:12 ` [RFC WIP PATCH v8 00/13] learn --keep-base & more Ævar Arnfjörð Bjarmason 2019-05-08 3:57 ` Junio C Hamano 2019-07-19 19:14 ` Junio C Hamano 2019-07-19 21:01 ` Denton Liu 2019-08-25 9:11 ` [PATCH v9 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour Denton Liu 2019-08-25 9:11 ` [PATCH v9 1/9] t3431: add rebase --fork-point tests Denton Liu 2019-08-25 9:12 ` [PATCH v9 2/9] t3432: test rebase fast-forward behavior Denton Liu 2019-08-25 9:12 ` [PATCH v9 3/9] t3432: distinguish "noop-same" v.s. "work-same" in "same head" tests Denton Liu 2019-08-25 9:12 ` [PATCH v9 4/9] t3432: test for --no-ff's interaction with fast-forward Denton Liu 2019-08-25 9:12 ` [PATCH v9 5/9] rebase: refactor can_fast_forward into goto tower Denton Liu 2019-08-25 13:17 ` Pratyush Yadav 2019-08-26 23:17 ` Denton Liu 2019-08-25 9:12 ` [PATCH v9 6/9] rebase: fast-forward --onto in more cases Denton Liu 2019-08-25 9:12 ` [PATCH v9 7/9] rebase: fast-forward --fork-point " Denton Liu 2019-08-25 9:12 ` [PATCH v9 8/9] rebase tests: test linear branch topology Denton Liu 2019-08-25 9:12 ` [PATCH v9 9/9] rebase: teach rebase --keep-base Denton Liu 2019-08-25 22:59 ` Philip Oakley 2019-08-26 19:37 ` [PATCH v9 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour Junio C Hamano 2019-08-27 5:37 ` [PATCH v10 " Denton Liu 2019-08-27 5:37 ` [PATCH v10 1/9] t3431: add rebase --fork-point tests Denton Liu 2019-08-27 5:37 ` [PATCH v10 2/9] t3432: test rebase fast-forward behavior Denton Liu 2019-08-27 5:37 ` [PATCH v10 3/9] t3432: distinguish "noop-same" v.s. "work-same" in "same head" tests Denton Liu 2019-08-27 5:37 ` [PATCH v10 4/9] t3432: test for --no-ff's interaction with fast-forward Denton Liu 2019-08-27 8:11 ` SZEDER Gábor 2019-08-27 5:37 ` [PATCH v10 5/9] rebase: refactor can_fast_forward into goto tower Denton Liu 2019-08-27 5:37 ` [PATCH v10 6/9] rebase: fast-forward --onto in more cases Denton Liu 2019-08-27 5:38 ` [PATCH v10 7/9] rebase: fast-forward --fork-point " Denton Liu 2019-08-27 5:38 ` [PATCH v10 8/9] rebase tests: test linear branch topology Denton Liu 2019-08-27 5:38 ` [PATCH v10 9/9] rebase: teach rebase --keep-base Denton Liu 2019-05-08 0:12 ` [RFC WIP PATCH v8 01/13] t3431: add rebase --fork-point tests Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 02/13] t3432: test rebase fast-forward behavior Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 03/13] t3432: distinguish "noop-same" v.s. "work-same" in "same head" tests Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 04/13] t3432: test for --no-ff's interaction with fast-forward Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 05/13] rebase: refactor can_fast_forward into goto tower Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 06/13] rebase: fast-forward --onto in more cases Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 07/13] rebase: fast-forward --fork-point " Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 08/13] rebase: teach rebase --keep-base Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 09/13] rebase tests: test linear branch topology Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 10/13] rebase: don't rebase linear topology with --fork-point Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 11/13] rebase: eliminate side-effects from can_fast_forward() Ævar Arnfjörð Bjarmason 2019-05-17 21:16 ` Johannes Schindelin 2019-05-08 0:12 ` [RFC WIP PATCH v8 12/13] rebase: add a should_fast_forward() utility function Ævar Arnfjörð Bjarmason 2019-05-08 0:12 ` [RFC WIP PATCH v8 13/13] WIP: can_fast_forward() support for --preserve-merges and --rebase-merges Ævar Arnfjörð Bjarmason 2019-05-17 22:02 ` Johannes Schindelin 2019-04-06 19:44 ` [PATCH v3 0/4] rebase: teach rebase --keep-base Ævar Arnfjörð Bjarmason 2019-04-06 20:38 ` Denton Liu 2019-04-13 21:10 ` Ævar Arnfjörð Bjarmason
Code repositories for project(s) associated with this public inbox https://80x24.org/mirrors/git.git This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).