git@vger.kernel.org list mirror (unofficial, one of many)
 help / color / Atom feed
* [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	[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	[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	[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 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 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-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 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 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-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

* 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	[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 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 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 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	[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	[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	[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	[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	[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

* 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-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	[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	[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 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	[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	[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	[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	[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	[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 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 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

* [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	[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	[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	[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	[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	[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

* [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	[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	[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	[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	[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	[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	[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

* 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 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	[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	[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	[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	[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	[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	[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	[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

* [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

* [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	[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	[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	[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	[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	[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	[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	[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	[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	[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	[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	[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	[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	[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 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

* 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: [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	[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	[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	[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	[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	[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	[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	[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	[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	[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 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

* 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 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	[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	[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	[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	[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	[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	[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	[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	[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	[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

end of thread, back to index

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

git@vger.kernel.org list mirror (unofficial, one of many)

Archives are clonable:
	git clone --mirror https://public-inbox.org/git
	git clone --mirror http://ou63pmih66umazou.onion/git
	git clone --mirror http://czquwvybam4bgbro.onion/git
	git clone --mirror http://hjrcffqmbrq6wope.onion/git

Example config snippet for mirrors

Newsgroups are available over NNTP:
	nntp://news.public-inbox.org/inbox.comp.version-control.git
	nntp://ou63pmih66umazou.onion/inbox.comp.version-control.git
	nntp://czquwvybam4bgbro.onion/inbox.comp.version-control.git
	nntp://hjrcffqmbrq6wope.onion/inbox.comp.version-control.git
	nntp://news.gmane.org/gmane.comp.version-control.git

 note: .onion URLs require Tor: https://www.torproject.org/

AGPL code for this site: git clone https://public-inbox.org/ public-inbox