{"thread":{"id":"50810","subject":"[PATCH 0/3] rebase: learn --keep-base","startedAt":"2019-03-23T15:25:30Z","lastAt":"2019-08-27T08:11:12Z","messageCount":123,"participants":["Denton Liu","Eric Sunshine","Junio C Hamano","Johannes Schindelin","Ævar Arnfjörð Bjarmason","SZEDER Gábor","Johannes Sixt","Phillip Wood","Pratyush Yadav","Philip Oakley"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"372306","messageId":"cover.1553354374.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":null,"subject":"[PATCH 0/3] rebase: learn --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-03-23T15:25:22Z","receivedAt":"2019-03-23T15:25:30Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"This series teaches rebase the --keep-base option.\n\n'git rebase --keep-base <upstream>' is equivalent to\n'git rebase --onto <upstream>... <upstream>' or\n'git rebase --onto $(git merge-base <upstream> HEAD) <upstream>' .\n\nThis seems to be a common case that people (including myself!) run into; I was\nable to find these StackOverflow posts about this use case:\n\n* https://stackoverflow.com/questions/53234798/can-i-rebase-on-a-branchs-fork-point-without-explicitly-specifying-the-parent\n* https://stackoverflow.com/questions/41529128/how-do-you-rebase-only-changes-between-two-branches-into-another-branch\n* https://stackoverflow.com/a/4207357\n\n\nDenton Liu (3):\n  rebase: teach rebase --keep-base\n  t3416: test rebase --keep-base\n  git-rebase.txt: document --keep-base option\n\n Documentation/git-rebase.txt     | 12 +++++--\n builtin/rebase.c                 | 25 ++++++++++++--\n t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++++++++\n 3 files changed, 88 insertions(+), 6 deletions(-)\n\n-- \n2.21.0.512.g57bf1b23e1\n\n"},{"id":"372307","messageId":"f802e5442013613221a4efd8ef1fecce0f3a9914.1553354374.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1553354374.git.liu.denton@gmail.com","subject":"[PATCH 1/3] rebase: teach rebase --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-03-23T15:25:28Z","receivedAt":"2019-03-23T15:25:37Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"A common scenario is if a user is working on a topic branch and they\nwish to make some changes to intermediate commits or autosquashing, they\nwould run something such as\n\n\tgit rebase -i --onto master... master\n\nin order to preserve the merge base. This prevents unnecessary commit\nchurning.\n\nAlternatively, a user wishing to test individual commits in a topic\nbranch without changing anything may run\n\n\tgit rebase -x ./test.sh master... master\n\nSince rebasing onto the merge base of the branch and the upstream is\nsuch a common case, introduce the --keep-base option as a shortcut.\n\nThis allows us to rewrite the above as\n\n\tgit rebase -i --keep-base master\n\nand\n\n\tgit rebase -x ./test.sh --keep-base master\n\nrespectively.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n builtin/rebase.c | 25 ++++++++++++++++++++++---\n 1 file changed, 22 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 77deebc65c..fffee89064 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -27,8 +27,8 @@\n #include \"branch.h\"\n \n static char const * const builtin_rebase_usage[] = {\n-\tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n-\t\t\"[<upstream>] [<branch>]\"),\n+\tN_(\"git rebase [-i] [options] [--exec <cmd>] \"\n+\t\t\"[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\"),\n \tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n \t\t\"--root [<branch>]\"),\n \tN_(\"git rebase --continue | --abort | --skip | --edit-todo\"),\n@@ -1018,6 +1018,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t};\n \tconst char *branch_name;\n \tint ret, flags, total_argc, in_progress = 0;\n+\tint keep_base = 0;\n \tint ok_to_skip_pre_rebase = 0;\n \tstruct strbuf msg = STRBUF_INIT;\n \tstruct strbuf revisions = STRBUF_INIT;\n@@ -1051,6 +1052,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"rebase onto given branch instead of upstream\")),\n+\t\tOPT_BOOL(0, \"keep-base\", &keep_base,\n+\t\t\t N_(\"use the merge-base of upstream and branch as the current base\")),\n \t\tOPT_BOOL(0, \"no-verify\", &ok_to_skip_pre_rebase,\n \t\t\t N_(\"allow pre-rebase hook to run\")),\n \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n@@ -1217,6 +1220,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(builtin_rebase_usage,\n \t\t\t\t   builtin_rebase_options);\n \n+\tif (keep_base) {\n+\t\tif (options.onto_name)\n+\t\t\tdie(_(\"cannot combine '--keep-base' with '--onto'\"));\n+\t\tif (options.root)\n+\t\t\tdie(_(\"cannot combine '--keep-base' with '--root'\"));\n+\t}\n+\n \tif (action != NO_ACTION && !in_progress)\n \t\tdie(_(\"No rebase in progress?\"));\n \tsetenv(GIT_REFLOG_ACTION_ENVIRONMENT, \"rebase\", 0);\n@@ -1541,10 +1551,19 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t}\n \n \t/* Make sure the branch to rebase onto is valid. */\n-\tif (!options.onto_name)\n+\tif (keep_base) {\n+\t    strbuf_reset(&buf);\n+\t    strbuf_addstr(&buf, options.upstream_name);\n+\t    strbuf_addstr(&buf, \"...\");\n+\t    options.onto_name = xstrdup(buf.buf);\n+\t} else if (!options.onto_name)\n \t\toptions.onto_name = options.upstream_name;\n \tif (strstr(options.onto_name, \"...\")) {\n \t\tif (get_oid_mb(options.onto_name, &merge_base) < 0)\n+\t\t    if (keep_base)\n+\t\t\tdie(_(\"'%s': need exactly one merge base with branch\"),\n+\t\t\t\toptions.upstream_name);\n+\t\t    else\n \t\t\tdie(_(\"'%s': need exactly one merge base\"),\n \t\t\t    options.onto_name);\n \t\toptions.onto = lookup_commit_or_die(&merge_base,\n-- \n2.21.0.512.g57bf1b23e1\n\n"},{"id":"372308","messageId":"6398944a6f9e696a6f6f7d616447160af93b0639.1553354374.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1553354374.git.liu.denton@gmail.com","subject":"[PATCH 2/3] t3416: test rebase --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-03-23T15:25:36Z","receivedAt":"2019-03-23T15:25:45Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Test rebase --keep-base to ensure it works correctly in the normal case\nand fails when there are multiple merge-bases, both in regular and\ninteractive mode. Also, test to make sure conflicting options causes\nrebase to fail.\n\nWhile we're at it, add a missing set_fake_editor call to\n'rebase -i --onto master...side'.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++++++++\n 1 file changed, 57 insertions(+)\n\ndiff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh\nindex ddf2f64853..9c2548423b 100755\n--- a/t/t3416-rebase-onto-threedots.sh\n+++ b/t/t3416-rebase-onto-threedots.sh\n@@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' '\n \tgit checkout side &&\n \tgit reset --hard K &&\n \n+\tset_fake_editor &&\n \ttest_must_fail git rebase -i --onto master...side J\n '\n \n+test_expect_success 'rebase --keep-base --onto incompatible' '\n+\ttest_must_fail git rebase --keep-base --onto master...\n+'\n+\n+test_expect_success 'rebase --keep-base --root incompatible' '\n+\ttest_must_fail git rebase --keep-base --root\n+'\n+\n+test_expect_success 'rebase --keep-base master from topic' '\n+\tgit reset --hard &&\n+\tgit checkout topic &&\n+\tgit reset --hard G &&\n+\n+\tgit rebase --keep-base master &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base master HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --keep-base master from side' '\n+\tgit reset --hard &&\n+\tgit checkout side &&\n+\tgit reset --hard K &&\n+\n+\ttest_must_fail git rebase --keep-base master\n+'\n+\n+test_expect_success 'rebase -i --keep-base master from topic' '\n+\tgit reset --hard &&\n+\tgit checkout topic &&\n+\tgit reset --hard G &&\n+\n+\tset_fake_editor &&\n+\tEXPECT_COUNT=2 git rebase -i --keep-base master &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base master HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase -i --keep-base master from side' '\n+\tgit reset --hard &&\n+\tgit checkout side &&\n+\tgit reset --hard K &&\n+\n+\tset_fake_editor &&\n+\ttest_must_fail git rebase -i --keep-base master\n+'\n+\n test_done\n-- \n2.21.0.512.g57bf1b23e1\n\n"},{"id":"372309","messageId":"8896a65c2cecfc84c494545e47c918a45cd63602.1553354374.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1553354374.git.liu.denton@gmail.com","subject":"[PATCH 3/3] git-rebase.txt: document --keep-base option","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-03-23T15:25:43Z","receivedAt":"2019-03-23T15:25:52Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Document the --keep-base rebase option.\n\nWhile we're at it, change an instance of \"merge-base\" to \"merge base\",\nwhich is the consistent spelling.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n Documentation/git-rebase.txt | 12 +++++++++---\n 1 file changed, 9 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 6363d674b7..780d381667 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip\n SYNOPSIS\n --------\n [verse]\n-'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n-\t[<upstream> [<branch>]]\n+'git rebase' [-i | --interactive] [<options>] [--exec <cmd>]\n+\t[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\n 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n \t--root [<branch>]\n 'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch\n@@ -217,6 +217,12 @@ As a special case, you may use \"A\\...B\" as a shortcut for the\n merge base of A and B if there is exactly one merge base. You can\n leave out at most one of A and B, in which case it defaults to HEAD.\n \n+--keep-base::\n+\tSet the starting point at which to create the new commits to the\n+\tmerge base of <upstream> <branch>. Running\n+\t'git rebase --keep-base <upstream> <branch>' is equivalent to\n+\trunning 'git rebase --onto <upstream>... <upstream>'.\n+\n <upstream>::\n \tUpstream branch to compare against.  May be any valid commit,\n \tnot just an existing branch name. Defaults to the configured\n@@ -863,7 +869,7 @@ NOTE: While an \"easy case recovery\" sometimes appears to be successful\n       --interactive` will be **resurrected**!\n \n The idea is to manually tell 'git rebase' \"where the old 'subsystem'\n-ended and your 'topic' began\", that is, what the old merge-base\n+ended and your 'topic' began\", that is, what the old merge base\n between them was.  You will have to find a way to name the last commit\n of the old 'subsystem', for example:\n \n-- \n2.21.0.512.g57bf1b23e1\n\n"},{"id":"372321","messageId":"CAPig+cT=jW0w==1ipD4Pem6Y+7orSH5q39j0rBhF87C8AT6Zjg@mail.gmail.com","threadId":"50810","inReplyTo":"f802e5442013613221a4efd8ef1fecce0f3a9914.1553354374.git.liu.denton@gmail.com","subject":"Re: [PATCH 1/3] rebase: teach rebase --keep-base","fromName":"Eric Sunshine","fromEmail":"sunshine@sunshineco.com","sentAt":"2019-03-24T03:46:09Z","receivedAt":"2019-03-24T03:46:21Z","isPatch":true,"sender":{"key":"sunshine@sunshineco.com","avatar":"https://avatars.githubusercontent.com/u/163641?v=4"},"body":"On Sat, Mar 23, 2019 at 11:25 AM Denton Liu <liu.denton@gmail.com> wrote:>\n> [...]\n> Since rebasing onto the merge base of the branch and the upstream is\n> such a common case, introduce the --keep-base option as a shortcut.\n> [...]\n> Signed-off-by: Denton Liu <liu.denton@gmail.com>\n> ---\n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> @@ -1541,10 +1551,19 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>         /* Make sure the branch to rebase onto is valid. */\n> -       if (!options.onto_name)\n> +       if (keep_base) {\n> +           strbuf_reset(&buf);\n> +           strbuf_addstr(&buf, options.upstream_name);\n> +           strbuf_addstr(&buf, \"...\");\n> +           options.onto_name = xstrdup(buf.buf);\n> +       } else if (!options.onto_name)\n>                 options.onto_name = options.upstream_name;\n>         if (strstr(options.onto_name, \"...\")) {\n>                 if (get_oid_mb(options.onto_name, &merge_base) < 0)\n> +                   if (keep_base)\n> +                       die(_(\"'%s': need exactly one merge base with branch\"),\n> +                               options.upstream_name);\n> +                   else\n\nStyle: Indent with tabs, not spaces.\n"},{"id":"372362","messageId":"xmqqy354o1l8.fsf@gitster-ct.c.googlers.com","threadId":"50810","inReplyTo":"cover.1553354374.git.liu.denton@gmail.com","subject":"Re: [PATCH 0/3] rebase: learn --keep-base","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-03-24T13:15:31Z","receivedAt":"2019-03-24T13:15:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n> This series teaches rebase the --keep-base option.\n>\n> 'git rebase --keep-base <upstream>' is equivalent to\n> 'git rebase --onto <upstream>... <upstream>' or\n> 'git rebase --onto $(git merge-base <upstream> HEAD) <upstream>' .\n>\n> This seems to be a common case that people (including myself!) run\n> into; I was able to find these StackOverflow posts about this use\n> case:\n\nSince this is 0/3 I won't complain too loudly, but read the above\nagain while pretending that you didn't know what your initial\nmotivating example was.  The last three lines does not explain\nanything useful to such a reader, and the reader needs to decipher\nthe two sample commands to guess what you wanted to achieve.\n\nBefore \"teaches rebase the --keep-base option\", you must tell what\nyou wanted to do with that new feature to attract readers---convince\nthem your new contribution is worth their time to read over.\n\nIf I understand correctly, what \"--onto $(git merge-base @{u} HEAD) @{u}\"\nlets you express is:\n\n\tno matter how much progress the upstream has made while I\n\twas away from the keyboard, I want to rebase the current\n\ttopic on top of the same commit from the upstream, on which\n\tI based the current iteration of the topic.\n\nI suspect that such a rebase will become no-op without \"-i\".  Am I\nmistaken?  I am not sure if \"--keep-base\" is useful without \"-i\".\n\nBut of course, it would be useful with \"-i\", i.e. when you want to\nfurther polish the topic.  You need to give <upstream> to the command\nto let it know where their work stops and your work begins,\ni.e. letting the command figure out what commits to replay.  But in\nsuch a workflow, you do not want <upstream> to affect on top of what\ncommit the replayed history is built.  And \"keep base\" would be a\nvery direct way to achieve that.\n\n\n\n"},{"id":"372364","messageId":"xmqqtvfso1cz.fsf@gitster-ct.c.googlers.com","threadId":"50810","inReplyTo":"f802e5442013613221a4efd8ef1fecce0f3a9914.1553354374.git.liu.denton@gmail.com","subject":"Re: [PATCH 1/3] rebase: teach rebase --keep-base","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-03-24T13:20:28Z","receivedAt":"2019-03-24T13:20:33Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n> A common scenario is if a user is working on a topic branch and they\n> wish to make some changes to intermediate commits or autosquashing, they\n> would run something such as\n>\n> \tgit rebase -i --onto master... master\n>\n> in order to preserve the merge base. This prevents unnecessary commit\n> churning.\n>\n> Alternatively, a user wishing to test individual commits in a topic\n> branch without changing anything may run\n>\n> \tgit rebase -x ./test.sh master... master\n>\n> Since rebasing onto the merge base of the branch and the upstream is\n> such a common case, introduce the --keep-base option as a shortcut.\n>\n> This allows us to rewrite the above as\n>\n> \tgit rebase -i --keep-base master\n>\n> and\n>\n> \tgit rebase -x ./test.sh --keep-base master\n>\n> respectively.\n\nI never use the \"feature\" myself, but I recall that when \"git\nrebase\" is run on a branch appropriately prepared, you do not even\nhave to say <upstream> (iow, you type \"git rebase<RET>\" and rebase\non top of @{upstream}).  \n\nCan this new \"--keep-base\" feature mesh well with it?  When the\ncurrent branch has forked from origin/master, for example, it would\nbe good if\n\n\t$ git rebase -i --same-base\n\nbecomes a usable short-hand for\n\n\t$ git rebase -i --same-base origin/master\n\naka\n\n\t$ git rebase -i --onto $(git merge-base HEAD origin/master) origin/master\n\n"},{"id":"372365","messageId":"xmqqpnqgo0k2.fsf@gitster-ct.c.googlers.com","threadId":"50810","inReplyTo":"f802e5442013613221a4efd8ef1fecce0f3a9914.1553354374.git.liu.denton@gmail.com","subject":"Re: [PATCH 1/3] rebase: teach rebase --keep-base","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-03-24T13:37:49Z","receivedAt":"2019-03-24T13:37:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n>  \tif (strstr(options.onto_name, \"...\")) {\n>  \t\tif (get_oid_mb(options.onto_name, &merge_base) < 0)\n> +\t\t    if (keep_base)\n> +\t\t\tdie(_(\"'%s': need exactly one merge base with branch\"),\n> +\t\t\t\toptions.upstream_name);\n> +\t\t    else\n>  \t\t\tdie(_(\"'%s': need exactly one merge base\"),\n>  \t\t\t    options.onto_name);\n\nIf you turn a single statement body into if/else, have {} around the\nbody of the outer if, i.e.\n\n\tif (get_oid_mb(...) < 0) {\n\t\tif (keep_base)\n\t\t\tdie(_(\"'%s': need exactly one merge base with branch\"),\n\t\t\t    options.upstream_name);\n\t\telse\n\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n\t\t\t    options.onto_name);\n\t}\n\nOtherwise -Werror=danglng-else will stop compilation.\n\nThanks.\n"},{"id":"372388","messageId":"20190325000427.GA9384@archbookpro.localdomain","threadId":"50810","inReplyTo":"xmqqy354o1l8.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH 0/3] rebase: learn --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-03-25T00:04:27Z","receivedAt":"2019-03-25T00:04:32Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Hi Junio,\n\nOn Sun, Mar 24, 2019 at 10:15:31PM +0900, Junio C Hamano wrote:\n> Denton Liu <liu.denton@gmail.com> writes:\n> \n> > This series teaches rebase the --keep-base option.\n> >\n> > 'git rebase --keep-base <upstream>' is equivalent to\n> > 'git rebase --onto <upstream>... <upstream>' or\n> > 'git rebase --onto $(git merge-base <upstream> HEAD) <upstream>' .\n> >\n> > This seems to be a common case that people (including myself!) run\n> > into; I was able to find these StackOverflow posts about this use\n> > case:\n> \n> Since this is 0/3 I won't complain too loudly, but read the above\n> again while pretending that you didn't know what your initial\n> motivating example was.  The last three lines does not explain\n> anything useful to such a reader, and the reader needs to decipher\n> the two sample commands to guess what you wanted to achieve.\n> \n> Before \"teaches rebase the --keep-base option\", you must tell what\n> you wanted to do with that new feature to attract readers---convince\n> them your new contribution is worth their time to read over.\n> \n> If I understand correctly, what \"--onto $(git merge-base @{u} HEAD) @{u}\"\n> lets you express is:\n> \n> \tno matter how much progress the upstream has made while I\n> \twas away from the keyboard, I want to rebase the current\n> \ttopic on top of the same commit from the upstream, on which\n> \tI based the current iteration of the topic.\n\nThanks for the clarification. I'll try my best to write future cover\nletters more clearly.\n\n> \n> I suspect that such a rebase will become no-op without \"-i\".  Am I\n> mistaken?  I am not sure if \"--keep-base\" is useful without \"-i\".\n\nIt's useful in the case of \"-x\", although that is a grey area since \"-x\"\nuses interactive machinery internally. Aside from \"-x\", I can't really\nthink of a situation where we would use \"--keep-base\" without \"-i\".\n\n> \n> But of course, it would be useful with \"-i\", i.e. when you want to\n> further polish the topic.  You need to give <upstream> to the command\n> to let it know where their work stops and your work begins,\n> i.e. letting the command figure out what commits to replay.  But in\n> such a workflow, you do not want <upstream> to affect on top of what\n> commit the replayed history is built.  And \"keep base\" would be a\n> very direct way to achieve that.\n> \n> \n> \n\nThanks,\n\nDenton\n"},{"id":"372389","messageId":"20190325000618.GB9384@archbookpro.localdomain","threadId":"50810","inReplyTo":"xmqqtvfso1cz.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH 1/3] rebase: teach rebase --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-03-25T00:06:18Z","receivedAt":"2019-03-25T00:06:22Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Hi Junio,\n\nOn Sun, Mar 24, 2019 at 10:20:28PM +0900, Junio C Hamano wrote:\n> Denton Liu <liu.denton@gmail.com> writes:\n> \n> > A common scenario is if a user is working on a topic branch and they\n> > wish to make some changes to intermediate commits or autosquashing, they\n> > would run something such as\n> >\n> > \tgit rebase -i --onto master... master\n> >\n> > in order to preserve the merge base. This prevents unnecessary commit\n> > churning.\n> >\n> > Alternatively, a user wishing to test individual commits in a topic\n> > branch without changing anything may run\n> >\n> > \tgit rebase -x ./test.sh master... master\n> >\n> > Since rebasing onto the merge base of the branch and the upstream is\n> > such a common case, introduce the --keep-base option as a shortcut.\n> >\n> > This allows us to rewrite the above as\n> >\n> > \tgit rebase -i --keep-base master\n> >\n> > and\n> >\n> > \tgit rebase -x ./test.sh --keep-base master\n> >\n> > respectively.\n> \n> I never use the \"feature\" myself, but I recall that when \"git\n> rebase\" is run on a branch appropriately prepared, you do not even\n> have to say <upstream> (iow, you type \"git rebase<RET>\" and rebase\n> on top of @{upstream}).  \n> \n> Can this new \"--keep-base\" feature mesh well with it?  When the\n> current branch has forked from origin/master, for example, it would\n> be good if\n> \n> \t$ git rebase -i --same-base\n> \n> becomes a usable short-hand for\n> \n> \t$ git rebase -i --same-base origin/master\n\nBy \"--same-base\", I am assuming you mistyped and meant to write\n\"--keep-base\"? If that's the case, I can make it a shorthand.\n\nThanks,\n\nDenton\n\n> \n> aka\n> \n> \t$ git rebase -i --onto $(git merge-base HEAD origin/master) origin/master\n> \n"},{"id":"372397","messageId":"20190325054145.GA1652@archbookpro.localdomain","threadId":"50810","inReplyTo":"20190325000618.GB9384@archbookpro.localdomain","subject":"Re: [PATCH 1/3] rebase: teach rebase --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-03-25T05:41:45Z","receivedAt":"2019-03-25T05:41:50Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"On Sun, Mar 24, 2019 at 05:06:18PM -0700, Denton Liu wrote:\n> Hi Junio,\n> \n> On Sun, Mar 24, 2019 at 10:20:28PM +0900, Junio C Hamano wrote:\n> > Denton Liu <liu.denton@gmail.com> writes:\n> > \n> > > A common scenario is if a user is working on a topic branch and they\n> > > wish to make some changes to intermediate commits or autosquashing, they\n> > > would run something such as\n> > >\n> > > \tgit rebase -i --onto master... master\n> > >\n> > > in order to preserve the merge base. This prevents unnecessary commit\n> > > churning.\n> > >\n> > > Alternatively, a user wishing to test individual commits in a topic\n> > > branch without changing anything may run\n> > >\n> > > \tgit rebase -x ./test.sh master... master\n> > >\n> > > Since rebasing onto the merge base of the branch and the upstream is\n> > > such a common case, introduce the --keep-base option as a shortcut.\n> > >\n> > > This allows us to rewrite the above as\n> > >\n> > > \tgit rebase -i --keep-base master\n> > >\n> > > and\n> > >\n> > > \tgit rebase -x ./test.sh --keep-base master\n> > >\n> > > respectively.\n> > \n> > I never use the \"feature\" myself, but I recall that when \"git\n> > rebase\" is run on a branch appropriately prepared, you do not even\n> > have to say <upstream> (iow, you type \"git rebase<RET>\" and rebase\n> > on top of @{upstream}).  \n> > \n> > Can this new \"--keep-base\" feature mesh well with it?  When the\n> > current branch has forked from origin/master, for example, it would\n> > be good if\n> > \n> > \t$ git rebase -i --same-base\n> > \n> > becomes a usable short-hand for\n> > \n> > \t$ git rebase -i --same-base origin/master\n> \n> By \"--same-base\", I am assuming you mistyped and meant to write\n> \"--keep-base\"? If that's the case, I can make it a shorthand.\n\nSorry, I misunderstood your question. \"--keep-base\" already has the\nshorthand case handled by default.\n\nOne caveat is that \"--fork-point\" is automatically implied if no\nupstream is supplied but this behaviour is the same for \"--onto\" or when\nno other options are supplied as well.\n\nI don't use the feature either so I'm not really sure if this behaviour\nwould be the most sane thing to do.\n\n> Thanks,\n> \n> Denton\n> \n> > \n> > aka\n> > \n> > \t$ git rebase -i --onto $(git merge-base HEAD origin/master) origin/master\n> > \n"},{"id":"372398","messageId":"20190325054730.GB1652@archbookpro.localdomain","threadId":"50810","inReplyTo":"f802e5442013613221a4efd8ef1fecce0f3a9914.1553354374.git.liu.denton@gmail.com","subject":"Re: [PATCH 1/3] rebase: teach rebase --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-03-25T05:47:30Z","receivedAt":"2019-03-25T05:47:37Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"On Sat, Mar 23, 2019 at 08:25:28AM -0700, Denton Liu wrote:\n> A common scenario is if a user is working on a topic branch and they\n> wish to make some changes to intermediate commits or autosquashing, they\n\nSorry, small typo here:\n\ns/autosquashing/autosquash/\n\n-Denton\n\n> would run something such as\n> \n> \tgit rebase -i --onto master... master\n> \n> in order to preserve the merge base. This prevents unnecessary commit\n> churning.\n> \n> Alternatively, a user wishing to test individual commits in a topic\n> branch without changing anything may run\n> \n> \tgit rebase -x ./test.sh master... master\n> \n> Since rebasing onto the merge base of the branch and the upstream is\n> such a common case, introduce the --keep-base option as a shortcut.\n> \n> This allows us to rewrite the above as\n> \n> \tgit rebase -i --keep-base master\n> \n> and\n> \n> \tgit rebase -x ./test.sh --keep-base master\n> \n> respectively.\n> \n> Signed-off-by: Denton Liu <liu.denton@gmail.com>\n> ---\n>  builtin/rebase.c | 25 ++++++++++++++++++++++---\n>  1 file changed, 22 insertions(+), 3 deletions(-)\n> \n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index 77deebc65c..fffee89064 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -27,8 +27,8 @@\n>  #include \"branch.h\"\n>  \n>  static char const * const builtin_rebase_usage[] = {\n> -\tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n> -\t\t\"[<upstream>] [<branch>]\"),\n> +\tN_(\"git rebase [-i] [options] [--exec <cmd>] \"\n> +\t\t\"[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\"),\n>  \tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n>  \t\t\"--root [<branch>]\"),\n>  \tN_(\"git rebase --continue | --abort | --skip | --edit-todo\"),\n> @@ -1018,6 +1018,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \t};\n>  \tconst char *branch_name;\n>  \tint ret, flags, total_argc, in_progress = 0;\n> +\tint keep_base = 0;\n>  \tint ok_to_skip_pre_rebase = 0;\n>  \tstruct strbuf msg = STRBUF_INIT;\n>  \tstruct strbuf revisions = STRBUF_INIT;\n> @@ -1051,6 +1052,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n>  \t\t\t   N_(\"revision\"),\n>  \t\t\t   N_(\"rebase onto given branch instead of upstream\")),\n> +\t\tOPT_BOOL(0, \"keep-base\", &keep_base,\n> +\t\t\t N_(\"use the merge-base of upstream and branch as the current base\")),\n>  \t\tOPT_BOOL(0, \"no-verify\", &ok_to_skip_pre_rebase,\n>  \t\t\t N_(\"allow pre-rebase hook to run\")),\n>  \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n> @@ -1217,6 +1220,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \t\tusage_with_options(builtin_rebase_usage,\n>  \t\t\t\t   builtin_rebase_options);\n>  \n> +\tif (keep_base) {\n> +\t\tif (options.onto_name)\n> +\t\t\tdie(_(\"cannot combine '--keep-base' with '--onto'\"));\n> +\t\tif (options.root)\n> +\t\t\tdie(_(\"cannot combine '--keep-base' with '--root'\"));\n> +\t}\n> +\n>  \tif (action != NO_ACTION && !in_progress)\n>  \t\tdie(_(\"No rebase in progress?\"));\n>  \tsetenv(GIT_REFLOG_ACTION_ENVIRONMENT, \"rebase\", 0);\n> @@ -1541,10 +1551,19 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \t}\n>  \n>  \t/* Make sure the branch to rebase onto is valid. */\n> -\tif (!options.onto_name)\n> +\tif (keep_base) {\n> +\t    strbuf_reset(&buf);\n> +\t    strbuf_addstr(&buf, options.upstream_name);\n> +\t    strbuf_addstr(&buf, \"...\");\n> +\t    options.onto_name = xstrdup(buf.buf);\n> +\t} else if (!options.onto_name)\n>  \t\toptions.onto_name = options.upstream_name;\n>  \tif (strstr(options.onto_name, \"...\")) {\n>  \t\tif (get_oid_mb(options.onto_name, &merge_base) < 0)\n> +\t\t    if (keep_base)\n> +\t\t\tdie(_(\"'%s': need exactly one merge base with branch\"),\n> +\t\t\t\toptions.upstream_name);\n> +\t\t    else\n>  \t\t\tdie(_(\"'%s': need exactly one merge base\"),\n>  \t\t\t    options.onto_name);\n>  \t\toptions.onto = lookup_commit_or_die(&merge_base,\n> -- \n> 2.21.0.512.g57bf1b23e1\n> \n"},{"id":"372455","messageId":"nycvar.QRO.7.76.6.1903251948200.41@tvgsbejvaqbjf.bet","threadId":"50810","inReplyTo":"f802e5442013613221a4efd8ef1fecce0f3a9914.1553354374.git.liu.denton@gmail.com","subject":"Re: [PATCH 1/3] rebase: teach rebase --keep-base","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-03-25T18:50:38Z","receivedAt":"2019-03-25T18:50:57Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Denton,\n\nOn Sat, 23 Mar 2019, Denton Liu wrote:\n\n> [...]\n>\n> This allows us to rewrite the above as\n>\n> \tgit rebase -i --keep-base master\n>\n> and\n>\n> \tgit rebase -x ./test.sh --keep-base master\n>\n> respectively.\n\nJust a quick note: this breaks t5407 because that test uses `git rebase\n--keep` and expects that abbreviated option to be expanded to\n`--keep-empty`, which is now no longer the only possible expansion.\n\nI just submitted a patch series to fix that, and other uses of abbreviated\noptions in the test suite, in\nhttps://public-inbox.org/git/pull.167.git.gitgitgadget@gmail.com/T/#t\n\nCiao,\nJohannes\n\nP.S.: Did you run the test suite before submitting your patches?\n"},{"id":"372460","messageId":"20190325192928.GA31067@dev-l","threadId":"50810","inReplyTo":"nycvar.QRO.7.76.6.1903251948200.41@tvgsbejvaqbjf.bet","subject":"Re: [PATCH 1/3] rebase: teach rebase --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-03-25T19:29:28Z","receivedAt":"2019-03-25T19:29:32Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Hi Johannes,\n\nOn Mon, Mar 25, 2019 at 07:50:38PM +0100, Johannes Schindelin wrote:\n> Hi Denton,\n> \n> On Sat, 23 Mar 2019, Denton Liu wrote:\n> \n> > [...]\n> >\n> > This allows us to rewrite the above as\n> >\n> > \tgit rebase -i --keep-base master\n> >\n> > and\n> >\n> > \tgit rebase -x ./test.sh --keep-base master\n> >\n> > respectively.\n> \n> Just a quick note: this breaks t5407 because that test uses `git rebase\n> --keep` and expects that abbreviated option to be expanded to\n> `--keep-empty`, which is now no longer the only possible expansion.\n> \n> I just submitted a patch series to fix that, and other uses of abbreviated\n> options in the test suite, in\n> https://public-inbox.org/git/pull.167.git.gitgitgadget@gmail.com/T/#t\n\nThanks for catching this. I replied with a (tiny) review.\n\n> \n> Ciao,\n> Johannes\n> \n> P.S.: Did you run the test suite before submitting your patches?\n\nUsually I'm more diligent about running tests but I wrote this patchset\nin the back of a car when I was running low on batteries. I only ran the\nrebase-related tests but I guess that wasn't enough.\n\nMy mistake, though. I'll be sure to _always_ run tests in the future.\n\nThanks,\n\nDenton\n"},{"id":"372516","messageId":"nycvar.QRO.7.76.6.1903261424270.41@tvgsbejvaqbjf.bet","threadId":"50810","inReplyTo":"20190325192928.GA31067@dev-l","subject":"Re: [PATCH 1/3] rebase: teach rebase --keep-base","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-03-26T13:27:47Z","receivedAt":"2019-03-26T13:28:07Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Denton,\n\nOn Mon, 25 Mar 2019, Denton Liu wrote:\n\n> On Mon, Mar 25, 2019 at 07:50:38PM +0100, Johannes Schindelin wrote:\n>\n> > P.S.: Did you run the test suite before submitting your patches?\n>\n> Usually I'm more diligent about running tests but I wrote this patchset\n> in the back of a car when I was running low on batteries. I only ran the\n> rebase-related tests but I guess that wasn't enough.\n\nI totally understand that kind of scenario.\n\n> My mistake, though. I'll be sure to _always_ run tests in the future.\n\nPlease do not feel bad. I did work pretty hard on making the Azure\nPipelines support as fast as it is for the very purpose of helping exactly\nsituations like yours: to hand off the testing to the cloud when batteries\nrun low (or when you want to use your laptop for something else than\nrunning Git's test suite).\n\nAnd of course to verify that you don't break things on platforms other\nthan the one you happen to develop on. And to check the documentation. And\nto run some static analyses.\n\n:-)\n\nMaybe you want to open PRs at https://github.com/git/git or at\nhttps://github.com/gitgitgadget/git to benefit from that kind of automatic\ntesting, benefitting from my work (which would make me feel real good\nabout spending that amount of time on it).\n\nCiao,\nJohannes\n"},{"id":"372519","messageId":"87bm1xbt55.fsf@evledraar.gmail.com","threadId":"50810","inReplyTo":"cover.1553354374.git.liu.denton@gmail.com","subject":"Re: [PATCH 0/3] rebase: learn --keep-base","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-03-26T14:35:34Z","receivedAt":"2019-03-26T14:35:40Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sat, Mar 23 2019, Denton Liu wrote:\n\n> This series teaches rebase the --keep-base option.\n>\n> 'git rebase --keep-base <upstream>' is equivalent to\n> 'git rebase --onto <upstream>... <upstream>' or\n> 'git rebase --onto $(git merge-base <upstream> HEAD) <upstream>' .\n>\n> This seems to be a common case that people (including myself!) run into; I was\n> able to find these StackOverflow posts about this use case:\n>\n> * https://stackoverflow.com/questions/53234798/can-i-rebase-on-a-branchs-fork-point-without-explicitly-specifying-the-parent\n> * https://stackoverflow.com/questions/41529128/how-do-you-rebase-only-changes-between-two-branches-into-another-branch\n> * https://stackoverflow.com/a/4207357\n\nLike with another series of yours I think this would be best squashed\ninto one patch.\n\nMaybe I've misunderstood this but isn't this like --fork-point except\nwith just plain \"git merge-base\" instead of \"git merge-base\n--fork-point\", but then again 2/3 shows multiple base aren't supported,\nbut merge-base supports that.\n\nI'd find something like the \"DISCUSSION ON FORK-POINT MODE\" in\ngit-merge-base helpful with examples of what we'd pick in the various\nscenarios, and also if whatever commit this picks was something you\ncould have \"git merge-base\" spew out, so you could get what rebase would\ndo here from other tooling (which maybe is possible, but I'm confused by\nthe \"no multiple bases\"...).\n"},{"id":"372533","messageId":"20190326175052.GA14922@dev-l","threadId":"50810","inReplyTo":"87bm1xbt55.fsf@evledraar.gmail.com","subject":"Re: [PATCH 0/3] rebase: learn --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-03-26T17:50:52Z","receivedAt":"2019-03-26T17:50:58Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Hi Ævar,\n\nOn Tue, Mar 26, 2019 at 03:35:34PM +0100, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Sat, Mar 23 2019, Denton Liu wrote:\n> \n> > This series teaches rebase the --keep-base option.\n> >\n> > 'git rebase --keep-base <upstream>' is equivalent to\n> > 'git rebase --onto <upstream>... <upstream>' or\n> > 'git rebase --onto $(git merge-base <upstream> HEAD) <upstream>' .\n> >\n> > This seems to be a common case that people (including myself!) run into; I was\n> > able to find these StackOverflow posts about this use case:\n> >\n> > * https://stackoverflow.com/questions/53234798/can-i-rebase-on-a-branchs-fork-point-without-explicitly-specifying-the-parent\n> > * https://stackoverflow.com/questions/41529128/how-do-you-rebase-only-changes-between-two-branches-into-another-branch\n> > * https://stackoverflow.com/a/4207357\n> \n> Like with another series of yours I think this would be best squashed\n> into one patch.\n\nWill do.\n\n> \n> Maybe I've misunderstood this but isn't this like --fork-point except\n> with just plain \"git merge-base\" instead of \"git merge-base\n> --fork-point\", but then again 2/3 shows multiple base aren't supported,\n> but merge-base supports that.\n> \n\n--fork-point gets used to determine the _set of_ commits which are to be\nrebased, whereas --keep-base (and --onto) determine the base where that\nset of commits will be spliced. As a result, these two options cover\northogonal use-cases.\n\nThe reason why --keep-base doesn't support multiple bases for the same\nreason that --onto already disallows multiple bases. If we have multiple\nbases, how do we determine which one is the \"true base\" to use? It makes\nmore sense to error out and let the user manually specify it.\n\n> I'd find something like the \"DISCUSSION ON FORK-POINT MODE\" in\n> git-merge-base helpful with examples of what we'd pick in the various\n> scenarios, and also if whatever commit this picks was something you\n> could have \"git merge-base\" spew out, so you could get what rebase would\n> do here from other tooling (which maybe is possible, but I'm confused by\n> the \"no multiple bases\"...).\n\nIf I'm understanding you correctly then yes, this could be done with\nother tooling. See the 0/3 for equivalent commands.\n\nPerhaps I should update the rebase documentation to mention that\n--fork-point and --keep-base are orthogonal because it's unclear for\nyou, it's probably unclear for other users as well.\n\nThanks,\n\nDenton\n"},{"id":"372547","messageId":"878sx1bcgr.fsf@evledraar.gmail.com","threadId":"50810","inReplyTo":"20190326175052.GA14922@dev-l","subject":"Re: [PATCH 0/3] rebase: learn --keep-base","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-03-26T20:35:48Z","receivedAt":"2019-03-26T20:36:00Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Mar 26 2019, Denton Liu wrote:\n\n> Hi Ævar,\n>\n> On Tue, Mar 26, 2019 at 03:35:34PM +0100, Ævar Arnfjörð Bjarmason wrote:\n>>\n>> On Sat, Mar 23 2019, Denton Liu wrote:\n>>\n>> > This series teaches rebase the --keep-base option.\n>> >\n>> > 'git rebase --keep-base <upstream>' is equivalent to\n>> > 'git rebase --onto <upstream>... <upstream>' or\n>> > 'git rebase --onto $(git merge-base <upstream> HEAD) <upstream>' .\n>> >\n>> > This seems to be a common case that people (including myself!) run into; I was\n>> > able to find these StackOverflow posts about this use case:\n>> >\n>> > * https://stackoverflow.com/questions/53234798/can-i-rebase-on-a-branchs-fork-point-without-explicitly-specifying-the-parent\n>> > * https://stackoverflow.com/questions/41529128/how-do-you-rebase-only-changes-between-two-branches-into-another-branch\n>> > * https://stackoverflow.com/a/4207357\n>>\n>> Like with another series of yours I think this would be best squashed\n>> into one patch.\n>\n> Will do.\n>\n>>\n>> Maybe I've misunderstood this but isn't this like --fork-point except\n>> with just plain \"git merge-base\" instead of \"git merge-base\n>> --fork-point\", but then again 2/3 shows multiple base aren't supported,\n>> but merge-base supports that.\n>>\n>\n> --fork-point gets used to determine the _set of_ commits which are to be\n> rebased, whereas --keep-base (and --onto) determine the base where that\n> set of commits will be spliced. As a result, these two options cover\n> orthogonal use-cases.\n\nRight. After playing with this a bit more though --fork-point is mostly\nthere, it it does find the same fork point, as evidenced all your tests\n(that aren't asserting incompatibility with other options) passing with\nthis:\n\n    diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh\n    index 9c2548423b..ab2d50e69a 100755\n    --- a/t/t3416-rebase-onto-threedots.sh\n    +++ b/t/t3416-rebase-onto-threedots.sh\n    @@ -116,7 +116,7 @@ test_expect_success 'rebase --keep-base master from topic' '\n            git checkout topic &&\n            git reset --hard G &&\n\n    -       git rebase --keep-base master &&\n    +       git rebase $(git merge-base --fork-point master HEAD) &&\n            git rev-parse C >base.expect &&\n            git merge-base master HEAD >base.actual &&\n            test_cmp base.expect base.actual &&\n    @@ -140,7 +140,7 @@ test_expect_success 'rebase -i --keep-base master from topic' '\n            git reset --hard G &&\n\n            set_fake_editor &&\n    -       EXPECT_COUNT=2 git rebase -i --keep-base master &&\n    +       EXPECT_COUNT=2 git rebase -i $(git merge-base --fork-point master HEAD) &&\n            git rev-parse C >base.expect &&\n            git merge-base master HEAD >base.actual &&\n            test_cmp base.expect base.actual &&\n\nI've poked at some of this recently in\nhttps://public-inbox.org/git/20190221214059.9195-3-avarab@gmail.com/ as\nnoted in the feedback there (I haven't gotten around to v2 yet) it's\nentirely possible that I haven't understood this at all :)\n\nBut it seems to me that this patch/implementation conflates two\nunrelated things.\n\nOnce is that we use --fork-point to mean that we're going to find the\ndivergence point with \"merge-base --fork-point\". This gets you halfway\nto where you want to be, i.e. AFAICT the --keep-base and --fork-point\nwill always find the same commit for \"git rebase\" and \"git rebase\n--keep-base\". See the \"options.restrict_revision = get_fork_point(...)\"\npart of the code.\n\nThe other, which you want to disable, is that --fork-point *also* says\n\"OK, once we've found the divergence point, let's then rebase it on the\nlatest upstream. Or in the example above the \"master\" part of \"git\nmerge-base --fork-point master HEAD\".\n\nShouldn't --keep-base just be implemented in terms of skipping *that*\npart, i.e. we find the fork point using the upstream info, but then\ndon't rebase *on* upstream.\n\nThe reason the distinction matters is because with your patch these two\nact differently:\n\n    git rebase --keep-base\n    git rebase $(git merge-base @{u} HEAD)\n\nThe latter will skip work (\"Current branch master is up to date\"), but\n--keep-base will always re-rebase things. There's some cases where\n--fork-point does that, which I was trying to address with my linked WIP\npatch above.\n\nWhereas the thing you actually want to work is:\n\n    git rebase -i --keep-base\n    git rebase -i $(git merge-base @{u} HEAD)\n\nI.e. to have both of those allow you to re-arrange/fixup whatever and\nstill rebase on the same divergence point with @{u}, and won't run\nrebase when there's no work to do unless you give it --force-rebase.\n\n> reason that --onto already disallows multiple bases. If we have multiple\n> bases, how do we determine which one is the \"true base\" to use? It makes\n> more sense to error out and let the user manually specify it.\n\nAh, makes sense.\n\n>> I'd find something like the \"DISCUSSION ON FORK-POINT MODE\" in\n>> git-merge-base helpful with examples of what we'd pick in the various\n>> scenarios, and also if whatever commit this picks was something you\n>> could have \"git merge-base\" spew out, so you could get what rebase would\n>> do here from other tooling (which maybe is possible, but I'm confused by\n>> the \"no multiple bases\"...).\n>\n> If I'm understanding you correctly then yes, this could be done with\n> other tooling. See the 0/3 for equivalent commands.\n>\n> Perhaps I should update the rebase documentation to mention that\n> --fork-point and --keep-base are orthogonal because it's unclear for\n> you, it's probably unclear for other users as well.\n>\n> Thanks,\n>\n> Denton\n"},{"id":"372549","messageId":"20190326213031.GA21504@dev-l","threadId":"50810","inReplyTo":"878sx1bcgr.fsf@evledraar.gmail.com","subject":"Re: [PATCH 0/3] rebase: learn --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-03-26T21:30:31Z","receivedAt":"2019-03-26T21:30:36Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Hi Ævar,\n\nOn Tue, Mar 26, 2019 at 09:35:48PM +0100, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Tue, Mar 26 2019, Denton Liu wrote:\n> \n> > Hi Ævar,\n> >\n> > On Tue, Mar 26, 2019 at 03:35:34PM +0100, Ævar Arnfjörð Bjarmason wrote:\n> >>\n> >> On Sat, Mar 23 2019, Denton Liu wrote:\n> >>\n> >> > This series teaches rebase the --keep-base option.\n> >> >\n> >> > 'git rebase --keep-base <upstream>' is equivalent to\n> >> > 'git rebase --onto <upstream>... <upstream>' or\n> >> > 'git rebase --onto $(git merge-base <upstream> HEAD) <upstream>' .\n> >> >\n> >> > This seems to be a common case that people (including myself!) run into; I was\n> >> > able to find these StackOverflow posts about this use case:\n> >> >\n> >> > * https://stackoverflow.com/questions/53234798/can-i-rebase-on-a-branchs-fork-point-without-explicitly-specifying-the-parent\n> >> > * https://stackoverflow.com/questions/41529128/how-do-you-rebase-only-changes-between-two-branches-into-another-branch\n> >> > * https://stackoverflow.com/a/4207357\n> >>\n> >> Like with another series of yours I think this would be best squashed\n> >> into one patch.\n> >\n> > Will do.\n> >\n> >>\n> >> Maybe I've misunderstood this but isn't this like --fork-point except\n> >> with just plain \"git merge-base\" instead of \"git merge-base\n> >> --fork-point\", but then again 2/3 shows multiple base aren't supported,\n> >> but merge-base supports that.\n> >>\n> >\n> > --fork-point gets used to determine the _set of_ commits which are to be\n> > rebased, whereas --keep-base (and --onto) determine the base where that\n> > set of commits will be spliced. As a result, these two options cover\n> > orthogonal use-cases.\n> \n> Right. After playing with this a bit more though --fork-point is mostly\n> there, it it does find the same fork point, as evidenced all your tests\n> (that aren't asserting incompatibility with other options) passing with\n> this:\n> \n>     diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh\n>     index 9c2548423b..ab2d50e69a 100755\n>     --- a/t/t3416-rebase-onto-threedots.sh\n>     +++ b/t/t3416-rebase-onto-threedots.sh\n>     @@ -116,7 +116,7 @@ test_expect_success 'rebase --keep-base master from topic' '\n>             git checkout topic &&\n>             git reset --hard G &&\n> \n>     -       git rebase --keep-base master &&\n>     +       git rebase $(git merge-base --fork-point master HEAD) &&\n>             git rev-parse C >base.expect &&\n>             git merge-base master HEAD >base.actual &&\n>             test_cmp base.expect base.actual &&\n>     @@ -140,7 +140,7 @@ test_expect_success 'rebase -i --keep-base master from topic' '\n>             git reset --hard G &&\n> \n>             set_fake_editor &&\n>     -       EXPECT_COUNT=2 git rebase -i --keep-base master &&\n>     +       EXPECT_COUNT=2 git rebase -i $(git merge-base --fork-point master HEAD) &&\n>             git rev-parse C >base.expect &&\n>             git merge-base master HEAD >base.actual &&\n>             test_cmp base.expect base.actual &&\n> \n> I've poked at some of this recently in\n> https://public-inbox.org/git/20190221214059.9195-3-avarab@gmail.com/ as\n> noted in the feedback there (I haven't gotten around to v2 yet) it's\n> entirely possible that I haven't understood this at all :)\n> \n> But it seems to me that this patch/implementation conflates two\n> unrelated things.\n> \n> Once is that we use --fork-point to mean that we're going to find the\n> divergence point with \"merge-base --fork-point\". This gets you halfway\n> to where you want to be, i.e. AFAICT the --keep-base and --fork-point\n> will always find the same commit for \"git rebase\" and \"git rebase\n> --keep-base\". See the \"options.restrict_revision = get_fork_point(...)\"\n> part of the code.\n\nI don't think this is true. The code that --keep-base uses to find the\nmerge base is get_oid_mb, see the relevant snippet\n\n\tif (strstr(options.onto_name, \"...\")) {\n\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0)\n\nwhereas the --fork-point code uses get_fork_point, as you mentioned\nabove. As a result, they don't necessarily refer to the same commit in\nthe case where upstream is rewound.\n\n> \n> The other, which you want to disable, is that --fork-point *also* says\n> \"OK, once we've found the divergence point, let's then rebase it on the\n> latest upstream. Or in the example above the \"master\" part of \"git\n> merge-base --fork-point master HEAD\".\n\nCorrect, I guess in essence this is what I'm doing.\n\n> \n> Shouldn't --keep-base just be implemented in terms of skipping *that*\n> part, i.e. we find the fork point using the upstream info, but then\n> don't rebase *on* upstream.\n> \n> The reason the distinction matters is because with your patch these two\n> act differently:\n> \n>     git rebase --keep-base\n>     git rebase $(git merge-base @{u} HEAD)\n> \n> The latter will skip work (\"Current branch master is up to date\"), but\n> --keep-base will always re-rebase things. There's some cases where\n> --fork-point does that, which I was trying to address with my linked WIP\n> patch above.\n\nI believe this is desired behaviour. Suppose we have this (modified)\ngraph from the git-merge-base docs, where B3 was formerly part of\norigin/master but it was then rewound:\n\n           ---o---o---B2--o---o---o---B (origin/master)\n                   \\\n                    B3\n                     \\\n                      Derived (local master)\n\nIf we run \"git rebase --keep-base\", we'll get the following graph:\n\n           ---o---o---B2--o---o---o---B (origin/master)\n                   \\\n                    Derived (local master)\n\nwhich I believe is the desired behaviour (we're abandoning B3 since\nupstream abandoned it).\n\nI hope I'm understanding you correctly. Please let me know if I've\nmisinterpreted anything you've said or if anything I've said is unclear.\n\nThanks,\n\nDenton\n\n> \n> Whereas the thing you actually want to work is:\n> \n>     git rebase -i --keep-base\n>     git rebase -i $(git merge-base @{u} HEAD)\n> \n> I.e. to have both of those allow you to re-arrange/fixup whatever and\n> still rebase on the same divergence point with @{u}, and won't run\n> rebase when there's no work to do unless you give it --force-rebase.\n> \n> > reason that --onto already disallows multiple bases. If we have multiple\n> > bases, how do we determine which one is the \"true base\" to use? It makes\n> > more sense to error out and let the user manually specify it.\n> \n> Ah, makes sense.\n> \n> >> I'd find something like the \"DISCUSSION ON FORK-POINT MODE\" in\n> >> git-merge-base helpful with examples of what we'd pick in the various\n> >> scenarios, and also if whatever commit this picks was something you\n> >> could have \"git merge-base\" spew out, so you could get what rebase would\n> >> do here from other tooling (which maybe is possible, but I'm confused by\n> >> the \"no multiple bases\"...).\n> >\n> > If I'm understanding you correctly then yes, this could be done with\n> > other tooling. See the 0/3 for equivalent commands.\n> >\n> > Perhaps I should update the rebase documentation to mention that\n> > --fork-point and --keep-base are orthogonal because it's unclear for\n> > you, it's probably unclear for other users as well.\n> >\n> > Thanks,\n> >\n> > Denton\n"},{"id":"372580","messageId":"871s2sba2h.fsf@evledraar.gmail.com","threadId":"50810","inReplyTo":"20190326213031.GA21504@dev-l","subject":"Re: [PATCH 0/3] rebase: learn --keep-base","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-03-27T15:39:50Z","receivedAt":"2019-03-27T15:39:56Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Tue, Mar 26 2019, Denton Liu wrote:\n\n> Hi Ævar,\n>\n> On Tue, Mar 26, 2019 at 09:35:48PM +0100, Ævar Arnfjörð Bjarmason wrote:\n>>\n>> On Tue, Mar 26 2019, Denton Liu wrote:\n>>\n>> > Hi Ævar,\n>> >\n>> > On Tue, Mar 26, 2019 at 03:35:34PM +0100, Ævar Arnfjörð Bjarmason wrote:\n>> >>\n>> >> On Sat, Mar 23 2019, Denton Liu wrote:\n>> >>\n>> >> > This series teaches rebase the --keep-base option.\n>> >> >\n>> >> > 'git rebase --keep-base <upstream>' is equivalent to\n>> >> > 'git rebase --onto <upstream>... <upstream>' or\n>> >> > 'git rebase --onto $(git merge-base <upstream> HEAD) <upstream>' .\n>> >> >\n>> >> > This seems to be a common case that people (including myself!) run into; I was\n>> >> > able to find these StackOverflow posts about this use case:\n>> >> >\n>> >> > * https://stackoverflow.com/questions/53234798/can-i-rebase-on-a-branchs-fork-point-without-explicitly-specifying-the-parent\n>> >> > * https://stackoverflow.com/questions/41529128/how-do-you-rebase-only-changes-between-two-branches-into-another-branch\n>> >> > * https://stackoverflow.com/a/4207357\n>> >>\n>> >> Like with another series of yours I think this would be best squashed\n>> >> into one patch.\n>> >\n>> > Will do.\n>> >\n>> >>\n>> >> Maybe I've misunderstood this but isn't this like --fork-point except\n>> >> with just plain \"git merge-base\" instead of \"git merge-base\n>> >> --fork-point\", but then again 2/3 shows multiple base aren't supported,\n>> >> but merge-base supports that.\n>> >>\n>> >\n>> > --fork-point gets used to determine the _set of_ commits which are to be\n>> > rebased, whereas --keep-base (and --onto) determine the base where that\n>> > set of commits will be spliced. As a result, these two options cover\n>> > orthogonal use-cases.\n>>\n>> Right. After playing with this a bit more though --fork-point is mostly\n>> there, it it does find the same fork point, as evidenced all your tests\n>> (that aren't asserting incompatibility with other options) passing with\n>> this:\n>>\n>>     diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh\n>>     index 9c2548423b..ab2d50e69a 100755\n>>     --- a/t/t3416-rebase-onto-threedots.sh\n>>     +++ b/t/t3416-rebase-onto-threedots.sh\n>>     @@ -116,7 +116,7 @@ test_expect_success 'rebase --keep-base master from topic' '\n>>             git checkout topic &&\n>>             git reset --hard G &&\n>>\n>>     -       git rebase --keep-base master &&\n>>     +       git rebase $(git merge-base --fork-point master HEAD) &&\n>>             git rev-parse C >base.expect &&\n>>             git merge-base master HEAD >base.actual &&\n>>             test_cmp base.expect base.actual &&\n>>     @@ -140,7 +140,7 @@ test_expect_success 'rebase -i --keep-base master from topic' '\n>>             git reset --hard G &&\n>>\n>>             set_fake_editor &&\n>>     -       EXPECT_COUNT=2 git rebase -i --keep-base master &&\n>>     +       EXPECT_COUNT=2 git rebase -i $(git merge-base --fork-point master HEAD) &&\n>>             git rev-parse C >base.expect &&\n>>             git merge-base master HEAD >base.actual &&\n>>             test_cmp base.expect base.actual &&\n>>\n>> I've poked at some of this recently in\n>> https://public-inbox.org/git/20190221214059.9195-3-avarab@gmail.com/ as\n>> noted in the feedback there (I haven't gotten around to v2 yet) it's\n>> entirely possible that I haven't understood this at all :)\n>>\n>> But it seems to me that this patch/implementation conflates two\n>> unrelated things.\n>>\n>> Once is that we use --fork-point to mean that we're going to find the\n>> divergence point with \"merge-base --fork-point\". This gets you halfway\n>> to where you want to be, i.e. AFAICT the --keep-base and --fork-point\n>> will always find the same commit for \"git rebase\" and \"git rebase\n>> --keep-base\". See the \"options.restrict_revision = get_fork_point(...)\"\n>> part of the code.\n>\n> I don't think this is true. The code that --keep-base uses to find the\n> merge base is get_oid_mb, see the relevant snippet\n>\n> \tif (strstr(options.onto_name, \"...\")) {\n> \t\tif (get_oid_mb(options.onto_name, &merge_base) < 0)\n>\n> whereas the --fork-point code uses get_fork_point, as you mentioned\n> above. As a result, they don't necessarily refer to the same commit in\n> the case where upstream is rewound.\n>\n>>\n>> The other, which you want to disable, is that --fork-point *also* says\n>> \"OK, once we've found the divergence point, let's then rebase it on the\n>> latest upstream. Or in the example above the \"master\" part of \"git\n>> merge-base --fork-point master HEAD\".\n>\n> Correct, I guess in essence this is what I'm doing.\n>\n>>\n>> Shouldn't --keep-base just be implemented in terms of skipping *that*\n>> part, i.e. we find the fork point using the upstream info, but then\n>> don't rebase *on* upstream.\n>>\n>> The reason the distinction matters is because with your patch these two\n>> act differently:\n>>\n>>     git rebase --keep-base\n>>     git rebase $(git merge-base @{u} HEAD)\n>>\n>> The latter will skip work (\"Current branch master is up to date\"), but\n>> --keep-base will always re-rebase things. There's some cases where\n>> --fork-point does that, which I was trying to address with my linked WIP\n>> patch above.\n>\n> I believe this is desired behaviour. Suppose we have this (modified)\n> graph from the git-merge-base docs, where B3 was formerly part of\n> origin/master but it was then rewound:\n>\n>            ---o---o---B2--o---o---o---B (origin/master)\n>                    \\\n>                     B3\n>                      \\\n>                       Derived (local master)\n>\n> If we run \"git rebase --keep-base\", we'll get the following graph:\n>\n>            ---o---o---B2--o---o---o---B (origin/master)\n>                    \\\n>                     Derived (local master)\n>\n> which I believe is the desired behaviour (we're abandoning B3 since\n> upstream abandoned it).\n>\n> I hope I'm understanding you correctly. Please let me know if I've\n> misinterpreted anything you've said or if anything I've said is unclear.\n\nYeah. I'm still confused, but mainly because I haven't allocated enough\nbrainpower to try to understand it :)\n\nSo yeah, I can believe it's subtly different, would be great to have a\nv2 whose docs/tests cover those subtleties, right now (as seen in my\ndiscussion upthread) the tests that are there can identically use the\nfork point.\n\nI also wonder if we can holistically think about this UI / how we can\nexpose different things. E.g. for the times I've needed this and have\nmanually dug up the fork point I haven't wanted to handle the case of\nupstream rewinding, just re-rebase-i on some old base, while still\nhaving upstream tracking info, and for rebase to exit early if there's\nnothing to do (similar to if I feed it the fork point as a rev).\n\n>>\n>> Whereas the thing you actually want to work is:\n>>\n>>     git rebase -i --keep-base\n>>     git rebase -i $(git merge-base @{u} HEAD)\n>>\n>> I.e. to have both of those allow you to re-arrange/fixup whatever and\n>> still rebase on the same divergence point with @{u}, and won't run\n>> rebase when there's no work to do unless you give it --force-rebase.\n>>\n>> > reason that --onto already disallows multiple bases. If we have multiple\n>> > bases, how do we determine which one is the \"true base\" to use? It makes\n>> > more sense to error out and let the user manually specify it.\n>>\n>> Ah, makes sense.\n>>\n>> >> I'd find something like the \"DISCUSSION ON FORK-POINT MODE\" in\n>> >> git-merge-base helpful with examples of what we'd pick in the various\n>> >> scenarios, and also if whatever commit this picks was something you\n>> >> could have \"git merge-base\" spew out, so you could get what rebase would\n>> >> do here from other tooling (which maybe is possible, but I'm confused by\n>> >> the \"no multiple bases\"...).\n>> >\n>> > If I'm understanding you correctly then yes, this could be done with\n>> > other tooling. See the 0/3 for equivalent commands.\n>> >\n>> > Perhaps I should update the rebase documentation to mention that\n>> > --fork-point and --keep-base are orthogonal because it's unclear for\n>> > you, it's probably unclear for other users as well.\n>> >\n>> > Thanks,\n>> >\n>> > Denton\n"},{"id":"372652","messageId":"20190328221745.GA3941@dev-l","threadId":"50810","inReplyTo":"cover.1553354374.git.liu.denton@gmail.com","subject":"[PATCH v2] rebase: teach rebase --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-03-28T22:17:45Z","receivedAt":"2019-03-28T22:17:51Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"A common scenario is if a user is working on a topic branch and they\nwish to make some changes to intermediate commits or autosquash, they\nwould run something such as\n\n\tgit rebase -i --onto master... master\n\nin order to preserve the merge base. This prevents unnecessary commit\nchurning.\n\nAlternatively, a user wishing to test individual commits in a topic\nbranch without changing anything may run\n\n\tgit rebase -x ./test.sh master... master\n\nSince rebasing onto the merge base of the branch and the upstream is\nsuch a common case, introduce the --keep-base option as a shortcut.\n\nThis allows us to rewrite the above as\n\n\tgit rebase -i --keep-base master\n\nand\n\n\tgit rebase -x ./test.sh --keep-base master\n\nrespectively.\n\nAdd tests to ensure --keep-base works correctly in the normal case and\nfails when there are multiple merge bases, both in regular and\ninteractive mode. Also, test to make sure conflicting options cause\nrebase to fail. While we're adding test cases, add a missing\nset_fake_editor call to 'rebase -i --onto master...side'.\n\nWhile we're documenting the --keep-base option, change an instance of\n\"merge-base\" to \"merge base\", which is the consistent spelling.\n\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n\nÆvar, I have a feeling that we're still miscommunicating and we don't\nfully understand each other. I'm putting up v2 to hopefully clear things\nup a little but I welcome more feedback.\n\nThis patch now depends \"[PATCH 1/8] tests (rebase): spell out the\n`--keep-empty` option\" which is the first patch of Johannes's \"Do not\nuse abbreviated options in tests\" patchset[1]. (Thanks for catching\nthat, Johannes!)\n\nChanges since v1:\n\n* Squashed old set into one patch\n* Fixed indentation style and dangling else\n* Added more documentation after discussion with Ævar\n\n[1]: https://public-inbox.org/git/a1b4b74b9167e279dae4cd8c58fb28d8a714a66a.1553537656.git.gitgitgadget@gmail.com/\n\n Documentation/git-rebase.txt     | 25 ++++++++++++--\n builtin/rebase.c                 | 32 ++++++++++++++----\n t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++++++++\n 3 files changed, 105 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 6363d674b7..27be1f48ff 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip\n SYNOPSIS\n --------\n [verse]\n-'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n-\t[<upstream> [<branch>]]\n+'git rebase' [-i | --interactive] [<options>] [--exec <cmd>]\n+\t[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\n 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n \t--root [<branch>]\n 'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch\n@@ -217,6 +217,19 @@ As a special case, you may use \"A\\...B\" as a shortcut for the\n merge base of A and B if there is exactly one merge base. You can\n leave out at most one of A and B, in which case it defaults to HEAD.\n \n+--keep-base::\n+\tSet the starting point at which to create the new commits to the\n+\tmerge base of <upstream> <branch>. Running\n+\t'git rebase --keep-base <upstream> <branch>' is equivalent to\n+\trunning 'git rebase --onto <upstream>... <upstream>'.\n++\n+Although both this option and --fork-point find the merge base between\n+<upstream> and <branch>, this option uses the merge base as the _starting\n+point_ on which new commits will be created, whereas --fork-point uses\n+the merge base to determine the _set of commits_ which will be rebased.\n++\n+See also INCOMPATIBLE OPTIONS below.\n+\n <upstream>::\n \tUpstream branch to compare against.  May be any valid commit,\n \tnot just an existing branch name. Defaults to the configured\n@@ -364,6 +377,10 @@ ends up being empty, the <upstream> will be used as a fallback.\n +\n If either <upstream> or --root is given on the command line, then the\n default is `--no-fork-point`, otherwise the default is `--fork-point`.\n++\n+If your branch was based on <upstream> but <upstream> was rewound and\n+your branch contains commits which were dropped, this option can be used\n+with `--keep-base` in order to drop those commits from your branch.\n \n --ignore-whitespace::\n --whitespace=<option>::\n@@ -539,6 +556,8 @@ In addition, the following pairs of options are incompatible:\n  * --preserve-merges and --rebase-merges\n  * --rebase-merges and --strategy\n  * --rebase-merges and --strategy-option\n+ * --keep-base and --onto\n+ * --keep-base and --root\n \n BEHAVIORAL DIFFERENCES\n -----------------------\n@@ -863,7 +882,7 @@ NOTE: While an \"easy case recovery\" sometimes appears to be successful\n       --interactive` will be **resurrected**!\n \n The idea is to manually tell 'git rebase' \"where the old 'subsystem'\n-ended and your 'topic' began\", that is, what the old merge-base\n+ended and your 'topic' began\", that is, what the old merge base\n between them was.  You will have to find a way to name the last commit\n of the old 'subsystem', for example:\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 77deebc65c..7c14a00460 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -27,8 +27,8 @@\n #include \"branch.h\"\n \n static char const * const builtin_rebase_usage[] = {\n-\tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n-\t\t\"[<upstream>] [<branch>]\"),\n+\tN_(\"git rebase [-i] [options] [--exec <cmd>] \"\n+\t\t\"[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\"),\n \tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n \t\t\"--root [<branch>]\"),\n \tN_(\"git rebase --continue | --abort | --skip | --edit-todo\"),\n@@ -1018,6 +1018,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t};\n \tconst char *branch_name;\n \tint ret, flags, total_argc, in_progress = 0;\n+\tint keep_base = 0;\n \tint ok_to_skip_pre_rebase = 0;\n \tstruct strbuf msg = STRBUF_INIT;\n \tstruct strbuf revisions = STRBUF_INIT;\n@@ -1051,6 +1052,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"rebase onto given branch instead of upstream\")),\n+\t\tOPT_BOOL(0, \"keep-base\", &keep_base,\n+\t\t\t N_(\"use the merge-base of upstream and branch as the current base\")),\n \t\tOPT_BOOL(0, \"no-verify\", &ok_to_skip_pre_rebase,\n \t\t\t N_(\"allow pre-rebase hook to run\")),\n \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n@@ -1217,6 +1220,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(builtin_rebase_usage,\n \t\t\t\t   builtin_rebase_options);\n \n+\tif (keep_base) {\n+\t\tif (options.onto_name)\n+\t\t\tdie(_(\"cannot combine '--keep-base' with '--onto'\"));\n+\t\tif (options.root)\n+\t\t\tdie(_(\"cannot combine '--keep-base' with '--root'\"));\n+\t}\n+\n \tif (action != NO_ACTION && !in_progress)\n \t\tdie(_(\"No rebase in progress?\"));\n \tsetenv(GIT_REFLOG_ACTION_ENVIRONMENT, \"rebase\", 0);\n@@ -1541,12 +1551,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t}\n \n \t/* Make sure the branch to rebase onto is valid. */\n-\tif (!options.onto_name)\n+\tif (keep_base) {\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addstr(&buf, options.upstream_name);\n+\t\tstrbuf_addstr(&buf, \"...\");\n+\t\toptions.onto_name = xstrdup(buf.buf);\n+\t} else if (!options.onto_name)\n \t\toptions.onto_name = options.upstream_name;\n \tif (strstr(options.onto_name, \"...\")) {\n-\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0)\n-\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n-\t\t\t    options.onto_name);\n+\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0) {\n+\t\t\tif (keep_base)\n+\t\t\t\tdie(_(\"'%s': need exactly one merge base with branch\"),\n+\t\t\t\t    options.upstream_name);\n+\t\t\telse\n+\t\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n+\t\t\t\t    options.onto_name);\n+\t\t}\n \t\toptions.onto = lookup_commit_or_die(&merge_base,\n \t\t\t\t\t\t    options.onto_name);\n \t} else {\ndiff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh\nindex ddf2f64853..9c2548423b 100755\n--- a/t/t3416-rebase-onto-threedots.sh\n+++ b/t/t3416-rebase-onto-threedots.sh\n@@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' '\n \tgit checkout side &&\n \tgit reset --hard K &&\n \n+\tset_fake_editor &&\n \ttest_must_fail git rebase -i --onto master...side J\n '\n \n+test_expect_success 'rebase --keep-base --onto incompatible' '\n+\ttest_must_fail git rebase --keep-base --onto master...\n+'\n+\n+test_expect_success 'rebase --keep-base --root incompatible' '\n+\ttest_must_fail git rebase --keep-base --root\n+'\n+\n+test_expect_success 'rebase --keep-base master from topic' '\n+\tgit reset --hard &&\n+\tgit checkout topic &&\n+\tgit reset --hard G &&\n+\n+\tgit rebase --keep-base master &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base master HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --keep-base master from side' '\n+\tgit reset --hard &&\n+\tgit checkout side &&\n+\tgit reset --hard K &&\n+\n+\ttest_must_fail git rebase --keep-base master\n+'\n+\n+test_expect_success 'rebase -i --keep-base master from topic' '\n+\tgit reset --hard &&\n+\tgit checkout topic &&\n+\tgit reset --hard G &&\n+\n+\tset_fake_editor &&\n+\tEXPECT_COUNT=2 git rebase -i --keep-base master &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base master HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase -i --keep-base master from side' '\n+\tgit reset --hard &&\n+\tgit checkout side &&\n+\tgit reset --hard K &&\n+\n+\tset_fake_editor &&\n+\ttest_must_fail git rebase -i --keep-base master\n+'\n+\n test_done\n-- \n2.21.0.695.gaf8658f249\n\n"},{"id":"372654","messageId":"87tvfma8yt.fsf@evledraar.gmail.com","threadId":"50810","inReplyTo":"20190328221745.GA3941@dev-l","subject":"Re: [PATCH v2] rebase: teach rebase --keep-base","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-03-28T23:13:30Z","receivedAt":"2019-03-28T23:13:36Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Thu, Mar 28 2019, Denton Liu wrote:\n\n> A common scenario is if a user is working on a topic branch and they\n> wish to make some changes to intermediate commits or autosquash, they\n> would run something such as\n>\n> \tgit rebase -i --onto master... master\n>\n> in order to preserve the merge base. This prevents unnecessary commit\n> churning.\n>\n> Alternatively, a user wishing to test individual commits in a topic\n> branch without changing anything may run\n>\n> \tgit rebase -x ./test.sh master... master\n>\n> Since rebasing onto the merge base of the branch and the upstream is\n> such a common case, introduce the --keep-base option as a shortcut.\n>\n> This allows us to rewrite the above as\n>\n> \tgit rebase -i --keep-base master\n>\n> and\n>\n> \tgit rebase -x ./test.sh --keep-base master\n>\n> respectively.\n>\n> Add tests to ensure --keep-base works correctly in the normal case and\n> fails when there are multiple merge bases, both in regular and\n> interactive mode. Also, test to make sure conflicting options cause\n> rebase to fail. While we're adding test cases, add a missing\n> set_fake_editor call to 'rebase -i --onto master...side'.\n>\n> While we're documenting the --keep-base option, change an instance of\n> \"merge-base\" to \"merge base\", which is the consistent spelling.\n>\n> Helped-by: Eric Sunshine <sunshine@sunshineco.com>\n> Helped-by: Junio C Hamano <gitster@pobox.com>\n> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> Signed-off-by: Denton Liu <liu.denton@gmail.com>\n> ---\n>\n> Ævar, I have a feeling that we're still miscommunicating and we don't\n> fully understand each other. I'm putting up v2 to hopefully clear things\n> up a little but I welcome more feedback.\n>\n> This patch now depends \"[PATCH 1/8] tests (rebase): spell out the\n> `--keep-empty` option\" which is the first patch of Johannes's \"Do not\n> use abbreviated options in tests\" patchset[1]. (Thanks for catching\n> that, Johannes!)\n\nYeah I'm still confused. Gotta go now, but just some early poking I did.\n\nFirst, there's now docs saying \"starting point\" v.s. \"fork point\", but\nthe tests are still the same, i.e. the ones I can just replace with\neither of `git merge-base [--fork-point] @{u} master`.\n\nIt would really help if the tests actually stressed the parts where this\nis different, including argument-less versions. I.e. just \"git rebase\n--keep-base\".\n\nSpeaking of that, even though you say this is different than\n\"--fork-point\" you may or may not have noticed that if \"argc < 1\" you\n*still* go through the whole fork_point codepath, which will set\n\"options.restrict_revision\" for you. This is part of what I mentioned\nupthread. I.e. with this on top of this patch all your tests still pass:\n\n    diff --git a/builtin/rebase.c b/builtin/rebase.c\n    index 3347dd8975..e38a5044eb 100644\n    --- a/builtin/rebase.c\n    +++ b/builtin/rebase.c\n    @@ -1515,7 +1515,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n     \t\t\t\t\t\t\t\t    NULL);\n     \t\t\tif (!options.upstream_name)\n     \t\t\t\terror_on_missing_default_upstream();\n    -\t\t\tif (fork_point < 0)\n    +\t\t\tif (fork_point < 0 && !keep_base)\n     \t\t\t\tfork_point = 1;\n     \t\t} else {\n     \t\t\toptions.upstream_name = argv[0];\n    @@ -1524,9 +1524,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n     \t\t\tif (!strcmp(options.upstream_name, \"-\"))\n     \t\t\t\toptions.upstream_name = \"@{-1}\";\n     \t\t}\n    -\t\toptions.upstream = peel_committish(options.upstream_name);\n    -\t\tif (!options.upstream)\n    -\t\t\tdie(_(\"invalid upstream '%s'\"), options.upstream_name);\n    +\t\tif (!keep_base) {\n    +\t\t\toptions.upstream = peel_committish(options.upstream_name);\n    +\t\t\tif (!options.upstream)\n    +\t\t\t\t/* I suppose we need to keep this die(...) somewhere still... */\n    +\t\t\t\tdie(_(\"invalid upstream '%s'\"), options.upstream_name);\n    +\t\t}\n     \t\toptions.upstream_arg = options.upstream_name;\n     \t} else {\n     \t\tif (!options.onto_name) {\n    @@ -1564,6 +1566,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n     \t\t}\n     \t\toptions.onto = lookup_commit_or_die(&merge_base,\n     \t\t\t\t\t\t    options.onto_name);\n    +\t\tif (keep_base)\n    +\t\t\toptions.upstream = options.onto;\n     \t} else {\n     \t\toptions.onto = peel_committish(options.onto_name);\n     \t\tif (!options.onto)\n\nBut now because (and bear with me, I don't really get all this) we are\nnot implicitly setting options.restrict_revision later in the\n\"fork_point > 0\" case *and* our \"options.upstream\" SHA-1 is the base you\nfind with --keep-base instead of the @{u} SHA-1 the\ncan_fast_forward(...) \"if\" kicks in, so now:\n\n    $ git status\n    On branch master\n    Your branch and 'origin/master' have diverged,\n    and have 3 and 37 different commits each, respectively.\n\n    # Here your version would always re-rebase it\n    $ ~/g/git/git --exec-path=$PWD rebase --keep-base\n    Current branch master is up to date.\n\nBut \"-i\" still works as intended:\n\n    $ ~/g/git/git --exec-path=$PWD rebase --keep-base -i\n    hint: Waiting for your editor to close the file... Waiting for Emacs...\n    Successfully rebased and updated refs/heads/master.\n    $ git status\n    On branch master\n    Your branch and 'origin/master' have diverged,\n    and have 3 and 37 different commits each, respectively.\n\nI don't know about the *other* use-cases you have in mind, but I can't\nsee a reason for why *that* simple case shouldn't work like that. Why\nwould we redundantly rebase every time in this case, just becase some\nmode of --onto does it? I think if anything we should teach it the same\nlazyness, or maybe that breaks stuff (what stuff?).\n\nPeff discussed some of these variables & their interaction in\nhttps://public-inbox.org/git/20190224101029.GA13438@sigill.intra.peff.net/\n\nI just re-read that, but this whole thing has paged out of my brain in\nthe meantime, and on my quick reading before sending this E-Mail I'm\nstill not sure what all the subtleties involved are.\n\nBut one thing's for sure, I think exhaustive testing of all the edge\ncases involved will make this a lot clearer. I.e.:\n\n * Here upstream has diverged, we rebase (and in the \"noop case?)\n * Here upstream has diverged, *and* rewound (and in the \"noop case?)\n * etc. etc.\n\n> Changes since v1:\n>\n> * Squashed old set into one patch\n> * Fixed indentation style and dangling else\n> * Added more documentation after discussion with Ævar\n>\n> [1]: https://public-inbox.org/git/a1b4b74b9167e279dae4cd8c58fb28d8a714a66a.1553537656.git.gitgitgadget@gmail.com/\n>\n>  Documentation/git-rebase.txt     | 25 ++++++++++++--\n>  builtin/rebase.c                 | 32 ++++++++++++++----\n>  t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++++++++\n>  3 files changed, 105 insertions(+), 9 deletions(-)\n>\n> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n> index 6363d674b7..27be1f48ff 100644\n> --- a/Documentation/git-rebase.txt\n> +++ b/Documentation/git-rebase.txt\n> @@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip\n>  SYNOPSIS\n>  --------\n>  [verse]\n> -'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n> -\t[<upstream> [<branch>]]\n> +'git rebase' [-i | --interactive] [<options>] [--exec <cmd>]\n> +\t[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\n>  'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n>  \t--root [<branch>]\n>  'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch\n> @@ -217,6 +217,19 @@ As a special case, you may use \"A\\...B\" as a shortcut for the\n>  merge base of A and B if there is exactly one merge base. You can\n>  leave out at most one of A and B, in which case it defaults to HEAD.\n>\n> +--keep-base::\n> +\tSet the starting point at which to create the new commits to the\n> +\tmerge base of <upstream> <branch>. Running\n> +\t'git rebase --keep-base <upstream> <branch>' is equivalent to\n> +\trunning 'git rebase --onto <upstream>... <upstream>'.\n> ++\n> +Although both this option and --fork-point find the merge base between\n> +<upstream> and <branch>, this option uses the merge base as the _starting\n> +point_ on which new commits will be created, whereas --fork-point uses\n> +the merge base to determine the _set of commits_ which will be rebased.\n> ++\n> +See also INCOMPATIBLE OPTIONS below.\n> +\n>  <upstream>::\n>  \tUpstream branch to compare against.  May be any valid commit,\n>  \tnot just an existing branch name. Defaults to the configured\n> @@ -364,6 +377,10 @@ ends up being empty, the <upstream> will be used as a fallback.\n>  +\n>  If either <upstream> or --root is given on the command line, then the\n>  default is `--no-fork-point`, otherwise the default is `--fork-point`.\n> ++\n> +If your branch was based on <upstream> but <upstream> was rewound and\n> +your branch contains commits which were dropped, this option can be used\n> +with `--keep-base` in order to drop those commits from your branch.\n>\n>  --ignore-whitespace::\n>  --whitespace=<option>::\n> @@ -539,6 +556,8 @@ In addition, the following pairs of options are incompatible:\n>   * --preserve-merges and --rebase-merges\n>   * --rebase-merges and --strategy\n>   * --rebase-merges and --strategy-option\n> + * --keep-base and --onto\n> + * --keep-base and --root\n>\n>  BEHAVIORAL DIFFERENCES\n>  -----------------------\n> @@ -863,7 +882,7 @@ NOTE: While an \"easy case recovery\" sometimes appears to be successful\n>        --interactive` will be **resurrected**!\n>\n>  The idea is to manually tell 'git rebase' \"where the old 'subsystem'\n> -ended and your 'topic' began\", that is, what the old merge-base\n> +ended and your 'topic' began\", that is, what the old merge base\n>  between them was.  You will have to find a way to name the last commit\n>  of the old 'subsystem', for example:\n>\n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index 77deebc65c..7c14a00460 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -27,8 +27,8 @@\n>  #include \"branch.h\"\n>\n>  static char const * const builtin_rebase_usage[] = {\n> -\tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n> -\t\t\"[<upstream>] [<branch>]\"),\n> +\tN_(\"git rebase [-i] [options] [--exec <cmd>] \"\n> +\t\t\"[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\"),\n>  \tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n>  \t\t\"--root [<branch>]\"),\n>  \tN_(\"git rebase --continue | --abort | --skip | --edit-todo\"),\n> @@ -1018,6 +1018,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \t};\n>  \tconst char *branch_name;\n>  \tint ret, flags, total_argc, in_progress = 0;\n> +\tint keep_base = 0;\n>  \tint ok_to_skip_pre_rebase = 0;\n>  \tstruct strbuf msg = STRBUF_INIT;\n>  \tstruct strbuf revisions = STRBUF_INIT;\n> @@ -1051,6 +1052,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n>  \t\t\t   N_(\"revision\"),\n>  \t\t\t   N_(\"rebase onto given branch instead of upstream\")),\n> +\t\tOPT_BOOL(0, \"keep-base\", &keep_base,\n> +\t\t\t N_(\"use the merge-base of upstream and branch as the current base\")),\n>  \t\tOPT_BOOL(0, \"no-verify\", &ok_to_skip_pre_rebase,\n>  \t\t\t N_(\"allow pre-rebase hook to run\")),\n>  \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n> @@ -1217,6 +1220,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \t\tusage_with_options(builtin_rebase_usage,\n>  \t\t\t\t   builtin_rebase_options);\n>\n> +\tif (keep_base) {\n> +\t\tif (options.onto_name)\n> +\t\t\tdie(_(\"cannot combine '--keep-base' with '--onto'\"));\n> +\t\tif (options.root)\n> +\t\t\tdie(_(\"cannot combine '--keep-base' with '--root'\"));\n> +\t}\n> +\n>  \tif (action != NO_ACTION && !in_progress)\n>  \t\tdie(_(\"No rebase in progress?\"));\n>  \tsetenv(GIT_REFLOG_ACTION_ENVIRONMENT, \"rebase\", 0);\n> @@ -1541,12 +1551,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \t}\n>\n>  \t/* Make sure the branch to rebase onto is valid. */\n> -\tif (!options.onto_name)\n> +\tif (keep_base) {\n> +\t\tstrbuf_reset(&buf);\n> +\t\tstrbuf_addstr(&buf, options.upstream_name);\n> +\t\tstrbuf_addstr(&buf, \"...\");\n> +\t\toptions.onto_name = xstrdup(buf.buf);\n> +\t} else if (!options.onto_name)\n>  \t\toptions.onto_name = options.upstream_name;\n>  \tif (strstr(options.onto_name, \"...\")) {\n> -\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0)\n> -\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n> -\t\t\t    options.onto_name);\n> +\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0) {\n> +\t\t\tif (keep_base)\n> +\t\t\t\tdie(_(\"'%s': need exactly one merge base with branch\"),\n> +\t\t\t\t    options.upstream_name);\n> +\t\t\telse\n> +\t\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n> +\t\t\t\t    options.onto_name);\n> +\t\t}\n>  \t\toptions.onto = lookup_commit_or_die(&merge_base,\n>  \t\t\t\t\t\t    options.onto_name);\n>  \t} else {\n> diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh\n> index ddf2f64853..9c2548423b 100755\n> --- a/t/t3416-rebase-onto-threedots.sh\n> +++ b/t/t3416-rebase-onto-threedots.sh\n> @@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' '\n>  \tgit checkout side &&\n>  \tgit reset --hard K &&\n>\n> +\tset_fake_editor &&\n>  \ttest_must_fail git rebase -i --onto master...side J\n>  '\n>\n> +test_expect_success 'rebase --keep-base --onto incompatible' '\n> +\ttest_must_fail git rebase --keep-base --onto master...\n> +'\n> +\n> +test_expect_success 'rebase --keep-base --root incompatible' '\n> +\ttest_must_fail git rebase --keep-base --root\n> +'\n> +\n> +test_expect_success 'rebase --keep-base master from topic' '\n> +\tgit reset --hard &&\n> +\tgit checkout topic &&\n> +\tgit reset --hard G &&\n> +\n> +\tgit rebase --keep-base master &&\n> +\tgit rev-parse C >base.expect &&\n> +\tgit merge-base master HEAD >base.actual &&\n> +\ttest_cmp base.expect base.actual &&\n> +\n> +\tgit rev-parse HEAD~2 >actual &&\n> +\tgit rev-parse C^0 >expect &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'rebase --keep-base master from side' '\n> +\tgit reset --hard &&\n> +\tgit checkout side &&\n> +\tgit reset --hard K &&\n> +\n> +\ttest_must_fail git rebase --keep-base master\n> +'\n> +\n> +test_expect_success 'rebase -i --keep-base master from topic' '\n> +\tgit reset --hard &&\n> +\tgit checkout topic &&\n> +\tgit reset --hard G &&\n> +\n> +\tset_fake_editor &&\n> +\tEXPECT_COUNT=2 git rebase -i --keep-base master &&\n> +\tgit rev-parse C >base.expect &&\n> +\tgit merge-base master HEAD >base.actual &&\n> +\ttest_cmp base.expect base.actual &&\n> +\n> +\tgit rev-parse HEAD~2 >actual &&\n> +\tgit rev-parse C^0 >expect &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'rebase -i --keep-base master from side' '\n> +\tgit reset --hard &&\n> +\tgit checkout side &&\n> +\tgit reset --hard K &&\n> +\n> +\tset_fake_editor &&\n> +\ttest_must_fail git rebase -i --keep-base master\n> +'\n> +\n>  test_done\n"},{"id":"372709","messageId":"nycvar.QRO.7.76.6.1903291643060.41@tvgsbejvaqbjf.bet","threadId":"50810","inReplyTo":"20190328221745.GA3941@dev-l","subject":"Re: [PATCH v2] rebase: teach rebase --keep-base","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-03-29T15:47:42Z","receivedAt":"2019-03-29T15:47:50Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Denton,\n\nOn Thu, 28 Mar 2019, Denton Liu wrote:\n\n> A common scenario is if a user is working on a topic branch and they\n> wish to make some changes to intermediate commits or autosquash, they\n> would run something such as\n>\n> \tgit rebase -i --onto master... master\n>\n> in order to preserve the merge base. This prevents unnecessary commit\n> churning.\n\nMaybe an example would clarify what you try to do here? Something like:\n\n\tExample: When contributing a patch series to the Git mailing list,\n\tone often starts on top of the current `master`. However, while\n\tdeveloping the patch series, `master` is also developed further,\n\tand it is sometimes not the best idea to keep rebasing on top\n\tof `master`, but to keep the base commit as-is. In such a case,\n\tthe user can call\n\n\t\tgit rebase -i --onto master... master\n\n\tas a shortcut to using the merge base between `master` and the\n\tcurrent branch as base commit.\n\nI wonder, however, whether it makes sense to introduce a shorter, sweeter\nway to do this:\n\n\t\tgit rebase -i master...\n\nI.e. if we detect that the `<upstream>` argument is not a valid ref, that\nit ends with three dots, and that stripping those dots off makes it a\nvalid ref, then we internally convert that to the same as `--onto\nmaster... master`.\n\nWhat do you think?\n\nCiao,\nDscho\n\n> Alternatively, a user wishing to test individual commits in a topic\n> branch without changing anything may run\n>\n> \tgit rebase -x ./test.sh master... master\n>\n> Since rebasing onto the merge base of the branch and the upstream is\n> such a common case, introduce the --keep-base option as a shortcut.\n>\n> This allows us to rewrite the above as\n>\n> \tgit rebase -i --keep-base master\n>\n> and\n>\n> \tgit rebase -x ./test.sh --keep-base master\n>\n> respectively.\n>\n> Add tests to ensure --keep-base works correctly in the normal case and\n> fails when there are multiple merge bases, both in regular and\n> interactive mode. Also, test to make sure conflicting options cause\n> rebase to fail. While we're adding test cases, add a missing\n> set_fake_editor call to 'rebase -i --onto master...side'.\n>\n> While we're documenting the --keep-base option, change an instance of\n> \"merge-base\" to \"merge base\", which is the consistent spelling.\n>\n> Helped-by: Eric Sunshine <sunshine@sunshineco.com>\n> Helped-by: Junio C Hamano <gitster@pobox.com>\n> Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> Signed-off-by: Denton Liu <liu.denton@gmail.com>\n> ---\n>\n> Ævar, I have a feeling that we're still miscommunicating and we don't\n> fully understand each other. I'm putting up v2 to hopefully clear things\n> up a little but I welcome more feedback.\n>\n> This patch now depends \"[PATCH 1/8] tests (rebase): spell out the\n> `--keep-empty` option\" which is the first patch of Johannes's \"Do not\n> use abbreviated options in tests\" patchset[1]. (Thanks for catching\n> that, Johannes!)\n>\n> Changes since v1:\n>\n> * Squashed old set into one patch\n> * Fixed indentation style and dangling else\n> * Added more documentation after discussion with Ævar\n>\n> [1]: https://public-inbox.org/git/a1b4b74b9167e279dae4cd8c58fb28d8a714a66a.1553537656.git.gitgitgadget@gmail.com/\n>\n>  Documentation/git-rebase.txt     | 25 ++++++++++++--\n>  builtin/rebase.c                 | 32 ++++++++++++++----\n>  t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++++++++\n>  3 files changed, 105 insertions(+), 9 deletions(-)\n>\n> diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n> index 6363d674b7..27be1f48ff 100644\n> --- a/Documentation/git-rebase.txt\n> +++ b/Documentation/git-rebase.txt\n> @@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip\n>  SYNOPSIS\n>  --------\n>  [verse]\n> -'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n> -\t[<upstream> [<branch>]]\n> +'git rebase' [-i | --interactive] [<options>] [--exec <cmd>]\n> +\t[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\n>  'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n>  \t--root [<branch>]\n>  'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch\n> @@ -217,6 +217,19 @@ As a special case, you may use \"A\\...B\" as a shortcut for the\n>  merge base of A and B if there is exactly one merge base. You can\n>  leave out at most one of A and B, in which case it defaults to HEAD.\n>\n> +--keep-base::\n> +\tSet the starting point at which to create the new commits to the\n> +\tmerge base of <upstream> <branch>. Running\n> +\t'git rebase --keep-base <upstream> <branch>' is equivalent to\n> +\trunning 'git rebase --onto <upstream>... <upstream>'.\n> ++\n> +Although both this option and --fork-point find the merge base between\n> +<upstream> and <branch>, this option uses the merge base as the _starting\n> +point_ on which new commits will be created, whereas --fork-point uses\n> +the merge base to determine the _set of commits_ which will be rebased.\n> ++\n> +See also INCOMPATIBLE OPTIONS below.\n> +\n>  <upstream>::\n>  \tUpstream branch to compare against.  May be any valid commit,\n>  \tnot just an existing branch name. Defaults to the configured\n> @@ -364,6 +377,10 @@ ends up being empty, the <upstream> will be used as a fallback.\n>  +\n>  If either <upstream> or --root is given on the command line, then the\n>  default is `--no-fork-point`, otherwise the default is `--fork-point`.\n> ++\n> +If your branch was based on <upstream> but <upstream> was rewound and\n> +your branch contains commits which were dropped, this option can be used\n> +with `--keep-base` in order to drop those commits from your branch.\n>\n>  --ignore-whitespace::\n>  --whitespace=<option>::\n> @@ -539,6 +556,8 @@ In addition, the following pairs of options are incompatible:\n>   * --preserve-merges and --rebase-merges\n>   * --rebase-merges and --strategy\n>   * --rebase-merges and --strategy-option\n> + * --keep-base and --onto\n> + * --keep-base and --root\n>\n>  BEHAVIORAL DIFFERENCES\n>  -----------------------\n> @@ -863,7 +882,7 @@ NOTE: While an \"easy case recovery\" sometimes appears to be successful\n>        --interactive` will be **resurrected**!\n>\n>  The idea is to manually tell 'git rebase' \"where the old 'subsystem'\n> -ended and your 'topic' began\", that is, what the old merge-base\n> +ended and your 'topic' began\", that is, what the old merge base\n>  between them was.  You will have to find a way to name the last commit\n>  of the old 'subsystem', for example:\n>\n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index 77deebc65c..7c14a00460 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -27,8 +27,8 @@\n>  #include \"branch.h\"\n>\n>  static char const * const builtin_rebase_usage[] = {\n> -\tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n> -\t\t\"[<upstream>] [<branch>]\"),\n> +\tN_(\"git rebase [-i] [options] [--exec <cmd>] \"\n> +\t\t\"[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\"),\n>  \tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n>  \t\t\"--root [<branch>]\"),\n>  \tN_(\"git rebase --continue | --abort | --skip | --edit-todo\"),\n> @@ -1018,6 +1018,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \t};\n>  \tconst char *branch_name;\n>  \tint ret, flags, total_argc, in_progress = 0;\n> +\tint keep_base = 0;\n>  \tint ok_to_skip_pre_rebase = 0;\n>  \tstruct strbuf msg = STRBUF_INIT;\n>  \tstruct strbuf revisions = STRBUF_INIT;\n> @@ -1051,6 +1052,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n>  \t\t\t   N_(\"revision\"),\n>  \t\t\t   N_(\"rebase onto given branch instead of upstream\")),\n> +\t\tOPT_BOOL(0, \"keep-base\", &keep_base,\n> +\t\t\t N_(\"use the merge-base of upstream and branch as the current base\")),\n>  \t\tOPT_BOOL(0, \"no-verify\", &ok_to_skip_pre_rebase,\n>  \t\t\t N_(\"allow pre-rebase hook to run\")),\n>  \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n> @@ -1217,6 +1220,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \t\tusage_with_options(builtin_rebase_usage,\n>  \t\t\t\t   builtin_rebase_options);\n>\n> +\tif (keep_base) {\n> +\t\tif (options.onto_name)\n> +\t\t\tdie(_(\"cannot combine '--keep-base' with '--onto'\"));\n> +\t\tif (options.root)\n> +\t\t\tdie(_(\"cannot combine '--keep-base' with '--root'\"));\n> +\t}\n> +\n>  \tif (action != NO_ACTION && !in_progress)\n>  \t\tdie(_(\"No rebase in progress?\"));\n>  \tsetenv(GIT_REFLOG_ACTION_ENVIRONMENT, \"rebase\", 0);\n> @@ -1541,12 +1551,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \t}\n>\n>  \t/* Make sure the branch to rebase onto is valid. */\n> -\tif (!options.onto_name)\n> +\tif (keep_base) {\n> +\t\tstrbuf_reset(&buf);\n> +\t\tstrbuf_addstr(&buf, options.upstream_name);\n> +\t\tstrbuf_addstr(&buf, \"...\");\n> +\t\toptions.onto_name = xstrdup(buf.buf);\n> +\t} else if (!options.onto_name)\n>  \t\toptions.onto_name = options.upstream_name;\n>  \tif (strstr(options.onto_name, \"...\")) {\n> -\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0)\n> -\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n> -\t\t\t    options.onto_name);\n> +\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0) {\n> +\t\t\tif (keep_base)\n> +\t\t\t\tdie(_(\"'%s': need exactly one merge base with branch\"),\n> +\t\t\t\t    options.upstream_name);\n> +\t\t\telse\n> +\t\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n> +\t\t\t\t    options.onto_name);\n> +\t\t}\n>  \t\toptions.onto = lookup_commit_or_die(&merge_base,\n>  \t\t\t\t\t\t    options.onto_name);\n>  \t} else {\n> diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh\n> index ddf2f64853..9c2548423b 100755\n> --- a/t/t3416-rebase-onto-threedots.sh\n> +++ b/t/t3416-rebase-onto-threedots.sh\n> @@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' '\n>  \tgit checkout side &&\n>  \tgit reset --hard K &&\n>\n> +\tset_fake_editor &&\n>  \ttest_must_fail git rebase -i --onto master...side J\n>  '\n>\n> +test_expect_success 'rebase --keep-base --onto incompatible' '\n> +\ttest_must_fail git rebase --keep-base --onto master...\n> +'\n> +\n> +test_expect_success 'rebase --keep-base --root incompatible' '\n> +\ttest_must_fail git rebase --keep-base --root\n> +'\n> +\n> +test_expect_success 'rebase --keep-base master from topic' '\n> +\tgit reset --hard &&\n> +\tgit checkout topic &&\n> +\tgit reset --hard G &&\n> +\n> +\tgit rebase --keep-base master &&\n> +\tgit rev-parse C >base.expect &&\n> +\tgit merge-base master HEAD >base.actual &&\n> +\ttest_cmp base.expect base.actual &&\n> +\n> +\tgit rev-parse HEAD~2 >actual &&\n> +\tgit rev-parse C^0 >expect &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'rebase --keep-base master from side' '\n> +\tgit reset --hard &&\n> +\tgit checkout side &&\n> +\tgit reset --hard K &&\n> +\n> +\ttest_must_fail git rebase --keep-base master\n> +'\n> +\n> +test_expect_success 'rebase -i --keep-base master from topic' '\n> +\tgit reset --hard &&\n> +\tgit checkout topic &&\n> +\tgit reset --hard G &&\n> +\n> +\tset_fake_editor &&\n> +\tEXPECT_COUNT=2 git rebase -i --keep-base master &&\n> +\tgit rev-parse C >base.expect &&\n> +\tgit merge-base master HEAD >base.actual &&\n> +\ttest_cmp base.expect base.actual &&\n> +\n> +\tgit rev-parse HEAD~2 >actual &&\n> +\tgit rev-parse C^0 >expect &&\n> +\ttest_cmp expect actual\n> +'\n> +\n> +test_expect_success 'rebase -i --keep-base master from side' '\n> +\tgit reset --hard &&\n> +\tgit checkout side &&\n> +\tgit reset --hard K &&\n> +\n> +\tset_fake_editor &&\n> +\ttest_must_fail git rebase -i --keep-base master\n> +'\n> +\n>  test_done\n> --\n> 2.21.0.695.gaf8658f249\n>\n>\n"},{"id":"372724","messageId":"20190329175250.GA18702@dev-l","threadId":"50810","inReplyTo":"nycvar.QRO.7.76.6.1903291643060.41@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v2] rebase: teach rebase --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-03-29T17:52:50Z","receivedAt":"2019-03-29T17:53:17Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Hi Johannes,\n\nOn Fri, Mar 29, 2019 at 04:47:42PM +0100, Johannes Schindelin wrote:\n> Hi Denton,\n> \n> On Thu, 28 Mar 2019, Denton Liu wrote:\n> \n> > A common scenario is if a user is working on a topic branch and they\n> > wish to make some changes to intermediate commits or autosquash, they\n> > would run something such as\n> >\n> > \tgit rebase -i --onto master... master\n> >\n> > in order to preserve the merge base. This prevents unnecessary commit\n> > churning.\n> \n> Maybe an example would clarify what you try to do here? Something like:\n> \n> \tExample: When contributing a patch series to the Git mailing list,\n> \tone often starts on top of the current `master`. However, while\n> \tdeveloping the patch series, `master` is also developed further,\n> \tand it is sometimes not the best idea to keep rebasing on top\n> \tof `master`, but to keep the base commit as-is. In such a case,\n> \tthe user can call\n> \n> \t\tgit rebase -i --onto master... master\n> \n> \tas a shortcut to using the merge base between `master` and the\n> \tcurrent branch as base commit.\n\nWill do, I'll include an example in the next reroll (probably in the\ndocs too).\n\n> \n> I wonder, however, whether it makes sense to introduce a shorter, sweeter\n> way to do this:\n> \n> \t\tgit rebase -i master...\n> \n> I.e. if we detect that the `<upstream>` argument is not a valid ref, that\n> it ends with three dots, and that stripping those dots off makes it a\n> valid ref, then we internally convert that to the same as `--onto\n> master... master`.\n\nThere's one use-case that this syntax wouldn't cover. Currently, if\n<upstream> isn't specified, rebase automatically uses \n'git merge-base --fork-point @{u} HEAD' as <upstream>. This is\neven true for an invocation of 'git rebase --keep-base'.\n\nAs a result, suppose we have the following graph\n\n\to - f - o - U' origin/master\n\t    \\\n\t     U - A master\n\nwhere U used to be in origin/master (when we forked off) but upstream\nwas rewound to drop it. If we run 'git rebase --keep-base', we'll get\nthe following graph:\n\n\to - f - o - U' origin/master\n\t    \\\n\t     A master\n\ni.e. we'll drop the commit dropped by upstream.\n\nI'm not entirely sure how useful this is, though, so if we decide to\ndrop support for this use-case, then your proposed syntax will be fine.\n\nThanks,\n\nDenton\n\n> \n> What do you think?\n> \n> Ciao,\n> Dscho\n> \n> > Alternatively, a user wishing to test individual commits in a topic\n> > branch without changing anything may run\n> >\n> > \tgit rebase -x ./test.sh master... master\n> >\n> > Since rebasing onto the merge base of the branch and the upstream is\n> > such a common case, introduce the --keep-base option as a shortcut.\n> >\n> > This allows us to rewrite the above as\n> >\n> > \tgit rebase -i --keep-base master\n> >\n> > and\n> >\n> > \tgit rebase -x ./test.sh --keep-base master\n> >\n> > respectively.\n> >\n> > Add tests to ensure --keep-base works correctly in the normal case and\n> > fails when there are multiple merge bases, both in regular and\n> > interactive mode. Also, test to make sure conflicting options cause\n> > rebase to fail. While we're adding test cases, add a missing\n> > set_fake_editor call to 'rebase -i --onto master...side'.\n> >\n> > While we're documenting the --keep-base option, change an instance of\n> > \"merge-base\" to \"merge base\", which is the consistent spelling.\n> >\n> > Helped-by: Eric Sunshine <sunshine@sunshineco.com>\n> > Helped-by: Junio C Hamano <gitster@pobox.com>\n> > Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> > Signed-off-by: Denton Liu <liu.denton@gmail.com>\n> > ---\n> >\n> > Ævar, I have a feeling that we're still miscommunicating and we don't\n> > fully understand each other. I'm putting up v2 to hopefully clear things\n> > up a little but I welcome more feedback.\n> >\n> > This patch now depends \"[PATCH 1/8] tests (rebase): spell out the\n> > `--keep-empty` option\" which is the first patch of Johannes's \"Do not\n> > use abbreviated options in tests\" patchset[1]. (Thanks for catching\n> > that, Johannes!)\n> >\n> > Changes since v1:\n> >\n> > * Squashed old set into one patch\n> > * Fixed indentation style and dangling else\n> > * Added more documentation after discussion with Ævar\n> >\n> > [1]: https://public-inbox.org/git/a1b4b74b9167e279dae4cd8c58fb28d8a714a66a.1553537656.git.gitgitgadget@gmail.com/\n> >\n> >  Documentation/git-rebase.txt     | 25 ++++++++++++--\n> >  builtin/rebase.c                 | 32 ++++++++++++++----\n> >  t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++++++++\n> >  3 files changed, 105 insertions(+), 9 deletions(-)\n> >\n> > diff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\n> > index 6363d674b7..27be1f48ff 100644\n> > --- a/Documentation/git-rebase.txt\n> > +++ b/Documentation/git-rebase.txt\n> > @@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip\n> >  SYNOPSIS\n> >  --------\n> >  [verse]\n> > -'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n> > -\t[<upstream> [<branch>]]\n> > +'git rebase' [-i | --interactive] [<options>] [--exec <cmd>]\n> > +\t[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\n> >  'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n> >  \t--root [<branch>]\n> >  'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch\n> > @@ -217,6 +217,19 @@ As a special case, you may use \"A\\...B\" as a shortcut for the\n> >  merge base of A and B if there is exactly one merge base. You can\n> >  leave out at most one of A and B, in which case it defaults to HEAD.\n> >\n> > +--keep-base::\n> > +\tSet the starting point at which to create the new commits to the\n> > +\tmerge base of <upstream> <branch>. Running\n> > +\t'git rebase --keep-base <upstream> <branch>' is equivalent to\n> > +\trunning 'git rebase --onto <upstream>... <upstream>'.\n> > ++\n> > +Although both this option and --fork-point find the merge base between\n> > +<upstream> and <branch>, this option uses the merge base as the _starting\n> > +point_ on which new commits will be created, whereas --fork-point uses\n> > +the merge base to determine the _set of commits_ which will be rebased.\n> > ++\n> > +See also INCOMPATIBLE OPTIONS below.\n> > +\n> >  <upstream>::\n> >  \tUpstream branch to compare against.  May be any valid commit,\n> >  \tnot just an existing branch name. Defaults to the configured\n> > @@ -364,6 +377,10 @@ ends up being empty, the <upstream> will be used as a fallback.\n> >  +\n> >  If either <upstream> or --root is given on the command line, then the\n> >  default is `--no-fork-point`, otherwise the default is `--fork-point`.\n> > ++\n> > +If your branch was based on <upstream> but <upstream> was rewound and\n> > +your branch contains commits which were dropped, this option can be used\n> > +with `--keep-base` in order to drop those commits from your branch.\n> >\n> >  --ignore-whitespace::\n> >  --whitespace=<option>::\n> > @@ -539,6 +556,8 @@ In addition, the following pairs of options are incompatible:\n> >   * --preserve-merges and --rebase-merges\n> >   * --rebase-merges and --strategy\n> >   * --rebase-merges and --strategy-option\n> > + * --keep-base and --onto\n> > + * --keep-base and --root\n> >\n> >  BEHAVIORAL DIFFERENCES\n> >  -----------------------\n> > @@ -863,7 +882,7 @@ NOTE: While an \"easy case recovery\" sometimes appears to be successful\n> >        --interactive` will be **resurrected**!\n> >\n> >  The idea is to manually tell 'git rebase' \"where the old 'subsystem'\n> > -ended and your 'topic' began\", that is, what the old merge-base\n> > +ended and your 'topic' began\", that is, what the old merge base\n> >  between them was.  You will have to find a way to name the last commit\n> >  of the old 'subsystem', for example:\n> >\n> > diff --git a/builtin/rebase.c b/builtin/rebase.c\n> > index 77deebc65c..7c14a00460 100644\n> > --- a/builtin/rebase.c\n> > +++ b/builtin/rebase.c\n> > @@ -27,8 +27,8 @@\n> >  #include \"branch.h\"\n> >\n> >  static char const * const builtin_rebase_usage[] = {\n> > -\tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n> > -\t\t\"[<upstream>] [<branch>]\"),\n> > +\tN_(\"git rebase [-i] [options] [--exec <cmd>] \"\n> > +\t\t\"[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\"),\n> >  \tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n> >  \t\t\"--root [<branch>]\"),\n> >  \tN_(\"git rebase --continue | --abort | --skip | --edit-todo\"),\n> > @@ -1018,6 +1018,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n> >  \t};\n> >  \tconst char *branch_name;\n> >  \tint ret, flags, total_argc, in_progress = 0;\n> > +\tint keep_base = 0;\n> >  \tint ok_to_skip_pre_rebase = 0;\n> >  \tstruct strbuf msg = STRBUF_INIT;\n> >  \tstruct strbuf revisions = STRBUF_INIT;\n> > @@ -1051,6 +1052,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n> >  \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n> >  \t\t\t   N_(\"revision\"),\n> >  \t\t\t   N_(\"rebase onto given branch instead of upstream\")),\n> > +\t\tOPT_BOOL(0, \"keep-base\", &keep_base,\n> > +\t\t\t N_(\"use the merge-base of upstream and branch as the current base\")),\n> >  \t\tOPT_BOOL(0, \"no-verify\", &ok_to_skip_pre_rebase,\n> >  \t\t\t N_(\"allow pre-rebase hook to run\")),\n> >  \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n> > @@ -1217,6 +1220,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n> >  \t\tusage_with_options(builtin_rebase_usage,\n> >  \t\t\t\t   builtin_rebase_options);\n> >\n> > +\tif (keep_base) {\n> > +\t\tif (options.onto_name)\n> > +\t\t\tdie(_(\"cannot combine '--keep-base' with '--onto'\"));\n> > +\t\tif (options.root)\n> > +\t\t\tdie(_(\"cannot combine '--keep-base' with '--root'\"));\n> > +\t}\n> > +\n> >  \tif (action != NO_ACTION && !in_progress)\n> >  \t\tdie(_(\"No rebase in progress?\"));\n> >  \tsetenv(GIT_REFLOG_ACTION_ENVIRONMENT, \"rebase\", 0);\n> > @@ -1541,12 +1551,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n> >  \t}\n> >\n> >  \t/* Make sure the branch to rebase onto is valid. */\n> > -\tif (!options.onto_name)\n> > +\tif (keep_base) {\n> > +\t\tstrbuf_reset(&buf);\n> > +\t\tstrbuf_addstr(&buf, options.upstream_name);\n> > +\t\tstrbuf_addstr(&buf, \"...\");\n> > +\t\toptions.onto_name = xstrdup(buf.buf);\n> > +\t} else if (!options.onto_name)\n> >  \t\toptions.onto_name = options.upstream_name;\n> >  \tif (strstr(options.onto_name, \"...\")) {\n> > -\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0)\n> > -\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n> > -\t\t\t    options.onto_name);\n> > +\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0) {\n> > +\t\t\tif (keep_base)\n> > +\t\t\t\tdie(_(\"'%s': need exactly one merge base with branch\"),\n> > +\t\t\t\t    options.upstream_name);\n> > +\t\t\telse\n> > +\t\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n> > +\t\t\t\t    options.onto_name);\n> > +\t\t}\n> >  \t\toptions.onto = lookup_commit_or_die(&merge_base,\n> >  \t\t\t\t\t\t    options.onto_name);\n> >  \t} else {\n> > diff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh\n> > index ddf2f64853..9c2548423b 100755\n> > --- a/t/t3416-rebase-onto-threedots.sh\n> > +++ b/t/t3416-rebase-onto-threedots.sh\n> > @@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' '\n> >  \tgit checkout side &&\n> >  \tgit reset --hard K &&\n> >\n> > +\tset_fake_editor &&\n> >  \ttest_must_fail git rebase -i --onto master...side J\n> >  '\n> >\n> > +test_expect_success 'rebase --keep-base --onto incompatible' '\n> > +\ttest_must_fail git rebase --keep-base --onto master...\n> > +'\n> > +\n> > +test_expect_success 'rebase --keep-base --root incompatible' '\n> > +\ttest_must_fail git rebase --keep-base --root\n> > +'\n> > +\n> > +test_expect_success 'rebase --keep-base master from topic' '\n> > +\tgit reset --hard &&\n> > +\tgit checkout topic &&\n> > +\tgit reset --hard G &&\n> > +\n> > +\tgit rebase --keep-base master &&\n> > +\tgit rev-parse C >base.expect &&\n> > +\tgit merge-base master HEAD >base.actual &&\n> > +\ttest_cmp base.expect base.actual &&\n> > +\n> > +\tgit rev-parse HEAD~2 >actual &&\n> > +\tgit rev-parse C^0 >expect &&\n> > +\ttest_cmp expect actual\n> > +'\n> > +\n> > +test_expect_success 'rebase --keep-base master from side' '\n> > +\tgit reset --hard &&\n> > +\tgit checkout side &&\n> > +\tgit reset --hard K &&\n> > +\n> > +\ttest_must_fail git rebase --keep-base master\n> > +'\n> > +\n> > +test_expect_success 'rebase -i --keep-base master from topic' '\n> > +\tgit reset --hard &&\n> > +\tgit checkout topic &&\n> > +\tgit reset --hard G &&\n> > +\n> > +\tset_fake_editor &&\n> > +\tEXPECT_COUNT=2 git rebase -i --keep-base master &&\n> > +\tgit rev-parse C >base.expect &&\n> > +\tgit merge-base master HEAD >base.actual &&\n> > +\ttest_cmp base.expect base.actual &&\n> > +\n> > +\tgit rev-parse HEAD~2 >actual &&\n> > +\tgit rev-parse C^0 >expect &&\n> > +\ttest_cmp expect actual\n> > +'\n> > +\n> > +test_expect_success 'rebase -i --keep-base master from side' '\n> > +\tgit reset --hard &&\n> > +\tgit checkout side &&\n> > +\tgit reset --hard K &&\n> > +\n> > +\tset_fake_editor &&\n> > +\ttest_must_fail git rebase -i --keep-base master\n> > +'\n> > +\n> >  test_done\n> > --\n> > 2.21.0.695.gaf8658f249\n> >\n> >\n\n"},{"id":"372871","messageId":"xmqqftr2vw9u.fsf@gitster-ct.c.googlers.com","threadId":"50810","inReplyTo":"20190325054145.GA1652@archbookpro.localdomain","subject":"Re: [PATCH 1/3] rebase: teach rebase --keep-base","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-04-01T10:45:49Z","receivedAt":"2019-04-01T10:45:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n>> > I never use the \"feature\" myself, but I recall that when \"git\n>> > rebase\" is run on a branch appropriately prepared, you do not even\n>> > have to say <upstream> (iow, you type \"git rebase<RET>\" and rebase\n>> > on top of @{upstream}).  \n>> > \n>> > Can this new \"--keep-base\" feature mesh well with it?  When the\n>> > current branch has forked from origin/master, for example, it would\n>> > be good if\n>> > \n>> > \t$ git rebase -i --same-base\n>> > \n>> > becomes a usable short-hand for\n>> > \n>> > \t$ git rebase -i --same-base origin/master\n>> \n>> By \"--same-base\", I am assuming you mistyped and meant to write\n>> \"--keep-base\"? If that's the case, I can make it a shorthand.\n>\n> Sorry, I misunderstood your question. \"--keep-base\" already has the\n> shorthand case handled by default.\n\nI actually think you understood _my_ question perfectly well, but\nmisremembered what your implementation already supported ;-)\n\nIf the new option works well with \"the branch knows who its upstream\nis\" feature already, so that the user does not have to type\norigin/master in the above example without doing anything special on\nyour implementation's side, that is a great news.\n"},{"id":"372872","messageId":"xmqq8swuvw9o.fsf@gitster-ct.c.googlers.com","threadId":"50810","inReplyTo":"20190325000427.GA9384@archbookpro.localdomain","subject":"Re: [PATCH 0/3] rebase: learn --keep-base","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-04-01T10:45:55Z","receivedAt":"2019-04-01T10:45:59Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n>> I suspect that such a rebase will become no-op without \"-i\".  Am I\n>> mistaken?  I am not sure if \"--keep-base\" is useful without \"-i\".\n>\n> It's useful in the case of \"-x\", although that is a grey area since \"-x\"\n> uses interactive machinery internally. Aside from \"-x\", I can't really\n> think of a situation where we would use \"--keep-base\" without \"-i\".\n\nI consider \"-x\" is a mere sugar-coat around \"-i\".  What I was\ngetting at was if we need some mention of the fact in the\ndocumentation (e.g. \"the option is accepted but fairly useless\nunless you are doing the rebase '-i/-x'\").\n\n"},{"id":"372873","messageId":"xmqq1s2mvw9i.fsf@gitster-ct.c.googlers.com","threadId":"50810","inReplyTo":"nycvar.QRO.7.76.6.1903291643060.41@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v2] rebase: teach rebase --keep-base","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-04-01T10:46:01Z","receivedAt":"2019-04-01T10:46:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> Hi Denton,\n>\n> On Thu, 28 Mar 2019, Denton Liu wrote:\n>\n>> A common scenario is if a user is working on a topic branch and they\n>> wish to make some changes to intermediate commits or autosquash, they\n>> would run something such as\n>>\n>> \tgit rebase -i --onto master... master\n> ...\n> I wonder, however, whether it makes sense to introduce a shorter, sweeter\n> way to do this:\n>\n> \t\tgit rebase -i master...\n\nI agree that this is very tempting, as it mimicks \"git checkout\nmaster...\".  But as Denton responds, it is not quite the same, so...\n"},{"id":"372938","messageId":"cover.1554151449.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"20190328221745.GA3941@dev-l","subject":"[PATCH v3 0/4] rebase: teach rebase --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-01T20:51:54Z","receivedAt":"2019-04-01T20:51:59Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Thanks again for your feedback, Ævar! I think we're both on the same page now.\nHopefully I've addressed all of your high-level concerns with this patchset and\nwe can move into a discussion on implementation detail.\n\nThis patchset now depends \"[PATCH 1/8] tests (rebase): spell out the\n`--keep-empty` option\" which is the first patch of Johannes's \"Do not\nuse abbreviated options in tests\" patchset[1]. (Thanks for catching\nthat, Johannes!)\n\nChanges since v1:\n\n* Squashed old set into one patch\n* Fixed indentation style and dangling else\n* Added more documentation after discussion with Ævar\n\nChanges since v2:\n\n* Add testing for rebase --fork-point behaviour\n* Add testing for rebase fast-forward behaviour\n* Make rebase --onto fast-forward in more cases\n* Update documentation to include use-case\n\n[1]: https://public-inbox.org/git/a1b4b74b9167e279dae4cd8c58fb28d8a714a66a.1553537656.git.gitgitgadget@gmail.com/\n\nDenton Liu (4):\n  t3431: add rebase --fork-point tests\n  t3432: test rebase fast-forward behavior\n  rebase: fast-forward --onto in more cases\n  rebase: teach rebase --keep-base\n\n Documentation/git-rebase.txt     | 30 +++++++++++--\n builtin/rebase.c                 | 72 +++++++++++++++++++++++---------\n t/t3400-rebase.sh                |  2 +-\n t/t3404-rebase-interactive.sh    |  2 +-\n t/t3416-rebase-onto-threedots.sh | 57 +++++++++++++++++++++++++\n t/t3431-rebase-fork-point.sh     | 57 +++++++++++++++++++++++++\n t/t3432-rebase-fast-forward.sh   | 62 +++++++++++++++++++++++++++\n 7 files changed, 258 insertions(+), 24 deletions(-)\n create mode 100755 t/t3431-rebase-fork-point.sh\n create mode 100755 t/t3432-rebase-fast-forward.sh\n\n-- \n2.21.0.695.gaf8658f249\n\n"},{"id":"372939","messageId":"234ac9f024bf4e6b4944fd8f3912cf6367cf828b.1554151449.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1554151449.git.liu.denton@gmail.com","subject":"[PATCH v3 1/4] t3431: add rebase --fork-point tests","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-01T20:51:57Z","receivedAt":"2019-04-01T20:52:01Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Signed-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3431-rebase-fork-point.sh | 53 ++++++++++++++++++++++++++++++++++++\n 1 file changed, 53 insertions(+)\n create mode 100755 t/t3431-rebase-fork-point.sh\n\ndiff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\nnew file mode 100755\nindex 0000000000..8e2483b73e\n--- /dev/null\n+++ b/t/t3431-rebase-fork-point.sh\n@@ -0,0 +1,53 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2019 Denton Liu\n+#\n+\n+test_description='git rebase --fork-point test'\n+\n+. ./test-lib.sh\n+\n+# A---B---D---E       (master)\n+#     \\\n+#      C*---F---G (side)\n+#\n+# C was formerly part of master but is side out\n+#\n+test_expect_success setup '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\ttest_commit C &&\n+\tgit branch -t side &&\n+\tgit reset --hard HEAD^ &&\n+\ttest_commit D &&\n+\ttest_commit E &&\n+\tgit checkout side &&\n+\ttest_commit F &&\n+\ttest_commit G\n+'\n+\n+test_rebase() {\n+\texpected=\"$1\" &&\n+\tshift &&\n+\ttest_expect_success \"git rebase $@\" \"\n+\t\tgit checkout master &&\n+\t\tgit reset --hard E &&\n+\t\tgit checkout side &&\n+\t\tgit reset --hard G &&\n+\t\tgit rebase $@ &&\n+\t\ttest_write_lines $expected >expect &&\n+\t\tgit log --pretty=%s >actual &&\n+\t\ttest_cmp expect actual\n+\t\"\n+}\n+\n+test_rebase 'G F E D B A' ''\n+test_rebase 'G F D B A' '--onto D'\n+test_rebase 'G F C E D B A' '--no-fork-point'\n+test_rebase 'G F C D B A' '--no-fork-point --onto D'\n+test_rebase 'G F E D B A' '--fork-point refs/heads/master'\n+test_rebase 'G F D B A' '--fork-point --onto D refs/heads/master'\n+test_rebase 'G F C E D B A' 'refs/heads/master'\n+test_rebase 'G F C D B A' '--onto D refs/heads/master'\n+\n+test_done\n-- \n2.21.0.695.gaf8658f249\n\n"},{"id":"372940","messageId":"4da2fe9b0429e10821e1cb96b79500c749fc31be.1554151449.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1554151449.git.liu.denton@gmail.com","subject":"[PATCH v3 2/4] t3432: test rebase fast-forward behavior","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-01T20:51:59Z","receivedAt":"2019-04-01T20:52:03Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"When rebase is run on a branch that can be fast-forwarded, this should\nautomatically be done. Create test to ensure this behavior happens.\n\nThere is one case that currently does not pass. In the case where a\nfeature and master have diverged, running \"git rebase master... master\"\ncauses a full rebase to happen even though a fast-forward should happen.\nMark this case as failure so we can fix it later.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3432-rebase-fast-forward.sh | 59 ++++++++++++++++++++++++++++++++++\n 1 file changed, 59 insertions(+)\n create mode 100755 t/t3432-rebase-fast-forward.sh\n\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nnew file mode 100755\nindex 0000000000..3e6362dd9c\n--- /dev/null\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -0,0 +1,59 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2019 Denton Liu\n+#\n+\n+test_description='ensure rebase fast-forwards commits when possible'\n+\n+. ./test-lib.sh\n+\n+test_expect_success setup '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\ttest_commit C &&\n+\ttest_commit D &&\n+\tgit checkout -t -b side\n+'\n+\n+test_rebase_same_head() {\n+\tstatus=\"$1\" &&\n+\tshift &&\n+\ttest_expect_$status \"git rebase $@ with $changes is no-op\" \"\n+\t\toldhead=\\$(git rev-parse HEAD) &&\n+\t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n+\t\tgit rebase $@ &&\n+\t\tnewhead=\\$(git rev-parse HEAD) &&\n+\t\ttest_cmp_rev \\$oldhead \\$newhead\n+\t\"\n+}\n+\n+changes='no changes'\n+test_rebase_same_head success ''\n+test_rebase_same_head success 'master'\n+test_rebase_same_head success '--onto B B'\n+test_rebase_same_head success '--onto B... B'\n+test_rebase_same_head success '--onto master... master'\n+\n+test_expect_success 'add work to side' '\n+\ttest_commit E\n+'\n+\n+changes='our changes'\n+test_rebase_same_head success ''\n+test_rebase_same_head success 'master'\n+test_rebase_same_head success '--onto B B'\n+test_rebase_same_head success '--onto B... B'\n+test_rebase_same_head success '--onto master... master'\n+\n+test_expect_success 'add work to upstream' '\n+\tgit checkout master &&\n+\ttest_commit F &&\n+\tgit checkout side\n+'\n+\n+changes='our and their changes'\n+test_rebase_same_head success '--onto B B'\n+test_rebase_same_head success '--onto B... B'\n+test_rebase_same_head failure '--onto master... master'\n+\n+test_done\n-- \n2.21.0.695.gaf8658f249\n\n"},{"id":"372941","messageId":"281443b2ae2dcf6a5a4d5766d4c006a4d0e22c91.1554151449.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1554151449.git.liu.denton@gmail.com","subject":"[PATCH v3 3/4] rebase: fast-forward --onto in more cases","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-01T20:52:00Z","receivedAt":"2019-04-01T20:52:06Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Before, when we had the following graph,\n\n\tA---B---C (master)\n\t    \\\n\t     D (side)\n\nrunning 'git rebase --onto master... master side' would result in D\nbeing always rebased, no matter what. However, the desired behavior is\nthat rebase should notice that this is fast-forwardable and do that\ninstead.\n\nAdd detection to `can_fast_forward` so that this case can be detected\nand a fast-forward will be performed.\n\nWhile we're at it, remove a trailing whitespace.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n builtin/rebase.c               | 40 +++++++++++++++++++++++-----------\n t/t3400-rebase.sh              |  2 +-\n t/t3404-rebase-interactive.sh  |  2 +-\n t/t3432-rebase-fast-forward.sh |  2 +-\n 4 files changed, 30 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 77deebc65c..7aa6a090d4 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -895,12 +895,12 @@ static int is_linear_history(struct commit *from, struct commit *to)\n \treturn 1;\n }\n \n-static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n-\t\t\t    struct object_id *merge_base)\n+static int can_fast_forward(struct commit *onto, struct commit *upstream,\n+\t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n {\n \tstruct commit *head = lookup_commit(the_repository, head_oid);\n-\tstruct commit_list *merge_bases;\n-\tint res;\n+\tstruct commit_list *merge_bases = NULL;\n+\tint res = 0;\n \n \tif (!head)\n \t\treturn 0;\n@@ -908,12 +908,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n \tmerge_bases = get_merge_bases(onto, head);\n \tif (merge_bases && !merge_bases->next) {\n \t\toidcpy(merge_base, &merge_bases->item->object.oid);\n-\t\tres = oideq(merge_base, &onto->object.oid);\n+\t\tif (!oideq(merge_base, &onto->object.oid))\n+\t\t\tgoto done;\n \t} else {\n \t\toidcpy(merge_base, &null_oid);\n-\t\tres = 0;\n+\t\tgoto done;\n \t}\n+\n+\tif (!upstream)\n+\t\tgoto done;\n+\n \tfree_commit_list(merge_bases);\n+\tmerge_bases = get_merge_bases(upstream, head);\n+\tif (merge_bases && !merge_bases->next) {\n+\t\tif (!oideq(&onto->object.oid, &merge_bases->item->object.oid))\n+\t\t\tgoto done;\n+\t} else\n+\t\tgoto done;\n+\n+\tres = 1;\n+\n+done:\n+\tif (merge_bases)\n+\t\tfree_commit_list(merge_bases);\n \treturn res && is_linear_history(onto, head);\n }\n \n@@ -1682,13 +1699,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \t/*\n \t * Check if we are already based on onto with linear history,\n-\t * but this should be done only when upstream and onto are the same\n-\t * and if this is not an interactive rebase.\n+\t * but this should be done if this is not an interactive rebase.\n \t */\n-\tif (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&\n-\t    !is_interactive(&options) && !options.restrict_revision &&\n-\t    options.upstream &&\n-\t    !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {\n+\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head, &merge_base) &&\n+\t    !is_interactive(&options) && !options.restrict_revision) {\n \t\tint flag;\n \n \t\tif (!(options.flags & REBASE_FORCE)) {\n@@ -1782,7 +1796,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tstrbuf_addf(&msg, \"%s: checkout %s\",\n \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);\n \tif (reset_head(&options.onto->object.oid, \"checkout\", NULL,\n-\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD | \n+\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n \t\t       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n \t\t       NULL, msg.buf))\n \t\tdie(_(\"Could not detach HEAD\"));\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 460d0523be..604d624ff8 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' '\n \t\techo two >>init.t &&\n \t\tgit commit -a -m two &&\n \t\tgit tag two &&\n-\t\ttest_must_fail git rebase --onto init HEAD^ &&\n+\t\ttest_must_fail git rebase -f --onto init HEAD^ &&\n \t\tGIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&\n \t\tgrep \"show.*$(git rev-parse two)\" stderr\n \t)\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex b60b11f9f2..f054186cc7 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int\n \tgit reset --hard &&\n \tgit checkout conflict-branch &&\n \tset_fake_editor &&\n-\ttest_must_fail git rebase --onto HEAD~2 HEAD~ &&\n+\ttest_must_fail git rebase -f --onto HEAD~2 HEAD~ &&\n \ttest_must_fail git rebase --edit-todo &&\n \tgit rebase --abort\n '\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 3e6362dd9c..414b4216d6 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -54,6 +54,6 @@ test_expect_success 'add work to upstream' '\n changes='our and their changes'\n test_rebase_same_head success '--onto B B'\n test_rebase_same_head success '--onto B... B'\n-test_rebase_same_head failure '--onto master... master'\n+test_rebase_same_head success '--onto master... master'\n \n test_done\n-- \n2.21.0.695.gaf8658f249\n\n"},{"id":"372942","messageId":"d0cde21c3049c76bf8f6057d6f92796479bedcff.1554151449.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1554151449.git.liu.denton@gmail.com","subject":"[PATCH v3 4/4] rebase: teach rebase --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-01T20:52:02Z","receivedAt":"2019-04-01T20:52:09Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"A common scenario is if a user is working on a topic branch and they\nwish to make some changes to intermediate commits or autosquash, they\nwould run something such as\n\n\tgit rebase -i --onto master... master\n\nin order to preserve the merge base. This is useful when contributing a\npatch series to the Git mailing list, one often starts on top of the\ncurrent 'master'. However, while developing the patches, 'master' is\nalso developed further and it is sometimes not the bst idea to keep\nrebasing on top of 'master', but to keep the base commit as-is.\n\nAlternatively, a user wishing to test individual commits in a topic\nbranch without changing anything may run\n\n\tgit rebase -x ./test.sh master... master\n\nSince rebasing onto the merge base of the branch and the upstream is\nsuch a common case, introduce the --keep-base option as a shortcut.\n\nThis allows us to rewrite the above as\n\n\tgit rebase -i --keep-base master\n\nand\n\n\tgit rebase -x ./test.sh --keep-base master\n\nrespectively.\n\nAdd tests to ensure --keep-base works correctly in the normal case and\nfails when there are multiple merge bases, both in regular and\ninteractive mode. Also, test to make sure conflicting options cause\nrebase to fail. While we're adding test cases, add a missing\nset_fake_editor call to 'rebase -i --onto master...side'.\n\nWhile we're documenting the --keep-base option, change an instance of\n\"merge-base\" to \"merge base\", which is the consistent spelling.\n\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nHelped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n Documentation/git-rebase.txt     | 30 +++++++++++++++--\n builtin/rebase.c                 | 32 ++++++++++++++----\n t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++++++++\n t/t3431-rebase-fork-point.sh     |  4 +++\n t/t3432-rebase-fast-forward.sh   |  3 ++\n 5 files changed, 117 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 6363d674b7..569ab708d4 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip\n SYNOPSIS\n --------\n [verse]\n-'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n-\t[<upstream> [<branch>]]\n+'git rebase' [-i | --interactive] [<options>] [--exec <cmd>]\n+\t[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\n 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n \t--root [<branch>]\n 'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch\n@@ -217,6 +217,24 @@ As a special case, you may use \"A\\...B\" as a shortcut for the\n merge base of A and B if there is exactly one merge base. You can\n leave out at most one of A and B, in which case it defaults to HEAD.\n \n+--keep-base::\n+\tSet the starting point at which to create the new commits to the\n+\tmerge base of <upstream> <branch>. Running\n+\t'git rebase --keep-base <upstream> <branch>' is equivalent to\n+\trunning 'git rebase --onto <upstream>... <upstream>'.\n++\n+This option is useful in the case where one is developing a feature on\n+top of an upstream branch. While the feature is being worked on, the\n+upstream branch may advance and it may not be the best idea to keep\n+rebasing on top of the upstream but to keep the base commit as-is.\n++\n+Although both this option and --fork-point find the merge base between\n+<upstream> and <branch>, this option uses the merge base as the _starting\n+point_ on which new commits will be created, whereas --fork-point uses\n+the merge base to determine the _set of commits_ which will be rebased.\n++\n+See also INCOMPATIBLE OPTIONS below.\n+\n <upstream>::\n \tUpstream branch to compare against.  May be any valid commit,\n \tnot just an existing branch name. Defaults to the configured\n@@ -364,6 +382,10 @@ ends up being empty, the <upstream> will be used as a fallback.\n +\n If either <upstream> or --root is given on the command line, then the\n default is `--no-fork-point`, otherwise the default is `--fork-point`.\n++\n+If your branch was based on <upstream> but <upstream> was rewound and\n+your branch contains commits which were dropped, this option can be used\n+with `--keep-base` in order to drop those commits from your branch.\n \n --ignore-whitespace::\n --whitespace=<option>::\n@@ -539,6 +561,8 @@ In addition, the following pairs of options are incompatible:\n  * --preserve-merges and --rebase-merges\n  * --rebase-merges and --strategy\n  * --rebase-merges and --strategy-option\n+ * --keep-base and --onto\n+ * --keep-base and --root\n \n BEHAVIORAL DIFFERENCES\n -----------------------\n@@ -863,7 +887,7 @@ NOTE: While an \"easy case recovery\" sometimes appears to be successful\n       --interactive` will be **resurrected**!\n \n The idea is to manually tell 'git rebase' \"where the old 'subsystem'\n-ended and your 'topic' began\", that is, what the old merge-base\n+ended and your 'topic' began\", that is, what the old merge base\n between them was.  You will have to find a way to name the last commit\n of the old 'subsystem', for example:\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 7aa6a090d4..caec1b56e8 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -27,8 +27,8 @@\n #include \"branch.h\"\n \n static char const * const builtin_rebase_usage[] = {\n-\tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n-\t\t\"[<upstream>] [<branch>]\"),\n+\tN_(\"git rebase [-i] [options] [--exec <cmd>] \"\n+\t\t\"[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\"),\n \tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n \t\t\"--root [<branch>]\"),\n \tN_(\"git rebase --continue | --abort | --skip | --edit-todo\"),\n@@ -1035,6 +1035,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t};\n \tconst char *branch_name;\n \tint ret, flags, total_argc, in_progress = 0;\n+\tint keep_base = 0;\n \tint ok_to_skip_pre_rebase = 0;\n \tstruct strbuf msg = STRBUF_INIT;\n \tstruct strbuf revisions = STRBUF_INIT;\n@@ -1068,6 +1069,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"rebase onto given branch instead of upstream\")),\n+\t\tOPT_BOOL(0, \"keep-base\", &keep_base,\n+\t\t\t N_(\"use the merge-base of upstream and branch as the current base\")),\n \t\tOPT_BOOL(0, \"no-verify\", &ok_to_skip_pre_rebase,\n \t\t\t N_(\"allow pre-rebase hook to run\")),\n \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n@@ -1234,6 +1237,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(builtin_rebase_usage,\n \t\t\t\t   builtin_rebase_options);\n \n+\tif (keep_base) {\n+\t\tif (options.onto_name)\n+\t\t\tdie(_(\"cannot combine '--keep-base' with '--onto'\"));\n+\t\tif (options.root)\n+\t\t\tdie(_(\"cannot combine '--keep-base' with '--root'\"));\n+\t}\n+\n \tif (action != NO_ACTION && !in_progress)\n \t\tdie(_(\"No rebase in progress?\"));\n \tsetenv(GIT_REFLOG_ACTION_ENVIRONMENT, \"rebase\", 0);\n@@ -1558,12 +1568,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t}\n \n \t/* Make sure the branch to rebase onto is valid. */\n-\tif (!options.onto_name)\n+\tif (keep_base) {\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addstr(&buf, options.upstream_name);\n+\t\tstrbuf_addstr(&buf, \"...\");\n+\t\toptions.onto_name = xstrdup(buf.buf);\n+\t} else if (!options.onto_name)\n \t\toptions.onto_name = options.upstream_name;\n \tif (strstr(options.onto_name, \"...\")) {\n-\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0)\n-\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n-\t\t\t    options.onto_name);\n+\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0) {\n+\t\t\tif (keep_base)\n+\t\t\t\tdie(_(\"'%s': need exactly one merge base with branch\"),\n+\t\t\t\t    options.upstream_name);\n+\t\t\telse\n+\t\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n+\t\t\t\t    options.onto_name);\n+\t\t}\n \t\toptions.onto = lookup_commit_or_die(&merge_base,\n \t\t\t\t\t\t    options.onto_name);\n \t} else {\ndiff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh\nindex ddf2f64853..9c2548423b 100755\n--- a/t/t3416-rebase-onto-threedots.sh\n+++ b/t/t3416-rebase-onto-threedots.sh\n@@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' '\n \tgit checkout side &&\n \tgit reset --hard K &&\n \n+\tset_fake_editor &&\n \ttest_must_fail git rebase -i --onto master...side J\n '\n \n+test_expect_success 'rebase --keep-base --onto incompatible' '\n+\ttest_must_fail git rebase --keep-base --onto master...\n+'\n+\n+test_expect_success 'rebase --keep-base --root incompatible' '\n+\ttest_must_fail git rebase --keep-base --root\n+'\n+\n+test_expect_success 'rebase --keep-base master from topic' '\n+\tgit reset --hard &&\n+\tgit checkout topic &&\n+\tgit reset --hard G &&\n+\n+\tgit rebase --keep-base master &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base master HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --keep-base master from side' '\n+\tgit reset --hard &&\n+\tgit checkout side &&\n+\tgit reset --hard K &&\n+\n+\ttest_must_fail git rebase --keep-base master\n+'\n+\n+test_expect_success 'rebase -i --keep-base master from topic' '\n+\tgit reset --hard &&\n+\tgit checkout topic &&\n+\tgit reset --hard G &&\n+\n+\tset_fake_editor &&\n+\tEXPECT_COUNT=2 git rebase -i --keep-base master &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base master HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase -i --keep-base master from side' '\n+\tgit reset --hard &&\n+\tgit checkout side &&\n+\tgit reset --hard K &&\n+\n+\tset_fake_editor &&\n+\ttest_must_fail git rebase -i --keep-base master\n+'\n+\n test_done\ndiff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\nindex 8e2483b73e..0311bcbc68 100755\n--- a/t/t3431-rebase-fork-point.sh\n+++ b/t/t3431-rebase-fork-point.sh\n@@ -43,11 +43,15 @@ test_rebase() {\n \n test_rebase 'G F E D B A' ''\n test_rebase 'G F D B A' '--onto D'\n+test_rebase 'G F B A' '--keep-base'\n test_rebase 'G F C E D B A' '--no-fork-point'\n test_rebase 'G F C D B A' '--no-fork-point --onto D'\n+test_rebase 'G F C B A' '--no-fork-point --keep-base'\n test_rebase 'G F E D B A' '--fork-point refs/heads/master'\n test_rebase 'G F D B A' '--fork-point --onto D refs/heads/master'\n+test_rebase 'G F B A' '--fork-point --keep-base refs/heads/master'\n test_rebase 'G F C E D B A' 'refs/heads/master'\n test_rebase 'G F C D B A' '--onto D refs/heads/master'\n+test_rebase 'G F C B A' '--keep-base refs/heads/master'\n \n test_done\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 414b4216d6..8585c21c5c 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -33,6 +33,7 @@ test_rebase_same_head success 'master'\n test_rebase_same_head success '--onto B B'\n test_rebase_same_head success '--onto B... B'\n test_rebase_same_head success '--onto master... master'\n+test_rebase_same_head success '--keep-base master'\n \n test_expect_success 'add work to side' '\n \ttest_commit E\n@@ -44,6 +45,7 @@ test_rebase_same_head success 'master'\n test_rebase_same_head success '--onto B B'\n test_rebase_same_head success '--onto B... B'\n test_rebase_same_head success '--onto master... master'\n+test_rebase_same_head success '--keep-base master'\n \n test_expect_success 'add work to upstream' '\n \tgit checkout master &&\n@@ -55,5 +57,6 @@ changes='our and their changes'\n test_rebase_same_head success '--onto B B'\n test_rebase_same_head success '--onto B... B'\n test_rebase_same_head success '--onto master... master'\n+test_rebase_same_head success '--keep-base master'\n \n test_done\n-- \n2.21.0.695.gaf8658f249\n\n"},{"id":"372960","messageId":"xmqqsgv1tcyi.fsf@gitster-ct.c.googlers.com","threadId":"50810","inReplyTo":"281443b2ae2dcf6a5a4d5766d4c006a4d0e22c91.1554151449.git.liu.denton@gmail.com","subject":"Re: [PATCH v3 3/4] rebase: fast-forward --onto in more cases","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-04-02T01:25:57Z","receivedAt":"2019-04-02T01:26:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n> Before, when we had the following graph,\n>\n> \tA---B---C (master)\n> \t    \\\n> \t     D (side)\n>\n> running 'git rebase --onto master... master side' would result in D\n> being always rebased, no matter what. However, the desired behavior is\n> that rebase should notice that this is fast-forwardable and do that\n> instead.\n>\n> Add detection to `can_fast_forward` so that this case can be detected\n> and a fast-forward will be performed.\n\nOK.  As long as the 'onto' commit is a strict ancestor of the side\nbranch being rebased, the 'upstream' that is used only to determine\nwhich commits are on the side branch (essentially those that are not\nreachable from upstream but that are from the side branch) should\nnot count in the equation to decide if we fast-forward or not.  That\nmakes sense.\n\n> ---\n>  builtin/rebase.c               | 40 +++++++++++++++++++++++-----------\n>  t/t3400-rebase.sh              |  2 +-\n>  t/t3404-rebase-interactive.sh  |  2 +-\n>  t/t3432-rebase-fast-forward.sh |  2 +-\n>  4 files changed, 30 insertions(+), 16 deletions(-)\n>\n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index 77deebc65c..7aa6a090d4 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -895,12 +895,12 @@ static int is_linear_history(struct commit *from, struct commit *to)\n>  \treturn 1;\n>  }\n>  \n> -static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n> -\t\t\t    struct object_id *merge_base)\n> +static int can_fast_forward(struct commit *onto, struct commit *upstream,\n> +\t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n>  {\n>  \tstruct commit *head = lookup_commit(the_repository, head_oid);\n> -\tstruct commit_list *merge_bases;\n> -\tint res;\n> +\tstruct commit_list *merge_bases = NULL;\n> +\tint res = 0;\n>  \n>  \tif (!head)\n>  \t\treturn 0;\n> @@ -908,12 +908,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n>  \tmerge_bases = get_merge_bases(onto, head);\n>  \tif (merge_bases && !merge_bases->next) {\n>  \t\toidcpy(merge_base, &merge_bases->item->object.oid);\n> -\t\tres = oideq(merge_base, &onto->object.oid);\n> +\t\tif (!oideq(merge_base, &onto->object.oid))\n> +\t\t\tgoto done;\n>  \t} else {\n>  \t\toidcpy(merge_base, &null_oid);\n> -\t\tres = 0;\n> +\t\tgoto done;\n>  \t}\n\nThe above does not change any existing logic, but purely simplifies\nthe code.  In your picture in the log message\n\n    A---B---C (master)\n         \\\n          D (side)\n\nwhere \"rebase --onto master... master side\" is run, \"onto\" in this\nfunction is B, and \"head\" is D.  There is a single merge base B\n(i.e. merge_bases->next == NULL), so we jump to the label \"done:\"\nwith res==0.\n\nSo why does the remainder of the function need to be changed?\n\n> +\tif (!upstream)\n> +\t\tgoto done;\n> +\n>  \tfree_commit_list(merge_bases);\n> +\tmerge_bases = get_merge_bases(upstream, head);\n> +\tif (merge_bases && !merge_bases->next) {\n> +\t\tif (!oideq(&onto->object.oid, &merge_bases->item->object.oid))\n> +\t\t\tgoto done;\n> +\t} else\n> +\t\tgoto done;\n\nThis computes the same between C and D (instead of B and D).  Why is\nthis needed?\n\nStepping back a bit, I understand that your argument we saw in the\nlog message was that we only need to know if the commit we are\ntransplanting the history on (i.e. \"onto\") already is an ancestor of\nthe history being transplanted (i.e. \"onto..head\"), and it does not\nmatter what upstream is.  Am I mistaken?  Why does this function now\nneed to know what 'upstream' is?\n\nPuzzled....\n\n> @@ -1682,13 +1699,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \n>  \t/*\n>  \t * Check if we are already based on onto with linear history,\n> -\t * but this should be done only when upstream and onto are the same\n> -\t * and if this is not an interactive rebase.\n> +\t * but this should be done if this is not an interactive rebase.\n>  \t */\n> -\tif (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&\n> -\t    !is_interactive(&options) && !options.restrict_revision &&\n> -\t    options.upstream &&\n> -\t    !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {\n> +\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head, &merge_base) &&\n> +\t    !is_interactive(&options) && !options.restrict_revision) {\n\nDitto.  Shouldn't it be the matter of\n\n\tif (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&\n\t    !is_interactive(&options) && !options.restrict_revision) {\n\ni.e. the original without paying any attention to the options.upstream?\n\n> diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\n> index 460d0523be..604d624ff8 100755\n> --- a/t/t3400-rebase.sh\n> +++ b/t/t3400-rebase.sh\n> @@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' '\n>  \t\techo two >>init.t &&\n>  \t\tgit commit -a -m two &&\n>  \t\tgit tag two &&\n> -\t\ttest_must_fail git rebase --onto init HEAD^ &&\n> +\t\ttest_must_fail git rebase -f --onto init HEAD^ &&\n>  \t\tGIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&\n>  \t\tgrep \"show.*$(git rev-parse two)\" stderr\n>  \t)\n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index b60b11f9f2..f054186cc7 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int\n>  \tgit reset --hard &&\n>  \tgit checkout conflict-branch &&\n>  \tset_fake_editor &&\n> -\ttest_must_fail git rebase --onto HEAD~2 HEAD~ &&\n> +\ttest_must_fail git rebase -f --onto HEAD~2 HEAD~ &&\n>  \ttest_must_fail git rebase --edit-todo &&\n>  \tgit rebase --abort\n>  '\n\nThe above two changes to 3400 and 3404 are not explained.\n\n\n> diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\n> index 3e6362dd9c..414b4216d6 100755\n> --- a/t/t3432-rebase-fast-forward.sh\n> +++ b/t/t3432-rebase-fast-forward.sh\n> @@ -54,6 +54,6 @@ test_expect_success 'add work to upstream' '\n>  changes='our and their changes'\n>  test_rebase_same_head success '--onto B B'\n>  test_rebase_same_head success '--onto B... B'\n> -test_rebase_same_head failure '--onto master... master'\n> +test_rebase_same_head success '--onto master... master'\n\nThis shows what the change wanted to achieve, which makes sense to\nme.\n\n"},{"id":"372962","messageId":"xmqqo95ptbxj.fsf@gitster-ct.c.googlers.com","threadId":"50810","inReplyTo":"xmqqsgv1tcyi.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 3/4] rebase: fast-forward --onto in more cases","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-04-02T01:48:08Z","receivedAt":"2019-04-02T01:48:13Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Denton Liu <liu.denton@gmail.com> writes:\n>\n>> Before, when we had the following graph,\n>>\n>> \tA---B---C (master)\n>> \t    \\\n>> \t     D (side)\n>>\n>> running 'git rebase --onto master... master side' would result in D\n>> being always rebased, no matter what. However, the desired behavior is\n>> that rebase should notice that this is fast-forwardable and do that\n>> instead.\n>>\n>> Add detection to `can_fast_forward` so that this case can be detected\n>> and a fast-forward will be performed.\n>\n> OK.  As long as the 'onto' commit is a strict ancestor of the side\n> branch being rebased, the 'upstream' that is used only to determine\n> which commits are on the side branch (essentially those that are not\n> reachable from upstream but that are from the side branch) should\n> not count in the equation to decide if we fast-forward or not.  That\n> makes sense.\n>\n>> ---\n>>  builtin/rebase.c               | 40 +++++++++++++++++++++++-----------\n>>  t/t3400-rebase.sh              |  2 +-\n>>  t/t3404-rebase-interactive.sh  |  2 +-\n>>  t/t3432-rebase-fast-forward.sh |  2 +-\n>>  4 files changed, 30 insertions(+), 16 deletions(-)\n>>\n>> diff --git a/builtin/rebase.c b/builtin/rebase.c\n>> index 77deebc65c..7aa6a090d4 100644\n>> --- a/builtin/rebase.c\n>> +++ b/builtin/rebase.c\n>> @@ -895,12 +895,12 @@ static int is_linear_history(struct commit *from, struct commit *to)\n>>  \treturn 1;\n>>  }\n>>  \n>> -static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n>> -\t\t\t    struct object_id *merge_base)\n>> +static int can_fast_forward(struct commit *onto, struct commit *upstream,\n>> +\t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n>>  {\n>>  \tstruct commit *head = lookup_commit(the_repository, head_oid);\n>> -\tstruct commit_list *merge_bases;\n>> -\tint res;\n>> +\tstruct commit_list *merge_bases = NULL;\n>> +\tint res = 0;\n>>  \n>>  \tif (!head)\n>>  \t\treturn 0;\n>> @@ -908,12 +908,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n>>  \tmerge_bases = get_merge_bases(onto, head);\n>>  \tif (merge_bases && !merge_bases->next) {\n>>  \t\toidcpy(merge_base, &merge_bases->item->object.oid);\n>> -\t\tres = oideq(merge_base, &onto->object.oid);\n>> +\t\tif (!oideq(merge_base, &onto->object.oid))\n>> +\t\t\tgoto done;\n>>  \t} else {\n>>  \t\toidcpy(merge_base, &null_oid);\n>> -\t\tres = 0;\n>> +\t\tgoto done;\n>>  \t}\n>\n> The above does not change any existing logic, but purely simplifies\n> the code.  In your picture in the log message\n>\n>     A---B---C (master)\n>          \\\n>           D (side)\n>\n> where \"rebase --onto master... master side\" is run, \"onto\" in this\n> function is B, and \"head\" is D.  There is a single merge base B\n> (i.e. merge_bases->next == NULL), so we jump to the label \"done:\"\n> with res==0.\n>\n> So why does the remainder of the function need to be changed?\n>\n>> +\tif (!upstream)\n>> +\t\tgoto done;\n>> +\n>>  \tfree_commit_list(merge_bases);\n>> +\tmerge_bases = get_merge_bases(upstream, head);\n>> +\tif (merge_bases && !merge_bases->next) {\n>> +\t\tif (!oideq(&onto->object.oid, &merge_bases->item->object.oid))\n>> +\t\t\tgoto done;\n>> +\t} else\n>> +\t\tgoto done;\n>\n> This computes the same between C and D (instead of B and D).  Why is\n> this needed?\n>\n> Stepping back a bit, I understand that your argument we saw in the\n> log message was that we only need to know if the commit we are\n> transplanting the history on (i.e. \"onto\") already is an ancestor of\n> the history being transplanted (i.e. \"onto..head\"), and it does not\n> matter what upstream is.  Am I mistaken?  Why does this function now\n> need to know what 'upstream' is?\n>\n> Puzzled....\n\nSo I replaced the part for builtin/rebase.c from your patch with the\nabove suggestion (attached) and the result seems to pass the three\ntests you touched.  I am still puzzled.\n\nThanks.\n\n builtin/rebase.c               | 7 ++-----\n t/t3400-rebase.sh              | 2 +-\n t/t3404-rebase-interactive.sh  | 2 +-\n t/t3432-rebase-fast-forward.sh | 2 +-\n 4 files changed, 5 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 77deebc65c..fe61c2a899 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1682,13 +1682,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \t/*\n \t * Check if we are already based on onto with linear history,\n-\t * but this should be done only when upstream and onto are the same\n-\t * and if this is not an interactive rebase.\n+\t * but this should be done if this is not an interactive rebase.\n \t */\n \tif (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&\n-\t    !is_interactive(&options) && !options.restrict_revision &&\n-\t    options.upstream &&\n-\t    !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {\n+\t    !is_interactive(&options) && !options.restrict_revision) {\n \t\tint flag;\n \n \t\tif (!(options.flags & REBASE_FORCE)) {\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 460d0523be..604d624ff8 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' '\n \t\techo two >>init.t &&\n \t\tgit commit -a -m two &&\n \t\tgit tag two &&\n-\t\ttest_must_fail git rebase --onto init HEAD^ &&\n+\t\ttest_must_fail git rebase -f --onto init HEAD^ &&\n \t\tGIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&\n \t\tgrep \"show.*$(git rev-parse two)\" stderr\n \t)\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex b60b11f9f2..f054186cc7 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int\n \tgit reset --hard &&\n \tgit checkout conflict-branch &&\n \tset_fake_editor &&\n-\ttest_must_fail git rebase --onto HEAD~2 HEAD~ &&\n+\ttest_must_fail git rebase -f --onto HEAD~2 HEAD~ &&\n \ttest_must_fail git rebase --edit-todo &&\n \tgit rebase --abort\n '\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 3e6362dd9c..414b4216d6 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -54,6 +54,6 @@ test_expect_success 'add work to upstream' '\n changes='our and their changes'\n test_rebase_same_head success '--onto B B'\n test_rebase_same_head success '--onto B... B'\n-test_rebase_same_head failure '--onto master... master'\n+test_rebase_same_head success '--onto master... master'\n \n test_done\n"},{"id":"372964","messageId":"20190402044419.GA7106@archbookpro.localdomain","threadId":"50810","inReplyTo":"xmqqo95ptbxj.fsf@gitster-ct.c.googlers.com","subject":"Re: [PATCH v3 3/4] rebase: fast-forward --onto in more cases","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-02T04:44:19Z","receivedAt":"2019-04-02T04:44:25Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Hi Junio,\n\nOn Tue, Apr 02, 2019 at 10:48:08AM +0900, Junio C Hamano wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n> > Denton Liu <liu.denton@gmail.com> writes:\n> >\n> >> Before, when we had the following graph,\n> >>\n> >> \tA---B---C (master)\n> >> \t    \\\n> >> \t     D (side)\n> >>\n> >> running 'git rebase --onto master... master side' would result in D\n> >> being always rebased, no matter what. However, the desired behavior is\n> >> that rebase should notice that this is fast-forwardable and do that\n> >> instead.\n> >>\n> >> Add detection to `can_fast_forward` so that this case can be detected\n> >> and a fast-forward will be performed.\n> >\n> > OK.  As long as the 'onto' commit is a strict ancestor of the side\n> > branch being rebased, the 'upstream' that is used only to determine\n> > which commits are on the side branch (essentially those that are not\n> > reachable from upstream but that are from the side branch) should\n> > not count in the equation to decide if we fast-forward or not.  That\n> > makes sense.\n> >\n> >> ---\n> >>  builtin/rebase.c               | 40 +++++++++++++++++++++++-----------\n> >>  t/t3400-rebase.sh              |  2 +-\n> >>  t/t3404-rebase-interactive.sh  |  2 +-\n> >>  t/t3432-rebase-fast-forward.sh |  2 +-\n> >>  4 files changed, 30 insertions(+), 16 deletions(-)\n> >>\n> >> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> >> index 77deebc65c..7aa6a090d4 100644\n> >> --- a/builtin/rebase.c\n> >> +++ b/builtin/rebase.c\n> >> @@ -895,12 +895,12 @@ static int is_linear_history(struct commit *from, struct commit *to)\n> >>  \treturn 1;\n> >>  }\n> >>  \n> >> -static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n> >> -\t\t\t    struct object_id *merge_base)\n> >> +static int can_fast_forward(struct commit *onto, struct commit *upstream,\n> >> +\t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n> >>  {\n> >>  \tstruct commit *head = lookup_commit(the_repository, head_oid);\n> >> -\tstruct commit_list *merge_bases;\n> >> -\tint res;\n> >> +\tstruct commit_list *merge_bases = NULL;\n> >> +\tint res = 0;\n> >>  \n> >>  \tif (!head)\n> >>  \t\treturn 0;\n> >> @@ -908,12 +908,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n> >>  \tmerge_bases = get_merge_bases(onto, head);\n> >>  \tif (merge_bases && !merge_bases->next) {\n> >>  \t\toidcpy(merge_base, &merge_bases->item->object.oid);\n> >> -\t\tres = oideq(merge_base, &onto->object.oid);\n> >> +\t\tif (!oideq(merge_base, &onto->object.oid))\n> >> +\t\t\tgoto done;\n> >>  \t} else {\n> >>  \t\toidcpy(merge_base, &null_oid);\n> >> -\t\tres = 0;\n> >> +\t\tgoto done;\n> >>  \t}\n> >\n> > The above does not change any existing logic, but purely simplifies\n> > the code.  In your picture in the log message\n> >\n> >     A---B---C (master)\n> >          \\\n> >           D (side)\n> >\n> > where \"rebase --onto master... master side\" is run, \"onto\" in this\n> > function is B, and \"head\" is D.  There is a single merge base B\n> > (i.e. merge_bases->next == NULL), so we jump to the label \"done:\"\n> > with res==0.\n\nI don't think this is correct. There is a single merge base so we\ncompare 'merge_base' and 'onto'. They are the same so we don't jump to\n'done', instead we keep going.\n\n> >\n> > So why does the remainder of the function need to be changed?\n> >\n> >> +\tif (!upstream)\n> >> +\t\tgoto done;\n> >> +\n> >>  \tfree_commit_list(merge_bases);\n> >> +\tmerge_bases = get_merge_bases(upstream, head);\n> >> +\tif (merge_bases && !merge_bases->next) {\n> >> +\t\tif (!oideq(&onto->object.oid, &merge_bases->item->object.oid))\n> >> +\t\t\tgoto done;\n> >> +\t} else\n> >> +\t\tgoto done;\n> >\n> > This computes the same between C and D (instead of B and D).  Why is\n> > this needed?\n\nWithout this logic, t3416 would fail case 2 and 3. It would only see the\n'head' and the 'onto' and therefore, it would incorrectly return 1,\nindicating that it is fast-forwardable.\n\nThe graph for t3416 is as follows:\n\n\t\t    F---G topic\n\t\t   /\n\t  A---B---C---D---E master\n\t       \\   \\ /\n\t\t\\   x\n\t\t \\ / \\\n\t\t  H---I---J---K side\n\nAnd the command that fails is\n\n    git rebase --onto master...topic F # called from topic\n\nWithout the additional logic, merge base is C and there is only one\nmerge base. Also, head is G. Finally, onto is also C. We enter the outer\nif becase there is one merge base then we see that onto == merge_base.\nThus, the function would falsely conclude that this is fast-forwardable.\nThis gives us ABCFG when we're expecting ABCG.\n\nThe additional logic detects if we're not rebasing the entire branch but\nonly part of the branch. In this case, it detects that F is excluded and\nas a result, we can't fast-forward.\n\n> >\n> > Stepping back a bit, I understand that your argument we saw in the\n> > log message was that we only need to know if the commit we are\n> > transplanting the history on (i.e. \"onto\") already is an ancestor of\n> > the history being transplanted (i.e. \"onto..head\"), and it does not\n> > matter what upstream is.  Am I mistaken?  Why does this function now\n> > need to know what 'upstream' is?\n\nAs stated above, the function needs to know what upstream is because it\nuses it to determine if the set of commits being rebased is equal to\nmaster..topic. If it's not that set, then it's not fast-forwardable.\n\n> >\n> > Puzzled....\n\n...I guess this is a sign that I need to clarify my commit message. I'll\nput what I said above into the commit message in my next reroll.\n\nThanks,\n\nDenton\n\n> \n> So I replaced the part for builtin/rebase.c from your patch with the\n> above suggestion (attached) and the result seems to pass the three\n> tests you touched.  I am still puzzled.\n> \n> Thanks.\n> \n>  builtin/rebase.c               | 7 ++-----\n>  t/t3400-rebase.sh              | 2 +-\n>  t/t3404-rebase-interactive.sh  | 2 +-\n>  t/t3432-rebase-fast-forward.sh | 2 +-\n>  4 files changed, 5 insertions(+), 8 deletions(-)\n> \n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index 77deebc65c..fe61c2a899 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -1682,13 +1682,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \n>  \t/*\n>  \t * Check if we are already based on onto with linear history,\n> -\t * but this should be done only when upstream and onto are the same\n> -\t * and if this is not an interactive rebase.\n> +\t * but this should be done if this is not an interactive rebase.\n>  \t */\n>  \tif (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&\n> -\t    !is_interactive(&options) && !options.restrict_revision &&\n> -\t    options.upstream &&\n> -\t    !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {\n> +\t    !is_interactive(&options) && !options.restrict_revision) {\n>  \t\tint flag;\n>  \n>  \t\tif (!(options.flags & REBASE_FORCE)) {\n> diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\n> index 460d0523be..604d624ff8 100755\n> --- a/t/t3400-rebase.sh\n> +++ b/t/t3400-rebase.sh\n> @@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' '\n>  \t\techo two >>init.t &&\n>  \t\tgit commit -a -m two &&\n>  \t\tgit tag two &&\n> -\t\ttest_must_fail git rebase --onto init HEAD^ &&\n> +\t\ttest_must_fail git rebase -f --onto init HEAD^ &&\n>  \t\tGIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&\n>  \t\tgrep \"show.*$(git rev-parse two)\" stderr\n>  \t)\n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index b60b11f9f2..f054186cc7 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int\n>  \tgit reset --hard &&\n>  \tgit checkout conflict-branch &&\n>  \tset_fake_editor &&\n> -\ttest_must_fail git rebase --onto HEAD~2 HEAD~ &&\n> +\ttest_must_fail git rebase -f --onto HEAD~2 HEAD~ &&\n>  \ttest_must_fail git rebase --edit-todo &&\n>  \tgit rebase --abort\n>  '\n> diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\n> index 3e6362dd9c..414b4216d6 100755\n> --- a/t/t3432-rebase-fast-forward.sh\n> +++ b/t/t3432-rebase-fast-forward.sh\n> @@ -54,6 +54,6 @@ test_expect_success 'add work to upstream' '\n>  changes='our and their changes'\n>  test_rebase_same_head success '--onto B B'\n>  test_rebase_same_head success '--onto B... B'\n> -test_rebase_same_head failure '--onto master... master'\n> +test_rebase_same_head success '--onto master... master'\n>  \n>  test_done\n"},{"id":"373131","messageId":"20190404202814.GA1290@dev-l","threadId":"50810","inReplyTo":"234ac9f024bf4e6b4944fd8f3912cf6367cf828b.1554151449.git.liu.denton@gmail.com","subject":"Re: [PATCH v3 1/4] t3431: add rebase --fork-point tests","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-04T20:28:14Z","receivedAt":"2019-04-04T20:28:19Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"On Mon, Apr 01, 2019 at 01:51:57PM -0700, Denton Liu wrote:\n> Signed-off-by: Denton Liu <liu.denton@gmail.com>\n> ---\n>  t/t3431-rebase-fork-point.sh | 53 ++++++++++++++++++++++++++++++++++++\n>  1 file changed, 53 insertions(+)\n>  create mode 100755 t/t3431-rebase-fork-point.sh\n> \n> diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\n> new file mode 100755\n> index 0000000000..8e2483b73e\n> --- /dev/null\n> +++ b/t/t3431-rebase-fork-point.sh\n> @@ -0,0 +1,53 @@\n> +#!/bin/sh\n> +#\n> +# Copyright (c) 2019 Denton Liu\n> +#\n> +\n> +test_description='git rebase --fork-point test'\n> +\n> +. ./test-lib.sh\n> +\n> +# A---B---D---E       (master)\n> +#     \\\n> +#      C*---F---G (side)\n> +#\n> +# C was formerly part of master but is side out\n\nSorry, small typo. We should probably fix this before it gets merged\ninto next.\n\nThis should read \"C was formerly part of master but master was rewound\nto remove C\"\n\nThanks,\n\nDenton\n\n> +#\n> +test_expect_success setup '\n> +\ttest_commit A &&\n> +\ttest_commit B &&\n> +\ttest_commit C &&\n> +\tgit branch -t side &&\n> +\tgit reset --hard HEAD^ &&\n> +\ttest_commit D &&\n> +\ttest_commit E &&\n> +\tgit checkout side &&\n> +\ttest_commit F &&\n> +\ttest_commit G\n> +'\n> +\n> +test_rebase() {\n> +\texpected=\"$1\" &&\n> +\tshift &&\n> +\ttest_expect_success \"git rebase $@\" \"\n> +\t\tgit checkout master &&\n> +\t\tgit reset --hard E &&\n> +\t\tgit checkout side &&\n> +\t\tgit reset --hard G &&\n> +\t\tgit rebase $@ &&\n> +\t\ttest_write_lines $expected >expect &&\n> +\t\tgit log --pretty=%s >actual &&\n> +\t\ttest_cmp expect actual\n> +\t\"\n> +}\n> +\n> +test_rebase 'G F E D B A' ''\n> +test_rebase 'G F D B A' '--onto D'\n> +test_rebase 'G F C E D B A' '--no-fork-point'\n> +test_rebase 'G F C D B A' '--no-fork-point --onto D'\n> +test_rebase 'G F E D B A' '--fork-point refs/heads/master'\n> +test_rebase 'G F D B A' '--fork-point --onto D refs/heads/master'\n> +test_rebase 'G F C E D B A' 'refs/heads/master'\n> +test_rebase 'G F C D B A' '--onto D refs/heads/master'\n> +\n> +test_done\n> -- \n> 2.21.0.695.gaf8658f249\n> \n"},{"id":"373194","messageId":"20190405111513.GU32732@szeder.dev","threadId":"50810","inReplyTo":"234ac9f024bf4e6b4944fd8f3912cf6367cf828b.1554151449.git.liu.denton@gmail.com","subject":"Re: [PATCH v3 1/4] t3431: add rebase --fork-point tests","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2019-04-05T11:15:13Z","receivedAt":"2019-04-05T11:15:19Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Mon, Apr 01, 2019 at 01:51:57PM -0700, Denton Liu wrote:\n> Signed-off-by: Denton Liu <liu.denton@gmail.com>\n> ---\n>  t/t3431-rebase-fork-point.sh | 53 ++++++++++++++++++++++++++++++++++++\n>  1 file changed, 53 insertions(+)\n>  create mode 100755 t/t3431-rebase-fork-point.sh\n> \n> diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\n> new file mode 100755\n> index 0000000000..8e2483b73e\n> --- /dev/null\n> +++ b/t/t3431-rebase-fork-point.sh\n> @@ -0,0 +1,53 @@\n> +#!/bin/sh\n> +#\n> +# Copyright (c) 2019 Denton Liu\n> +#\n> +\n> +test_description='git rebase --fork-point test'\n> +\n> +. ./test-lib.sh\n> +\n> +# A---B---D---E       (master)\n> +#     \\\n> +#      C*---F---G (side)\n> +#\n> +# C was formerly part of master but is side out\n> +#\n> +test_expect_success setup '\n> +\ttest_commit A &&\n> +\ttest_commit B &&\n> +\ttest_commit C &&\n> +\tgit branch -t side &&\n> +\tgit reset --hard HEAD^ &&\n> +\ttest_commit D &&\n> +\ttest_commit E &&\n> +\tgit checkout side &&\n> +\ttest_commit F &&\n> +\ttest_commit G\n> +'\n> +\n> +test_rebase() {\n> +\texpected=\"$1\" &&\n> +\tshift &&\n> +\ttest_expect_success \"git rebase $@\" \"\n> +\t\tgit checkout master &&\n> +\t\tgit reset --hard E &&\n> +\t\tgit checkout side &&\n> +\t\tgit reset --hard G &&\n> +\t\tgit rebase $@ &&\n> +\t\ttest_write_lines $expected >expect &&\n> +\t\tgit log --pretty=%s >actual &&\n> +\t\ttest_cmp expect actual\n> +\t\"\n> +}\n> +\n> +test_rebase 'G F E D B A' ''\n\nIt appears that this last empty argument triggers some bug in Bash\nv4.2 and older (and on macOS such an old Bash is the default /bin/sh),\nas it turns that empty argument into something else, which in turn\nfails the test with:\n\n  <...>\n  ++ git rebase $'\\177'\n  fatal: invalid upstream '?'\n  error: last command exited with $?=128\n  not ok 2 - git rebase\n\nhttps://travis-ci.org/git/git/jobs/516070862#L2276\n\nOmitting that empty argument avoids this issue, and the test still\nchecks what it was supposed to.\n\n> +test_rebase 'G F D B A' '--onto D'\n> +test_rebase 'G F C E D B A' '--no-fork-point'\n> +test_rebase 'G F C D B A' '--no-fork-point --onto D'\n> +test_rebase 'G F E D B A' '--fork-point refs/heads/master'\n> +test_rebase 'G F D B A' '--fork-point --onto D refs/heads/master'\n> +test_rebase 'G F C E D B A' 'refs/heads/master'\n> +test_rebase 'G F C D B A' '--onto D refs/heads/master'\n> +\n> +test_done\n> -- \n> 2.21.0.695.gaf8658f249\n> \n"},{"id":"373202","messageId":"nycvar.QRO.7.76.6.1904051653360.41@tvgsbejvaqbjf.bet","threadId":"50810","inReplyTo":"234ac9f024bf4e6b4944fd8f3912cf6367cf828b.1554151449.git.liu.denton@gmail.com","subject":"Re: [PATCH v3 1/4] t3431: add rebase --fork-point tests","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-04-05T14:55:37Z","receivedAt":"2019-04-05T14:55:46Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Denton,\n\nOn Mon, 1 Apr 2019, Denton Liu wrote:\n\n> diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\n> new file mode 100755\n> index 0000000000..8e2483b73e\n> --- /dev/null\n> +++ b/t/t3431-rebase-fork-point.sh\n> @@ -0,0 +1,53 @@\n> +#!/bin/sh\n> +#\n> +# Copyright (c) 2019 Denton Liu\n> +#\n> +\n> +test_description='git rebase --fork-point test'\n> +\n> +. ./test-lib.sh\n> +\n> +# A---B---D---E       (master)\n> +#     \\\n> +#      C*---F---G (side)\n> +#\n> +# C was formerly part of master but is side out\n> +#\n> +test_expect_success setup '\n> +\ttest_commit A &&\n> +\ttest_commit B &&\n> +\ttest_commit C &&\n> +\tgit branch -t side &&\n> +\tgit reset --hard HEAD^ &&\n> +\ttest_commit D &&\n> +\ttest_commit E &&\n> +\tgit checkout side &&\n> +\ttest_commit F &&\n> +\ttest_commit G\n> +'\n> +\n> +test_rebase() {\n> +\texpected=\"$1\" &&\n> +\tshift &&\n> +\ttest_expect_success \"git rebase $@\" \"\n> +\t\tgit checkout master &&\n> +\t\tgit reset --hard E &&\n> +\t\tgit checkout side &&\n> +\t\tgit reset --hard G &&\n> +\t\tgit rebase $@ &&\n\nI think we need this patch, to make the macOS build happy:\n\n-- snip --\nSubject: fixup??? t3431: add rebase --fork-point tests\n\nTry to fix the Mac build, which currently fails thusly:\n\n    ++ git reset --hard G\n    HEAD is now at d8775ba G\n    ++ git rebase $'\\177'\n    fatal: invalid upstream '?'\n    error: last command exited with $?=128\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n\ndiff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\nindex 4607e65de6..b41a0c0b68 100755\n--- a/t/t3431-rebase-fork-point.sh\n+++ b/t/t3431-rebase-fork-point.sh\n@@ -34,7 +34,7 @@ test_rebase() {\n \t\tgit reset --hard E &&\n \t\tgit checkout side &&\n \t\tgit reset --hard G &&\n-\t\tgit rebase $@ &&\n+\t\teval git rebase \\\"$@\\\" &&\n \t\ttest_write_lines $expected >expect &&\n \t\tgit log --pretty=%s >actual &&\n \t\ttest_cmp expect actual\n-- snap --\n\nCiao,\nDscho\n\n> +\t\ttest_write_lines $expected >expect &&\n> +\t\tgit log --pretty=%s >actual &&\n> +\t\ttest_cmp expect actual\n> +\t\"\n> +}\n> +\n> +test_rebase 'G F E D B A' ''\n> +test_rebase 'G F D B A' '--onto D'\n> +test_rebase 'G F C E D B A' '--no-fork-point'\n> +test_rebase 'G F C D B A' '--no-fork-point --onto D'\n> +test_rebase 'G F E D B A' '--fork-point refs/heads/master'\n> +test_rebase 'G F D B A' '--fork-point --onto D refs/heads/master'\n> +test_rebase 'G F C E D B A' 'refs/heads/master'\n> +test_rebase 'G F C D B A' '--onto D refs/heads/master'\n> +\n> +test_done\n> --\n> 2.21.0.695.gaf8658f249\n>\n>\n"},{"id":"373223","messageId":"20190405172536.GA11212@dev-l","threadId":"50810","inReplyTo":"nycvar.QRO.7.76.6.1904051653360.41@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v3 1/4] t3431: add rebase --fork-point tests","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-05T17:25:36Z","receivedAt":"2019-04-05T17:25:42Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"On Fri, Apr 05, 2019 at 04:55:37PM +0200, Johannes Schindelin wrote:\n> Hi Denton,\n> \n> On Mon, 1 Apr 2019, Denton Liu wrote:\n> \n> > diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\n> > new file mode 100755\n> > index 0000000000..8e2483b73e\n> > --- /dev/null\n> > +++ b/t/t3431-rebase-fork-point.sh\n> > @@ -0,0 +1,53 @@\n> > +#!/bin/sh\n> > +#\n> > +# Copyright (c) 2019 Denton Liu\n> > +#\n> > +\n> > +test_description='git rebase --fork-point test'\n> > +\n> > +. ./test-lib.sh\n> > +\n> > +# A---B---D---E       (master)\n> > +#     \\\n> > +#      C*---F---G (side)\n> > +#\n> > +# C was formerly part of master but is side out\n> > +#\n> > +test_expect_success setup '\n> > +\ttest_commit A &&\n> > +\ttest_commit B &&\n> > +\ttest_commit C &&\n> > +\tgit branch -t side &&\n> > +\tgit reset --hard HEAD^ &&\n> > +\ttest_commit D &&\n> > +\ttest_commit E &&\n> > +\tgit checkout side &&\n> > +\ttest_commit F &&\n> > +\ttest_commit G\n> > +'\n> > +\n> > +test_rebase() {\n> > +\texpected=\"$1\" &&\n> > +\tshift &&\n> > +\ttest_expect_success \"git rebase $@\" \"\n> > +\t\tgit checkout master &&\n> > +\t\tgit reset --hard E &&\n> > +\t\tgit checkout side &&\n> > +\t\tgit reset --hard G &&\n> > +\t\tgit rebase $@ &&\n> \n> I think we need this patch, to make the macOS build happy:\n\nThanks for digging into this, both.\n\nOut of curiosity, t3432 is written similarly:\n\n\ttest_rebase_same_head() {\n\t\tstatus=\"$1\" &&\n\t\tshift &&\n\t\ttest_expect_$status \"git rebase $@ with $changes is no-op\" \"\n\t\t\toldhead=\\$(git rev-parse HEAD) &&\n\t\t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n\t\t\tgit rebase $@ &&\n\t\t\tnewhead=\\$(git rev-parse HEAD) &&\n\t\t\ttest_cmp_rev \\$oldhead \\$newhead\n\t\t\"\n\t}\n\nand is also invoked similarly\n\n\ttest_rebase_same_head success ''\n\nbut it doesn't seem to fail. Any ideas on why?\n\nThanks,\n\nDenton\n\n> \n> -- snip --\n> Subject: fixup??? t3431: add rebase --fork-point tests\n> \n> Try to fix the Mac build, which currently fails thusly:\n> \n>     ++ git reset --hard G\n>     HEAD is now at d8775ba G\n>     ++ git rebase $'\\177'\n>     fatal: invalid upstream '?'\n>     error: last command exited with $?=128\n> \n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> \n> diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\n> index 4607e65de6..b41a0c0b68 100755\n> --- a/t/t3431-rebase-fork-point.sh\n> +++ b/t/t3431-rebase-fork-point.sh\n> @@ -34,7 +34,7 @@ test_rebase() {\n>  \t\tgit reset --hard E &&\n>  \t\tgit checkout side &&\n>  \t\tgit reset --hard G &&\n> -\t\tgit rebase $@ &&\n> +\t\teval git rebase \\\"$@\\\" &&\n>  \t\ttest_write_lines $expected >expect &&\n>  \t\tgit log --pretty=%s >actual &&\n>  \t\ttest_cmp expect actual\n> -- snap --\n> \n> Ciao,\n> Dscho\n> \n> > +\t\ttest_write_lines $expected >expect &&\n> > +\t\tgit log --pretty=%s >actual &&\n> > +\t\ttest_cmp expect actual\n> > +\t\"\n> > +}\n> > +\n> > +test_rebase 'G F E D B A' ''\n> > +test_rebase 'G F D B A' '--onto D'\n> > +test_rebase 'G F C E D B A' '--no-fork-point'\n> > +test_rebase 'G F C D B A' '--no-fork-point --onto D'\n> > +test_rebase 'G F E D B A' '--fork-point refs/heads/master'\n> > +test_rebase 'G F D B A' '--fork-point --onto D refs/heads/master'\n> > +test_rebase 'G F C E D B A' 'refs/heads/master'\n> > +test_rebase 'G F C D B A' '--onto D refs/heads/master'\n> > +\n> > +test_done\n> > --\n> > 2.21.0.695.gaf8658f249\n> >\n> >\n"},{"id":"373224","messageId":"34af9bf5-4031-63ed-c34b-9f2c7ff76c51@kdbg.org","threadId":"50810","inReplyTo":"20190405172536.GA11212@dev-l","subject":"Re: [PATCH v3 1/4] t3431: add rebase --fork-point tests","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2019-04-05T17:51:16Z","receivedAt":"2019-04-05T17:51:22Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 05.04.19 um 19:25 schrieb Denton Liu:\n> On Fri, Apr 05, 2019 at 04:55:37PM +0200, Johannes Schindelin wrote:\n>> On Mon, 1 Apr 2019, Denton Liu wrote:\n>>> +test_rebase() {\n>>> +\texpected=\"$1\" &&\n>>> +\tshift &&\n>>> +\ttest_expect_success \"git rebase $@\" \"\n>>> +\t\tgit checkout master &&\n>>> +\t\tgit reset --hard E &&\n>>> +\t\tgit checkout side &&\n>>> +\t\tgit reset --hard G &&\n>>> +\t\tgit rebase $@ &&\n>>\n>> I think we need this patch, to make the macOS build happy:\n> \n> Thanks for digging into this, both.\n> \n> Out of curiosity, t3432 is written similarly:\n> \n> \ttest_rebase_same_head() {\n> \t\tstatus=\"$1\" &&\n> \t\tshift &&\n> \t\ttest_expect_$status \"git rebase $@ with $changes is no-op\" \"\n> \t\t\toldhead=\\$(git rev-parse HEAD) &&\n> \t\t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n> \t\t\tgit rebase $@ &&\n> \t\t\tnewhead=\\$(git rev-parse HEAD) &&\n> \t\t\ttest_cmp_rev \\$oldhead \\$newhead\n> \t\t\"\n> \t}\n\nUsing $@ in these expansions is wrong. You do not want to forward an\nargument list, but you want to construct a command line. $* is correct\nhere. Then you can remove the single-quotes at the invocation, like so:\n\n\ttest_rebase_same_head success\n\ttest_rebase_same_head success --onto B B\n\nFunction test_rebase() could be done in the same way, but the first\nargument, expected, still needs quotes at the call site, of course.\n\n-- Hannes\n"},{"id":"373248","messageId":"nycvar.QRO.7.76.6.1904052047240.41@tvgsbejvaqbjf.bet","threadId":"50810","inReplyTo":"34af9bf5-4031-63ed-c34b-9f2c7ff76c51@kdbg.org","subject":"Re: [PATCH v3 1/4] t3431: add rebase --fork-point tests","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-04-05T18:51:25Z","receivedAt":"2019-04-05T18:51:33Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Hannes & Denton,\n\n\nOn Fri, 5 Apr 2019, Johannes Sixt wrote:\n\n> Am 05.04.19 um 19:25 schrieb Denton Liu:\n> > On Fri, Apr 05, 2019 at 04:55:37PM +0200, Johannes Schindelin wrote:\n> >> On Mon, 1 Apr 2019, Denton Liu wrote:\n> >>> +test_rebase() {\n> >>> +\texpected=\"$1\" &&\n> >>> +\tshift &&\n> >>> +\ttest_expect_success \"git rebase $@\" \"\n> >>> +\t\tgit checkout master &&\n> >>> +\t\tgit reset --hard E &&\n> >>> +\t\tgit checkout side &&\n> >>> +\t\tgit reset --hard G &&\n> >>> +\t\tgit rebase $@ &&\n> >>\n> >> I think we need this patch, to make the macOS build happy:\n\nActually, my patch did not even fix the build, I looked at the wrong\n(succeeding) build, sorry for the noise.\n\n> > Thanks for digging into this, both.\n> >\n> > Out of curiosity, t3432 is written similarly:\n> >\n> > \ttest_rebase_same_head() {\n> > \t\tstatus=\"$1\" &&\n> > \t\tshift &&\n> > \t\ttest_expect_$status \"git rebase $@ with $changes is no-op\" \"\n> > \t\t\toldhead=\\$(git rev-parse HEAD) &&\n> > \t\t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n> > \t\t\tgit rebase $@ &&\n> > \t\t\tnewhead=\\$(git rev-parse HEAD) &&\n> > \t\t\ttest_cmp_rev \\$oldhead \\$newhead\n> > \t\t\"\n> > \t}\n\nThat is curious, indeed!\n\n> Using $@ in these expansions is wrong. You do not want to forward an\n> argument list, but you want to construct a command line. $* is correct\n> here. Then you can remove the single-quotes at the invocation, like so:\n>\n> \ttest_rebase_same_head success\n> \ttest_rebase_same_head success --onto B B\n>\n> Function test_rebase() could be done in the same way, but the first\n> argument, expected, still needs quotes at the call site, of course.\n\nThat's a good idea, let me run with it.\n\nCiao,\nDscho\n"},{"id":"373257","messageId":"nycvar.QRO.7.76.6.1904052218200.41@tvgsbejvaqbjf.bet","threadId":"50810","inReplyTo":"nycvar.QRO.7.76.6.1904052047240.41@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v3 1/4] t3431: add rebase --fork-point tests","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-04-05T20:19:59Z","receivedAt":"2019-04-05T20:20:08Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Fri, 5 Apr 2019, Johannes Schindelin wrote:\n\n> On Fri, 5 Apr 2019, Johannes Sixt wrote:\n>\n> > Am 05.04.19 um 19:25 schrieb Denton Liu:\n> > > On Fri, Apr 05, 2019 at 04:55:37PM +0200, Johannes Schindelin wrote:\n> > >> On Mon, 1 Apr 2019, Denton Liu wrote:\n> > >>> +test_rebase() {\n> > >>> +\texpected=\"$1\" &&\n> > >>> +\tshift &&\n> > >>> +\ttest_expect_success \"git rebase $@\" \"\n> > >>> +\t\tgit checkout master &&\n> > >>> +\t\tgit reset --hard E &&\n> > >>> +\t\tgit checkout side &&\n> > >>> +\t\tgit reset --hard G &&\n> > >>> +\t\tgit rebase $@ &&\n>\n> > Using $@ in these expansions is wrong. You do not want to forward an\n> > argument list, but you want to construct a command line. $* is correct\n> > here. Then you can remove the single-quotes at the invocation, like so:\n> >\n> > \ttest_rebase_same_head success\n> > \ttest_rebase_same_head success --onto B B\n> >\n> > Function test_rebase() could be done in the same way, but the first\n> > argument, expected, still needs quotes at the call site, of course.\n>\n> That's a good idea, let me run with it.\n\nIndeed, this patch fixes it (see e.g.\nhttps://dev.azure.com/Git-for-Windows/git/_build/results?buildId=34370):\n\n-- snipsnap --\nSubject: [PATCH] fixup??? t3431: add rebase --fork-point tests\n\nTry to fix the Mac build, which currently fails thusly:\n\n\t++ git reset --hard G\n\tHEAD is now at d8775ba G\n\t++ git rebase $'\\177'\n\tfatal: invalid upstream '?'\n\terror: last command exited with $?=128\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n t/t3431-rebase-fork-point.sh | 28 ++++++++++++++--------------\n 1 file changed, 14 insertions(+), 14 deletions(-)\n\ndiff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\nindex 4607e65de6..daa0c77467 100755\n--- a/t/t3431-rebase-fork-point.sh\n+++ b/t/t3431-rebase-fork-point.sh\n@@ -29,29 +29,29 @@ test_expect_success setup '\n test_rebase() {\n \texpected=\"$1\" &&\n \tshift &&\n-\ttest_expect_success \"git rebase $@\" \"\n+\ttest_expect_success \"git rebase $*\" \"\n \t\tgit checkout master &&\n \t\tgit reset --hard E &&\n \t\tgit checkout side &&\n \t\tgit reset --hard G &&\n-\t\tgit rebase $@ &&\n+\t\teval git rebase $* &&\n \t\ttest_write_lines $expected >expect &&\n \t\tgit log --pretty=%s >actual &&\n \t\ttest_cmp expect actual\n \t\"\n }\n\n-test_rebase 'G F E D B A' ''\n-test_rebase 'G F D B A' '--onto D'\n-test_rebase 'G F B A' '--keep-base'\n-test_rebase 'G F C E D B A' '--no-fork-point'\n-test_rebase 'G F C D B A' '--no-fork-point --onto D'\n-test_rebase 'G F C B A' '--no-fork-point --keep-base'\n-test_rebase 'G F E D B A' '--fork-point refs/heads/master'\n-test_rebase 'G F D B A' '--fork-point --onto D refs/heads/master'\n-test_rebase 'G F B A' '--fork-point --keep-base refs/heads/master'\n-test_rebase 'G F C E D B A' 'refs/heads/master'\n-test_rebase 'G F C D B A' '--onto D refs/heads/master'\n-test_rebase 'G F C B A' '--keep-base refs/heads/master'\n+test_rebase 'G F E D B A'\n+test_rebase 'G F D B A' --onto D\n+test_rebase 'G F B A' --keep-base\n+test_rebase 'G F C E D B A' --no-fork-point\n+test_rebase 'G F C D B A' --no-fork-point --onto D\n+test_rebase 'G F C B A' --no-fork-point --keep-base\n+test_rebase 'G F E D B A' --fork-point refs/heads/master\n+test_rebase 'G F D B A' --fork-point --onto D refs/heads/master\n+test_rebase 'G F B A' --fork-point --keep-base refs/heads/master\n+test_rebase 'G F C E D B A' refs/heads/master\n+test_rebase 'G F C D B A' --onto D refs/heads/master\n+test_rebase 'G F C B A' --keep-base refs/heads/master\n\n test_done\n--\n2.21.0.windows.1.152.g5895f170b6\n\n"},{"id":"373261","messageId":"20190405211022.GC8796@szeder.dev","threadId":"50810","inReplyTo":"nycvar.QRO.7.76.6.1904052218200.41@tvgsbejvaqbjf.bet","subject":"Re: [PATCH v3 1/4] t3431: add rebase --fork-point tests","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2019-04-05T21:10:22Z","receivedAt":"2019-04-05T21:10:28Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Fri, Apr 05, 2019 at 10:19:59PM +0200, Johannes Schindelin wrote:\n> Hi,\n> \n> On Fri, 5 Apr 2019, Johannes Schindelin wrote:\n> \n> > On Fri, 5 Apr 2019, Johannes Sixt wrote:\n> >\n> > > Am 05.04.19 um 19:25 schrieb Denton Liu:\n> > > > On Fri, Apr 05, 2019 at 04:55:37PM +0200, Johannes Schindelin wrote:\n> > > >> On Mon, 1 Apr 2019, Denton Liu wrote:\n> > > >>> +test_rebase() {\n> > > >>> +\texpected=\"$1\" &&\n> > > >>> +\tshift &&\n> > > >>> +\ttest_expect_success \"git rebase $@\" \"\n> > > >>> +\t\tgit checkout master &&\n> > > >>> +\t\tgit reset --hard E &&\n> > > >>> +\t\tgit checkout side &&\n> > > >>> +\t\tgit reset --hard G &&\n> > > >>> +\t\tgit rebase $@ &&\n> >\n> > > Using $@ in these expansions is wrong. You do not want to forward an\n> > > argument list, but you want to construct a command line. $* is correct\n> > > here. Then you can remove the single-quotes at the invocation, like so:\n> > >\n> > > \ttest_rebase_same_head success\n> > > \ttest_rebase_same_head success --onto B B\n> > >\n> > > Function test_rebase() could be done in the same way, but the first\n> > > argument, expected, still needs quotes at the call site, of course.\n> >\n> > That's a good idea, let me run with it.\n> \n> Indeed, this patch fixes it (see e.g.\n> https://dev.azure.com/Git-for-Windows/git/_build/results?buildId=34370):\n> \n> -- snipsnap --\n> Subject: [PATCH] fixup??? t3431: add rebase --fork-point tests\n> \n> Try to fix the Mac build, which currently fails thusly:\n> \n> \t++ git reset --hard G\n> \tHEAD is now at d8775ba G\n> \t++ git rebase $'\\177'\n> \tfatal: invalid upstream '?'\n> \terror: last command exited with $?=128\n> \n> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n> ---\n>  t/t3431-rebase-fork-point.sh | 28 ++++++++++++++--------------\n>  1 file changed, 14 insertions(+), 14 deletions(-)\n> \n> diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\n> index 4607e65de6..daa0c77467 100755\n> --- a/t/t3431-rebase-fork-point.sh\n> +++ b/t/t3431-rebase-fork-point.sh\n> @@ -29,29 +29,29 @@ test_expect_success setup '\n>  test_rebase() {\n>  \texpected=\"$1\" &&\n>  \tshift &&\n> -\ttest_expect_success \"git rebase $@\" \"\n> +\ttest_expect_success \"git rebase $*\" \"\n>  \t\tgit checkout master &&\n>  \t\tgit reset --hard E &&\n>  \t\tgit checkout side &&\n>  \t\tgit reset --hard G &&\n> -\t\tgit rebase $@ &&\n> +\t\teval git rebase $* &&\n\nThe 'eval' is not necessary, all Bash versions down to v3.0 work\nwithout it.\n\n>  \t\ttest_write_lines $expected >expect &&\n>  \t\tgit log --pretty=%s >actual &&\n>  \t\ttest_cmp expect actual\n>  \t\"\n>  }\n> \n> -test_rebase 'G F E D B A' ''\n> -test_rebase 'G F D B A' '--onto D'\n> -test_rebase 'G F B A' '--keep-base'\n> -test_rebase 'G F C E D B A' '--no-fork-point'\n> -test_rebase 'G F C D B A' '--no-fork-point --onto D'\n> -test_rebase 'G F C B A' '--no-fork-point --keep-base'\n> -test_rebase 'G F E D B A' '--fork-point refs/heads/master'\n> -test_rebase 'G F D B A' '--fork-point --onto D refs/heads/master'\n> -test_rebase 'G F B A' '--fork-point --keep-base refs/heads/master'\n> -test_rebase 'G F C E D B A' 'refs/heads/master'\n> -test_rebase 'G F C D B A' '--onto D refs/heads/master'\n> -test_rebase 'G F C B A' '--keep-base refs/heads/master'\n> +test_rebase 'G F E D B A'\n> +test_rebase 'G F D B A' --onto D\n> +test_rebase 'G F B A' --keep-base\n> +test_rebase 'G F C E D B A' --no-fork-point\n> +test_rebase 'G F C D B A' --no-fork-point --onto D\n> +test_rebase 'G F C B A' --no-fork-point --keep-base\n> +test_rebase 'G F E D B A' --fork-point refs/heads/master\n> +test_rebase 'G F D B A' --fork-point --onto D refs/heads/master\n> +test_rebase 'G F B A' --fork-point --keep-base refs/heads/master\n> +test_rebase 'G F C E D B A' refs/heads/master\n> +test_rebase 'G F C D B A' --onto D refs/heads/master\n> +test_rebase 'G F C B A' --keep-base refs/heads/master\n> \n>  test_done\n> --\n> 2.21.0.windows.1.152.g5895f170b6\n> \n"},{"id":"373263","messageId":"cover.1554500051.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1554151449.git.liu.denton@gmail.com","subject":"[PATCH v4 0/4] rebase: teach rebase --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-05T21:39:58Z","receivedAt":"2019-04-05T21:40:03Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Hi all,\n\nI dug into it a little more and according to the bash 4.2->4.3\nchangelog,\n\n\tbbb. Fixed a bug that caused spurious DEL characters (\\177) to appear in\n\tdouble-quoted expansion where the RHS is evaluated to the empty string.\n\nso, in particular, I've removed all of the '' from the tests.\n\nI also downloaded bash 4.2 and tested everything with that. Seems to be\nworking well!\n\nThanks for catching this!\n\n---\n\nThis patchset now depends \"[PATCH 1/8] tests (rebase): spell out the\n`--keep-empty` option\" which is the first patch of Johannes's \"Do not\nuse abbreviated options in tests\" patchset[1]. (Thanks for catching\nthat, Johannes!)\n\nChanges since v1:\n\n* Squashed old set into one patch\n* Fixed indentation style and dangling else\n* Added more documentation after discussion with Ævar\n\nChanges since v2:\n\n* Add testing for rebase --fork-point behaviour\n* Add testing for rebase fast-forward behaviour\n* Make rebase --onto fast-forward in more cases\n* Update documentation to include use-case\n\nChanges since v3:\n\n* Fix tests failing on bash 4.2\n* Fix typo in t3431 comment\n\n[1]: https://public-inbox.org/git/a1b4b74b9167e279dae4cd8c58fb28d8a714a66a.1553537656.git.gitgitgadget@gmail.com/\n\nDenton Liu (4):\n  t3431: add rebase --fork-point tests\n  t3432: test rebase fast-forward behavior\n  rebase: fast-forward --onto in more cases\n  rebase: teach rebase --keep-base\n\n Documentation/git-rebase.txt     | 30 +++++++++++--\n builtin/rebase.c                 | 72 +++++++++++++++++++++++---------\n t/t3400-rebase.sh                |  2 +-\n t/t3404-rebase-interactive.sh    |  2 +-\n t/t3416-rebase-onto-threedots.sh | 57 +++++++++++++++++++++++++\n t/t3431-rebase-fork-point.sh     | 57 +++++++++++++++++++++++++\n t/t3432-rebase-fast-forward.sh   | 62 +++++++++++++++++++++++++++\n 7 files changed, 258 insertions(+), 24 deletions(-)\n create mode 100755 t/t3431-rebase-fork-point.sh\n create mode 100755 t/t3432-rebase-fast-forward.sh\n\nInterdiff against v3:\ndiff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\nindex 0311bcbc68..e63040932f 100755\n--- a/t/t3431-rebase-fork-point.sh\n+++ b/t/t3431-rebase-fork-point.sh\n@@ -11,7 +11,7 @@ test_description='git rebase --fork-point test'\n #     \\\n #      C*---F---G (side)\n #\n-# C was formerly part of master but is side out\n+# C was formerly part of master but master was rewound to remove C\n #\n test_expect_success setup '\n \ttest_commit A &&\n@@ -29,29 +29,29 @@ test_expect_success setup '\n test_rebase() {\n \texpected=\"$1\" &&\n \tshift &&\n-\ttest_expect_success \"git rebase $@\" \"\n+\ttest_expect_success \"git rebase $*\" \"\n \t\tgit checkout master &&\n \t\tgit reset --hard E &&\n \t\tgit checkout side &&\n \t\tgit reset --hard G &&\n-\t\tgit rebase $@ &&\n+\t\tgit rebase $* &&\n \t\ttest_write_lines $expected >expect &&\n \t\tgit log --pretty=%s >actual &&\n \t\ttest_cmp expect actual\n \t\"\n }\n \n-test_rebase 'G F E D B A' ''\n-test_rebase 'G F D B A' '--onto D'\n-test_rebase 'G F B A' '--keep-base'\n-test_rebase 'G F C E D B A' '--no-fork-point'\n-test_rebase 'G F C D B A' '--no-fork-point --onto D'\n-test_rebase 'G F C B A' '--no-fork-point --keep-base'\n-test_rebase 'G F E D B A' '--fork-point refs/heads/master'\n-test_rebase 'G F D B A' '--fork-point --onto D refs/heads/master'\n-test_rebase 'G F B A' '--fork-point --keep-base refs/heads/master'\n-test_rebase 'G F C E D B A' 'refs/heads/master'\n-test_rebase 'G F C D B A' '--onto D refs/heads/master'\n-test_rebase 'G F C B A' '--keep-base refs/heads/master'\n+test_rebase 'G F E D B A'\n+test_rebase 'G F D B A' --onto D\n+test_rebase 'G F B A' --keep-base\n+test_rebase 'G F C E D B A' --no-fork-point\n+test_rebase 'G F C D B A' --no-fork-point --onto D\n+test_rebase 'G F C B A' --no-fork-point --keep-base\n+test_rebase 'G F E D B A' --fork-point refs/heads/master\n+test_rebase 'G F D B A' --fork-point --onto D refs/heads/master\n+test_rebase 'G F B A' --fork-point --keep-base refs/heads/master\n+test_rebase 'G F C E D B A' refs/heads/master\n+test_rebase 'G F C D B A' --onto D refs/heads/master\n+test_rebase 'G F C B A' --keep-base refs/heads/master\n \n test_done\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 8585c21c5c..f493ce64c4 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -18,34 +18,34 @@ test_expect_success setup '\n test_rebase_same_head() {\n \tstatus=\"$1\" &&\n \tshift &&\n-\ttest_expect_$status \"git rebase $@ with $changes is no-op\" \"\n+\ttest_expect_$status \"git rebase $* with $changes is no-op\" \"\n \t\toldhead=\\$(git rev-parse HEAD) &&\n \t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n-\t\tgit rebase $@ &&\n+\t\tgit rebase $* &&\n \t\tnewhead=\\$(git rev-parse HEAD) &&\n \t\ttest_cmp_rev \\$oldhead \\$newhead\n \t\"\n }\n \n changes='no changes'\n-test_rebase_same_head success ''\n-test_rebase_same_head success 'master'\n-test_rebase_same_head success '--onto B B'\n-test_rebase_same_head success '--onto B... B'\n-test_rebase_same_head success '--onto master... master'\n-test_rebase_same_head success '--keep-base master'\n+test_rebase_same_head success\n+test_rebase_same_head success master\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --keep-base master\n \n test_expect_success 'add work to side' '\n \ttest_commit E\n '\n \n changes='our changes'\n-test_rebase_same_head success ''\n-test_rebase_same_head success 'master'\n-test_rebase_same_head success '--onto B B'\n-test_rebase_same_head success '--onto B... B'\n-test_rebase_same_head success '--onto master... master'\n-test_rebase_same_head success '--keep-base master'\n+test_rebase_same_head success\n+test_rebase_same_head success master\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --keep-base master\n \n test_expect_success 'add work to upstream' '\n \tgit checkout master &&\n@@ -54,9 +54,9 @@ test_expect_success 'add work to upstream' '\n '\n \n changes='our and their changes'\n-test_rebase_same_head success '--onto B B'\n-test_rebase_same_head success '--onto B... B'\n-test_rebase_same_head success '--onto master... master'\n-test_rebase_same_head success '--keep-base master'\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --keep-base master\n \n test_done\n-- \n2.21.0.843.gd0ae0373aa\n\n"},{"id":"373264","messageId":"0f1e9ac5c8b88649a378f676ee924a966ce0693c.1554500051.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1554500051.git.liu.denton@gmail.com","subject":"[PATCH v4 1/4] t3431: add rebase --fork-point tests","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-05T21:40:00Z","receivedAt":"2019-04-05T21:40:06Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Signed-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3431-rebase-fork-point.sh | 53 ++++++++++++++++++++++++++++++++++++\n 1 file changed, 53 insertions(+)\n create mode 100755 t/t3431-rebase-fork-point.sh\n\ndiff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\nnew file mode 100755\nindex 0000000000..6d523123d0\n--- /dev/null\n+++ b/t/t3431-rebase-fork-point.sh\n@@ -0,0 +1,53 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2019 Denton Liu\n+#\n+\n+test_description='git rebase --fork-point test'\n+\n+. ./test-lib.sh\n+\n+# A---B---D---E       (master)\n+#     \\\n+#      C*---F---G (side)\n+#\n+# C was formerly part of master but master was rewound to remove C\n+#\n+test_expect_success setup '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\ttest_commit C &&\n+\tgit branch -t side &&\n+\tgit reset --hard HEAD^ &&\n+\ttest_commit D &&\n+\ttest_commit E &&\n+\tgit checkout side &&\n+\ttest_commit F &&\n+\ttest_commit G\n+'\n+\n+test_rebase() {\n+\texpected=\"$1\" &&\n+\tshift &&\n+\ttest_expect_success \"git rebase $*\" \"\n+\t\tgit checkout master &&\n+\t\tgit reset --hard E &&\n+\t\tgit checkout side &&\n+\t\tgit reset --hard G &&\n+\t\tgit rebase $* &&\n+\t\ttest_write_lines $expected >expect &&\n+\t\tgit log --pretty=%s >actual &&\n+\t\ttest_cmp expect actual\n+\t\"\n+}\n+\n+test_rebase 'G F E D B A'\n+test_rebase 'G F D B A' --onto D\n+test_rebase 'G F C E D B A' --no-fork-point\n+test_rebase 'G F C D B A' --no-fork-point --onto D\n+test_rebase 'G F E D B A' --fork-point refs/heads/master\n+test_rebase 'G F D B A' --fork-point --onto D refs/heads/master\n+test_rebase 'G F C E D B A' refs/heads/master\n+test_rebase 'G F C D B A' --onto D refs/heads/master\n+\n+test_done\n-- \n2.21.0.843.gd0ae0373aa\n\n"},{"id":"373265","messageId":"963b600c79d3d59c5a359e87fb413fec88ed8aaa.1554500051.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1554500051.git.liu.denton@gmail.com","subject":"[PATCH v4 2/4] t3432: test rebase fast-forward behavior","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-05T21:40:03Z","receivedAt":"2019-04-05T21:40:07Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"When rebase is run on a branch that can be fast-forwarded, this should\nautomatically be done. Create test to ensure this behavior happens.\n\nThere is one case that currently does not pass. In the case where a\nfeature and master have diverged, running \"git rebase master... master\"\ncauses a full rebase to happen even though a fast-forward should happen.\nMark this case as failure so we can fix it later.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3432-rebase-fast-forward.sh | 59 ++++++++++++++++++++++++++++++++++\n 1 file changed, 59 insertions(+)\n create mode 100755 t/t3432-rebase-fast-forward.sh\n\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nnew file mode 100755\nindex 0000000000..b7c9af17c3\n--- /dev/null\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -0,0 +1,59 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2019 Denton Liu\n+#\n+\n+test_description='ensure rebase fast-forwards commits when possible'\n+\n+. ./test-lib.sh\n+\n+test_expect_success setup '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\ttest_commit C &&\n+\ttest_commit D &&\n+\tgit checkout -t -b side\n+'\n+\n+test_rebase_same_head() {\n+\tstatus=\"$1\" &&\n+\tshift &&\n+\ttest_expect_$status \"git rebase $* with $changes is no-op\" \"\n+\t\toldhead=\\$(git rev-parse HEAD) &&\n+\t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n+\t\tgit rebase $* &&\n+\t\tnewhead=\\$(git rev-parse HEAD) &&\n+\t\ttest_cmp_rev \\$oldhead \\$newhead\n+\t\"\n+}\n+\n+changes='no changes'\n+test_rebase_same_head success\n+test_rebase_same_head success master\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head success --onto master... master\n+\n+test_expect_success 'add work to side' '\n+\ttest_commit E\n+'\n+\n+changes='our changes'\n+test_rebase_same_head success\n+test_rebase_same_head success master\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head success --onto master... master\n+\n+test_expect_success 'add work to upstream' '\n+\tgit checkout master &&\n+\ttest_commit F &&\n+\tgit checkout side\n+'\n+\n+changes='our and their changes'\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head failure --onto master... master\n+\n+test_done\n-- \n2.21.0.843.gd0ae0373aa\n\n"},{"id":"373266","messageId":"6635a99edebee43273ee47bbebe13dfe539fc69c.1554500051.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1554500051.git.liu.denton@gmail.com","subject":"[PATCH v4 4/4] rebase: teach rebase --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-05T21:40:06Z","receivedAt":"2019-04-05T21:40:10Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"A common scenario is if a user is working on a topic branch and they\nwish to make some changes to intermediate commits or autosquash, they\nwould run something such as\n\n\tgit rebase -i --onto master... master\n\nin order to preserve the merge base. This is useful when contributing a\npatch series to the Git mailing list, one often starts on top of the\ncurrent 'master'. However, while developing the patches, 'master' is\nalso developed further and it is sometimes not the bst idea to keep\nrebasing on top of 'master', but to keep the base commit as-is.\n\nAlternatively, a user wishing to test individual commits in a topic\nbranch without changing anything may run\n\n\tgit rebase -x ./test.sh master... master\n\nSince rebasing onto the merge base of the branch and the upstream is\nsuch a common case, introduce the --keep-base option as a shortcut.\n\nThis allows us to rewrite the above as\n\n\tgit rebase -i --keep-base master\n\nand\n\n\tgit rebase -x ./test.sh --keep-base master\n\nrespectively.\n\nAdd tests to ensure --keep-base works correctly in the normal case and\nfails when there are multiple merge bases, both in regular and\ninteractive mode. Also, test to make sure conflicting options cause\nrebase to fail. While we're adding test cases, add a missing\nset_fake_editor call to 'rebase -i --onto master...side'.\n\nWhile we're documenting the --keep-base option, change an instance of\n\"merge-base\" to \"merge base\", which is the consistent spelling.\n\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nHelped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n Documentation/git-rebase.txt     | 30 +++++++++++++++--\n builtin/rebase.c                 | 32 ++++++++++++++----\n t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++++++++\n t/t3431-rebase-fork-point.sh     |  4 +++\n t/t3432-rebase-fast-forward.sh   |  3 ++\n 5 files changed, 117 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 6363d674b7..569ab708d4 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip\n SYNOPSIS\n --------\n [verse]\n-'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n-\t[<upstream> [<branch>]]\n+'git rebase' [-i | --interactive] [<options>] [--exec <cmd>]\n+\t[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\n 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n \t--root [<branch>]\n 'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch\n@@ -217,6 +217,24 @@ As a special case, you may use \"A\\...B\" as a shortcut for the\n merge base of A and B if there is exactly one merge base. You can\n leave out at most one of A and B, in which case it defaults to HEAD.\n \n+--keep-base::\n+\tSet the starting point at which to create the new commits to the\n+\tmerge base of <upstream> <branch>. Running\n+\t'git rebase --keep-base <upstream> <branch>' is equivalent to\n+\trunning 'git rebase --onto <upstream>... <upstream>'.\n++\n+This option is useful in the case where one is developing a feature on\n+top of an upstream branch. While the feature is being worked on, the\n+upstream branch may advance and it may not be the best idea to keep\n+rebasing on top of the upstream but to keep the base commit as-is.\n++\n+Although both this option and --fork-point find the merge base between\n+<upstream> and <branch>, this option uses the merge base as the _starting\n+point_ on which new commits will be created, whereas --fork-point uses\n+the merge base to determine the _set of commits_ which will be rebased.\n++\n+See also INCOMPATIBLE OPTIONS below.\n+\n <upstream>::\n \tUpstream branch to compare against.  May be any valid commit,\n \tnot just an existing branch name. Defaults to the configured\n@@ -364,6 +382,10 @@ ends up being empty, the <upstream> will be used as a fallback.\n +\n If either <upstream> or --root is given on the command line, then the\n default is `--no-fork-point`, otherwise the default is `--fork-point`.\n++\n+If your branch was based on <upstream> but <upstream> was rewound and\n+your branch contains commits which were dropped, this option can be used\n+with `--keep-base` in order to drop those commits from your branch.\n \n --ignore-whitespace::\n --whitespace=<option>::\n@@ -539,6 +561,8 @@ In addition, the following pairs of options are incompatible:\n  * --preserve-merges and --rebase-merges\n  * --rebase-merges and --strategy\n  * --rebase-merges and --strategy-option\n+ * --keep-base and --onto\n+ * --keep-base and --root\n \n BEHAVIORAL DIFFERENCES\n -----------------------\n@@ -863,7 +887,7 @@ NOTE: While an \"easy case recovery\" sometimes appears to be successful\n       --interactive` will be **resurrected**!\n \n The idea is to manually tell 'git rebase' \"where the old 'subsystem'\n-ended and your 'topic' began\", that is, what the old merge-base\n+ended and your 'topic' began\", that is, what the old merge base\n between them was.  You will have to find a way to name the last commit\n of the old 'subsystem', for example:\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 7aa6a090d4..caec1b56e8 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -27,8 +27,8 @@\n #include \"branch.h\"\n \n static char const * const builtin_rebase_usage[] = {\n-\tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n-\t\t\"[<upstream>] [<branch>]\"),\n+\tN_(\"git rebase [-i] [options] [--exec <cmd>] \"\n+\t\t\"[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\"),\n \tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n \t\t\"--root [<branch>]\"),\n \tN_(\"git rebase --continue | --abort | --skip | --edit-todo\"),\n@@ -1035,6 +1035,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t};\n \tconst char *branch_name;\n \tint ret, flags, total_argc, in_progress = 0;\n+\tint keep_base = 0;\n \tint ok_to_skip_pre_rebase = 0;\n \tstruct strbuf msg = STRBUF_INIT;\n \tstruct strbuf revisions = STRBUF_INIT;\n@@ -1068,6 +1069,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"rebase onto given branch instead of upstream\")),\n+\t\tOPT_BOOL(0, \"keep-base\", &keep_base,\n+\t\t\t N_(\"use the merge-base of upstream and branch as the current base\")),\n \t\tOPT_BOOL(0, \"no-verify\", &ok_to_skip_pre_rebase,\n \t\t\t N_(\"allow pre-rebase hook to run\")),\n \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n@@ -1234,6 +1237,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(builtin_rebase_usage,\n \t\t\t\t   builtin_rebase_options);\n \n+\tif (keep_base) {\n+\t\tif (options.onto_name)\n+\t\t\tdie(_(\"cannot combine '--keep-base' with '--onto'\"));\n+\t\tif (options.root)\n+\t\t\tdie(_(\"cannot combine '--keep-base' with '--root'\"));\n+\t}\n+\n \tif (action != NO_ACTION && !in_progress)\n \t\tdie(_(\"No rebase in progress?\"));\n \tsetenv(GIT_REFLOG_ACTION_ENVIRONMENT, \"rebase\", 0);\n@@ -1558,12 +1568,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t}\n \n \t/* Make sure the branch to rebase onto is valid. */\n-\tif (!options.onto_name)\n+\tif (keep_base) {\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addstr(&buf, options.upstream_name);\n+\t\tstrbuf_addstr(&buf, \"...\");\n+\t\toptions.onto_name = xstrdup(buf.buf);\n+\t} else if (!options.onto_name)\n \t\toptions.onto_name = options.upstream_name;\n \tif (strstr(options.onto_name, \"...\")) {\n-\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0)\n-\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n-\t\t\t    options.onto_name);\n+\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0) {\n+\t\t\tif (keep_base)\n+\t\t\t\tdie(_(\"'%s': need exactly one merge base with branch\"),\n+\t\t\t\t    options.upstream_name);\n+\t\t\telse\n+\t\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n+\t\t\t\t    options.onto_name);\n+\t\t}\n \t\toptions.onto = lookup_commit_or_die(&merge_base,\n \t\t\t\t\t\t    options.onto_name);\n \t} else {\ndiff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh\nindex ddf2f64853..9c2548423b 100755\n--- a/t/t3416-rebase-onto-threedots.sh\n+++ b/t/t3416-rebase-onto-threedots.sh\n@@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' '\n \tgit checkout side &&\n \tgit reset --hard K &&\n \n+\tset_fake_editor &&\n \ttest_must_fail git rebase -i --onto master...side J\n '\n \n+test_expect_success 'rebase --keep-base --onto incompatible' '\n+\ttest_must_fail git rebase --keep-base --onto master...\n+'\n+\n+test_expect_success 'rebase --keep-base --root incompatible' '\n+\ttest_must_fail git rebase --keep-base --root\n+'\n+\n+test_expect_success 'rebase --keep-base master from topic' '\n+\tgit reset --hard &&\n+\tgit checkout topic &&\n+\tgit reset --hard G &&\n+\n+\tgit rebase --keep-base master &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base master HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --keep-base master from side' '\n+\tgit reset --hard &&\n+\tgit checkout side &&\n+\tgit reset --hard K &&\n+\n+\ttest_must_fail git rebase --keep-base master\n+'\n+\n+test_expect_success 'rebase -i --keep-base master from topic' '\n+\tgit reset --hard &&\n+\tgit checkout topic &&\n+\tgit reset --hard G &&\n+\n+\tset_fake_editor &&\n+\tEXPECT_COUNT=2 git rebase -i --keep-base master &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base master HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase -i --keep-base master from side' '\n+\tgit reset --hard &&\n+\tgit checkout side &&\n+\tgit reset --hard K &&\n+\n+\tset_fake_editor &&\n+\ttest_must_fail git rebase -i --keep-base master\n+'\n+\n test_done\ndiff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\nindex 6d523123d0..e63040932f 100755\n--- a/t/t3431-rebase-fork-point.sh\n+++ b/t/t3431-rebase-fork-point.sh\n@@ -43,11 +43,15 @@ test_rebase() {\n \n test_rebase 'G F E D B A'\n test_rebase 'G F D B A' --onto D\n+test_rebase 'G F B A' --keep-base\n test_rebase 'G F C E D B A' --no-fork-point\n test_rebase 'G F C D B A' --no-fork-point --onto D\n+test_rebase 'G F C B A' --no-fork-point --keep-base\n test_rebase 'G F E D B A' --fork-point refs/heads/master\n test_rebase 'G F D B A' --fork-point --onto D refs/heads/master\n+test_rebase 'G F B A' --fork-point --keep-base refs/heads/master\n test_rebase 'G F C E D B A' refs/heads/master\n test_rebase 'G F C D B A' --onto D refs/heads/master\n+test_rebase 'G F C B A' --keep-base refs/heads/master\n \n test_done\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 357cf17bdd..f493ce64c4 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -33,6 +33,7 @@ test_rebase_same_head success master\n test_rebase_same_head success --onto B B\n test_rebase_same_head success --onto B... B\n test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --keep-base master\n \n test_expect_success 'add work to side' '\n \ttest_commit E\n@@ -44,6 +45,7 @@ test_rebase_same_head success master\n test_rebase_same_head success --onto B B\n test_rebase_same_head success --onto B... B\n test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --keep-base master\n \n test_expect_success 'add work to upstream' '\n \tgit checkout master &&\n@@ -55,5 +57,6 @@ changes='our and their changes'\n test_rebase_same_head success --onto B B\n test_rebase_same_head success --onto B... B\n test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --keep-base master\n \n test_done\n-- \n2.21.0.843.gd0ae0373aa\n\n"},{"id":"373267","messageId":"338a4b6b3a40d64d3eac37cc4d19ebe55ec02282.1554500051.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1554500051.git.liu.denton@gmail.com","subject":"[PATCH v4 3/4] rebase: fast-forward --onto in more cases","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-05T21:40:05Z","receivedAt":"2019-04-05T21:40:12Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Before, when we had the following graph,\n\n\tA---B---C (master)\n\t    \\\n\t     D (side)\n\nrunning 'git rebase --onto master... master side' would result in D\nbeing always rebased, no matter what. However, the desired behavior is\nthat rebase should notice that this is fast-forwardable and do that\ninstead.\n\nAdd detection to `can_fast_forward` so that this case can be detected\nand a fast-forward will be performed. First of all, rewrite the function\nto use gotos which simplifies the logic. Next, since the\n\n\toptions.upstream &&\n\t!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)\n\nconditions were removed in `cmd_rebase`, we reintroduce a substitute in\n`can_fast_forward`. In particular, checking the merge bases of\n`upstream` and `head` fixes a failing case in t3416.\n\nThe abbreviated graph for t3416 is as follows:\n\n\t\t    F---G topic\n\t\t   /\n\t  A---B---C---D---E master\n\nand the failing command was\n\n\tgit rebase --onto master...topic F topic\n\nBefore, Git would see that there was one merge base (C), and the merge\nand onto were the same so it would incorrectly return 1, indicating that\nwe could fast-forward. This would cause the rebased graph to be 'ABCFG'\nwhen we were expecting 'ABCG'.\n\nWith the additional logic, we detect that upstream and head's merge base\nis F. Since onto isn't F, it means we're not rebasing the full set of\ncommits from master..topic. Since we're excluding some commits, a\nfast-forward cannot be performed and so we correctly return 0.\n\nAdd '-f' to test cases that failed as a result of this change because\nthey were not expecting a fast-forward so that a rebase is forced.\n\nWhile we're at it, remove a trailing whitespace from rebase.c.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n builtin/rebase.c               | 40 +++++++++++++++++++++++-----------\n t/t3400-rebase.sh              |  2 +-\n t/t3404-rebase-interactive.sh  |  2 +-\n t/t3432-rebase-fast-forward.sh |  2 +-\n 4 files changed, 30 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 77deebc65c..7aa6a090d4 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -895,12 +895,12 @@ static int is_linear_history(struct commit *from, struct commit *to)\n \treturn 1;\n }\n \n-static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n-\t\t\t    struct object_id *merge_base)\n+static int can_fast_forward(struct commit *onto, struct commit *upstream,\n+\t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n {\n \tstruct commit *head = lookup_commit(the_repository, head_oid);\n-\tstruct commit_list *merge_bases;\n-\tint res;\n+\tstruct commit_list *merge_bases = NULL;\n+\tint res = 0;\n \n \tif (!head)\n \t\treturn 0;\n@@ -908,12 +908,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n \tmerge_bases = get_merge_bases(onto, head);\n \tif (merge_bases && !merge_bases->next) {\n \t\toidcpy(merge_base, &merge_bases->item->object.oid);\n-\t\tres = oideq(merge_base, &onto->object.oid);\n+\t\tif (!oideq(merge_base, &onto->object.oid))\n+\t\t\tgoto done;\n \t} else {\n \t\toidcpy(merge_base, &null_oid);\n-\t\tres = 0;\n+\t\tgoto done;\n \t}\n+\n+\tif (!upstream)\n+\t\tgoto done;\n+\n \tfree_commit_list(merge_bases);\n+\tmerge_bases = get_merge_bases(upstream, head);\n+\tif (merge_bases && !merge_bases->next) {\n+\t\tif (!oideq(&onto->object.oid, &merge_bases->item->object.oid))\n+\t\t\tgoto done;\n+\t} else\n+\t\tgoto done;\n+\n+\tres = 1;\n+\n+done:\n+\tif (merge_bases)\n+\t\tfree_commit_list(merge_bases);\n \treturn res && is_linear_history(onto, head);\n }\n \n@@ -1682,13 +1699,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \t/*\n \t * Check if we are already based on onto with linear history,\n-\t * but this should be done only when upstream and onto are the same\n-\t * and if this is not an interactive rebase.\n+\t * but this should be done if this is not an interactive rebase.\n \t */\n-\tif (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&\n-\t    !is_interactive(&options) && !options.restrict_revision &&\n-\t    options.upstream &&\n-\t    !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {\n+\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head, &merge_base) &&\n+\t    !is_interactive(&options) && !options.restrict_revision) {\n \t\tint flag;\n \n \t\tif (!(options.flags & REBASE_FORCE)) {\n@@ -1782,7 +1796,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tstrbuf_addf(&msg, \"%s: checkout %s\",\n \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);\n \tif (reset_head(&options.onto->object.oid, \"checkout\", NULL,\n-\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD | \n+\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n \t\t       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n \t\t       NULL, msg.buf))\n \t\tdie(_(\"Could not detach HEAD\"));\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 460d0523be..604d624ff8 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' '\n \t\techo two >>init.t &&\n \t\tgit commit -a -m two &&\n \t\tgit tag two &&\n-\t\ttest_must_fail git rebase --onto init HEAD^ &&\n+\t\ttest_must_fail git rebase -f --onto init HEAD^ &&\n \t\tGIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&\n \t\tgrep \"show.*$(git rev-parse two)\" stderr\n \t)\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex b60b11f9f2..f054186cc7 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int\n \tgit reset --hard &&\n \tgit checkout conflict-branch &&\n \tset_fake_editor &&\n-\ttest_must_fail git rebase --onto HEAD~2 HEAD~ &&\n+\ttest_must_fail git rebase -f --onto HEAD~2 HEAD~ &&\n \ttest_must_fail git rebase --edit-todo &&\n \tgit rebase --abort\n '\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex b7c9af17c3..357cf17bdd 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -54,6 +54,6 @@ test_expect_success 'add work to upstream' '\n changes='our and their changes'\n test_rebase_same_head success --onto B B\n test_rebase_same_head success --onto B... B\n-test_rebase_same_head failure --onto master... master\n+test_rebase_same_head success --onto master... master\n \n test_done\n-- \n2.21.0.843.gd0ae0373aa\n\n"},{"id":"373335","messageId":"87ftquapfy.fsf@evledraar.gmail.com","threadId":"50810","inReplyTo":"cover.1554151449.git.liu.denton@gmail.com","subject":"Re: [PATCH v3 0/4] rebase: teach rebase --keep-base","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-04-06T19:44:49Z","receivedAt":"2019-04-06T19:44:54Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Apr 01 2019, Denton Liu wrote:\n\n> Thanks again for your feedback, Ævar! I think we're both on the same page now.\n> Hopefully I've addressed all of your high-level concerns with this patchset and\n> we can move into a discussion on implementation detail.\n\nLate in replying to this, have been off-list. This also applies for your\nv4.\n\nThe current version you have still doesn't explain the \"Why would we\nredundantly rebase every time in this case...\" question I had in\nhttps://public-inbox.org/git/87tvfma8yt.fsf@evledraar.gmail.com/\n\nI *think* it's closer to \"it was easier to implement this in terms of\n--onto, which happens to behave that way now\" than \"it must work this\nway for --keep-base\", which is fair enough.\n\nAlthough I see when I forward-port my POC patch from that E-Mail that\none test fails now, which is good, that wasn't the case before, but it\nlooks like that might be testing something else than just the lazy\nbehavior.\n\nIt would be good to know in terms of commit message or (better) explicit\ntests so that if we teach these various rebase modes the same lazyness\n--fork-point uses in the future it's clear if that's OK or not.\n"},{"id":"373339","messageId":"20190406203800.GA6147@archbookpro.localdomain","threadId":"50810","inReplyTo":"87ftquapfy.fsf@evledraar.gmail.com","subject":"Re: [PATCH v3 0/4] rebase: teach rebase --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-06T20:38:00Z","receivedAt":"2019-04-06T20:38:06Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"On Sat, Apr 06, 2019 at 09:44:49PM +0200, Ævar Arnfjörð Bjarmason wrote:\n> \n> On Mon, Apr 01 2019, Denton Liu wrote:\n> \n> > Thanks again for your feedback, Ævar! I think we're both on the same page now.\n> > Hopefully I've addressed all of your high-level concerns with this patchset and\n> > we can move into a discussion on implementation detail.\n> \n> Late in replying to this, have been off-list. This also applies for your\n> v4.\n> \n> The current version you have still doesn't explain the \"Why would we\n> redundantly rebase every time in this case...\" question I had in\n> https://public-inbox.org/git/87tvfma8yt.fsf@evledraar.gmail.com/\n> \n> I *think* it's closer to \"it was easier to implement this in terms of\n> --onto, which happens to behave that way now\" than \"it must work this\n> way for --keep-base\", which is fair enough.\n\nCorrect, the reason why --keep-base was not lazy initially was because\n\"--onto did it that way\". You are correct in that --keep-base should be\nlazily rebasing so I changed --onto's behaviour in 3/4 because it would\nalso benefit from laziness. Thus, now --keep-base lazily rebases because\n--onto also does.\n\n> \n> Although I see when I forward-port my POC patch from that E-Mail that\n> one test fails now, which is good, that wasn't the case before, but it\n> looks like that might be testing something else than just the lazy\n> behavior.\n\nThe test fails because the patch disables fork_point if --keep-base is\nset. So, with the patch applied, C is rebased even though it is excluded\nwhen fork_point is set.\n\n> \n> It would be good to know in terms of commit message or (better) explicit\n> tests so that if we teach these various rebase modes the same lazyness\n> --fork-point uses in the future it's clear if that's OK or not.\n\nSorry, could you please clarify what you mean by the \"lazyness\n--fork-point uses\"? I don't understand what laziness is introduced by\nusing --fork-point. Also, are the tests in t3432 not sufficient for\ntesting fast-forwarding (aka lazy) behaviour?\n\nThanks,\n\nDenton\n"},{"id":"373392","messageId":"xmqq4l79az7j.fsf@gitster-ct.c.googlers.com","threadId":"50810","inReplyTo":"20190405111513.GU32732@szeder.dev","subject":"Re: [PATCH v3 1/4] t3431: add rebase --fork-point tests","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-04-08T04:38:24Z","receivedAt":"2019-04-08T04:38:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"SZEDER Gábor <szeder.dev@gmail.com> writes:\n\n>> +\ttest_expect_success \"git rebase $@\" \"\n>> +...\n>> +\t\tgit rebase $@ &&\n>> +...\n>> +\t\"\n>> +}\n>> +\n>> +test_rebase 'G F E D B A' ''\n>\n> It appears that this last empty argument triggers some bug in Bash\n> v4.2 and older (and on macOS such an old Bash is the default /bin/sh),\n> as it turns that empty argument into something else, which in turn\n> fails the test with:\n>\n>   <...>\n>   ++ git rebase $'\\177'\n>   fatal: invalid upstream '?'\n>   error: last command exited with $?=128\n>   not ok 2 - git rebase\n>\n> https://travis-ci.org/git/git/jobs/516070862#L2276\n\nYeah, every time I see $@ that appears in any form other than \"$@\"\n(i.e. within a pair of double-quotes without anything else in it),\nit makes me feel very uneasy.  Shouldn't the argument to the above\n\"rebase\" be spelled $* instead?  I somehow do not think use of $@\nthere is buying us anything.\n\nOf course, if we were really passing an arg with $IFS character in\nit, we could probably eval 'git rebase \"$@\"' it (with appropriate\nquoting to adjust for the surrounding quote pair).\n\n> Omitting that empty argument avoids this issue, and the test still\n> checks what it was supposed to.\n>\n>> +test_rebase 'G F D B A' '--onto D'\n>> +test_rebase 'G F C E D B A' '--no-fork-point'\n>> +test_rebase 'G F C D B A' '--no-fork-point --onto D'\n>> +test_rebase 'G F E D B A' '--fork-point refs/heads/master'\n>> +test_rebase 'G F D B A' '--fork-point --onto D refs/heads/master'\n>> +test_rebase 'G F C E D B A' 'refs/heads/master'\n>> +test_rebase 'G F C D B A' '--onto D refs/heads/master'\n>> +\n>> +test_done\n>> -- \n>> 2.21.0.695.gaf8658f249\n>> \n"},{"id":"373802","messageId":"8736mlfw6k.fsf@evledraar.gmail.com","threadId":"50810","inReplyTo":"20190406203800.GA6147@archbookpro.localdomain","subject":"Re: [PATCH v3 0/4] rebase: teach rebase --keep-base","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-04-13T21:10:43Z","receivedAt":"2019-04-13T21:10:48Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Sat, Apr 06 2019, Denton Liu wrote:\n\n> On Sat, Apr 06, 2019 at 09:44:49PM +0200, Ævar Arnfjörð Bjarmason wrote:\n>>\n>> On Mon, Apr 01 2019, Denton Liu wrote:\n>>\n>> > Thanks again for your feedback, Ævar! I think we're both on the same page now.\n>> > Hopefully I've addressed all of your high-level concerns with this patchset and\n>> > we can move into a discussion on implementation detail.\n>>\n>> Late in replying to this, have been off-list. This also applies for your\n>> v4.\n>>\n>> The current version you have still doesn't explain the \"Why would we\n>> redundantly rebase every time in this case...\" question I had in\n>> https://public-inbox.org/git/87tvfma8yt.fsf@evledraar.gmail.com/\n>>\n>> I *think* it's closer to \"it was easier to implement this in terms of\n>> --onto, which happens to behave that way now\" than \"it must work this\n>> way for --keep-base\", which is fair enough.\n>\n> Correct, the reason why --keep-base was not lazy initially was because\n> \"--onto did it that way\". You are correct in that --keep-base should be\n> lazily rebasing so I changed --onto's behaviour in 3/4 because it would\n> also benefit from laziness. Thus, now --keep-base lazily rebases because\n> --onto also does.\n>\n>>\n>> Although I see when I forward-port my POC patch from that E-Mail that\n>> one test fails now, which is good, that wasn't the case before, but it\n>> looks like that might be testing something else than just the lazy\n>> behavior.\n>\n> The test fails because the patch disables fork_point if --keep-base is\n> set. So, with the patch applied, C is rebased even though it is excluded\n> when fork_point is set.\n>\n>>\n>> It would be good to know in terms of commit message or (better) explicit\n>> tests so that if we teach these various rebase modes the same lazyness\n>> --fork-point uses in the future it's clear if that's OK or not.\n>\n> Sorry, could you please clarify what you mean by the \"lazyness\n> --fork-point uses\"? I don't understand what laziness is introduced by\n> using --fork-point. Also, are the tests in t3432 not sufficient for\n> testing fast-forwarding (aka lazy) behaviour?\n\nLate reply, sorry. I mean if I do e.g. on git.git:\n\n    git checkout -b avar/test 041f5ea1cf\n\nI.e. branch of Junio's \"The third batch\" (we're now on the 4th) and\nthen:\n\n    git branch --set-upstream-to origin/master\n\nAnd commit a dummy change, I'm now:\n\n    On branch avar/test\n    Your branch and 'origin/master' have diverged,\n    and have 1 and 33 different commits each, respectively.\n\nThen I run a --keep-base rebase twice:\n\n    $ git log --oneline --no-decorate -2; ~/g/git/git --exec-path=$PWD rebase --keep-base; git rev-parse HEAD\n    fc3e916c5f foo\n    041f5ea1cf The third batch\n    First, rewinding head to replay your work on top of it...\n    Applying: foo\n    b10e672074dfee6b6e8b2901c9bb49f856a13708\n    $ git log --oneline --no-decorate -2; ~/g/git/git --exec-path=$PWD rebase --keep-base; git rev-parse HEAD\n    b10e672074 foo\n    041f5ea1cf The third batch\n    First, rewinding head to replay your work on top of it...\n    Applying: foo\n    fd3029e73b89f5a799653ff17199d00f2a6ee2e2\n\nI.e. I'll keep on getting a new commit, even though by any criteria I\ncan think of for this type of case there's no work to do. I.e. we're\nalready based on 041f5ea1cf, no need to rebase again.\n\nOf course this is also currently the case with --fork-point without\nargument, which'll settle on (note fourth, not third):\n\n    $ git log --oneline --no-decorate -2; ~/g/git/git --exec-path=$PWD rebase --fork-point; git rev-parse HEAD\n    85a1861cec foo\n    e35b8cb8e2 The fourth batch\n    First, rewinding head to replay your work on top of it...\n    Applying: foo\n    c6046e97af29d71bf6270080acf188c095e0cb7c\n    $ git log --oneline --no-decorate -2; ~/g/git/git --exec-path=$PWD rebase --fork-point; git rev-parse HEAD\n    c6046e97af foo\n    e35b8cb8e2 The fourth batch\n    First, rewinding head to replay your work on top of it...\n    Applying: foo\n    672d22d58e9aa9b6a28054531c21e1f1b598b013\n\nWhereas a --keep-base in *this* case should be identical to:\n\n    $ 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\n    fc3e916c5f foo\n    041f5ea1cf The third batch\n    Current branch avar/test is up to date.\n    fc3e916c5fc707cfc83f28b3faca81046306b305\n\nThe reason I'm asking is that as noted in the thread starting at\nhttps://public-inbox.org/git/20190224101029.GA13438@sigill.intra.peff.net/\nwe used to in *some* cases do the same for:\n\n    rebase $(git merge-base --fork-point origin/master HEAD)\n\nAs for just:\n\n    rebase --fork-point\n\nI.e. say \"Current branch is up-to-date\". I'm planning to loop back to\nthat and fix it for the --fork-point case. So I was wondering if you\ncould think of a reason for why --keep-base couldn't also do the same,\nor if there was some intrinsic difference I'm missing.\n\nAnyway. Don't worry about it. When I poke at it it'll likely shake out\nof the respective tests.\n\nI was just wondering if you having looked at many of the same things\nrecently could understand my ramblings and knew if this was a case of\n\"yup, we could add a bit more to that can_fast_forward(...) case and\nmake it lazy too\", or \"no, --keep-base is special for reasons you're\nmissing...\".\n"},{"id":"373919","messageId":"cover.1555366891.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1554500051.git.liu.denton@gmail.com","subject":"[PATCH v5 0/5] rebase: teach rebase --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-15T22:29:18Z","receivedAt":"2019-04-15T22:29:23Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Thanks for the example, Ævar, it clarified a lot. I think that _now_\nwe're on the same page. ;)\n\nI made can_fast_forward detect this case and now, it should behave as\nexpected. The change can be seen in 4/5. Hopefully this saves you some\nwork later.\n\n---\n\nThis patchset now depends \"[PATCH 1/8] tests (rebase): spell out the\n`--keep-empty` option\" which is the first patch of Johannes's \"Do not\nuse abbreviated options in tests\" patchset[1]. (Thanks for catching\nthat, Johannes!)\n\nChanges since v1:\n\n* Squashed old set into one patch\n* Fixed indentation style and dangling else\n* Added more documentation after discussion with Ævar\n\nChanges since v2:\n\n* Add testing for rebase --fork-point behaviour\n* Add testing for rebase fast-forward behaviour\n* Make rebase --onto fast-forward in more cases\n* Update documentation to include use-case\n\nChanges since v3:\n\n* Fix tests failing on bash 4.2\n* Fix typo in t3431 comment\n\nChanges since v4:\n\n* Make rebase --fork-point fast-forward in more cases\n\n[1]: https://public-inbox.org/git/a1b4b74b9167e279dae4cd8c58fb28d8a714a66a.1553537656.git.gitgitgadget@gmail.com/\n\nDenton Liu (5):\n  t3431: add rebase --fork-point tests\n  t3432: test rebase fast-forward behavior\n  rebase: fast-forward --onto in more cases\n  rebase: fast-forward --fork-point in more cases\n  rebase: teach rebase --keep-base\n\n Documentation/git-rebase.txt     | 30 ++++++++++--\n builtin/rebase.c                 | 77 +++++++++++++++++++++--------\n t/t3400-rebase.sh                |  2 +-\n t/t3404-rebase-interactive.sh    |  2 +-\n t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++\n t/t3431-rebase-fork-point.sh     | 57 ++++++++++++++++++++++\n t/t3432-rebase-fast-forward.sh   | 83 ++++++++++++++++++++++++++++++++\n 7 files changed, 284 insertions(+), 24 deletions(-)\n create mode 100755 t/t3431-rebase-fork-point.sh\n create mode 100755 t/t3432-rebase-fast-forward.sh\n\n-- \n2.21.0.921.gb27c68c4e9\n\n"},{"id":"373920","messageId":"0f1e9ac5c8b88649a378f676ee924a966ce0693c.1555366891.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1555366891.git.liu.denton@gmail.com","subject":"[PATCH v5 1/5] t3431: add rebase --fork-point tests","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-15T22:29:20Z","receivedAt":"2019-04-15T22:29:25Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Signed-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3431-rebase-fork-point.sh | 53 ++++++++++++++++++++++++++++++++++++\n 1 file changed, 53 insertions(+)\n create mode 100755 t/t3431-rebase-fork-point.sh\n\ndiff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\nnew file mode 100755\nindex 0000000000..6d523123d0\n--- /dev/null\n+++ b/t/t3431-rebase-fork-point.sh\n@@ -0,0 +1,53 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2019 Denton Liu\n+#\n+\n+test_description='git rebase --fork-point test'\n+\n+. ./test-lib.sh\n+\n+# A---B---D---E       (master)\n+#     \\\n+#      C*---F---G (side)\n+#\n+# C was formerly part of master but master was rewound to remove C\n+#\n+test_expect_success setup '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\ttest_commit C &&\n+\tgit branch -t side &&\n+\tgit reset --hard HEAD^ &&\n+\ttest_commit D &&\n+\ttest_commit E &&\n+\tgit checkout side &&\n+\ttest_commit F &&\n+\ttest_commit G\n+'\n+\n+test_rebase() {\n+\texpected=\"$1\" &&\n+\tshift &&\n+\ttest_expect_success \"git rebase $*\" \"\n+\t\tgit checkout master &&\n+\t\tgit reset --hard E &&\n+\t\tgit checkout side &&\n+\t\tgit reset --hard G &&\n+\t\tgit rebase $* &&\n+\t\ttest_write_lines $expected >expect &&\n+\t\tgit log --pretty=%s >actual &&\n+\t\ttest_cmp expect actual\n+\t\"\n+}\n+\n+test_rebase 'G F E D B A'\n+test_rebase 'G F D B A' --onto D\n+test_rebase 'G F C E D B A' --no-fork-point\n+test_rebase 'G F C D B A' --no-fork-point --onto D\n+test_rebase 'G F E D B A' --fork-point refs/heads/master\n+test_rebase 'G F D B A' --fork-point --onto D refs/heads/master\n+test_rebase 'G F C E D B A' refs/heads/master\n+test_rebase 'G F C D B A' --onto D refs/heads/master\n+\n+test_done\n-- \n2.21.0.921.gb27c68c4e9\n\n"},{"id":"373921","messageId":"148027a14be9b0ee5dde741e4528255f2ca3577a.1555366891.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1555366891.git.liu.denton@gmail.com","subject":"[PATCH v5 2/5] t3432: test rebase fast-forward behavior","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-15T22:29:23Z","receivedAt":"2019-04-15T22:29:27Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"When rebase is run on a branch that can be fast-forwarded, this should\nautomatically be done. Create test to ensure this behavior happens.\n\nThere are some cases that currently don't pass. The first case is where\na feature and master have diverged, running\n\"git rebase master... master\" causes a full rebase to happen even though\na fast-forward should happen.\n\nThe second case is when we are doing \"git rebase --fork-point\" and a\nfork-point commit is found. Once again, a full rebase happens even\nthough a fast-forward should happen.\n\nMark these cases as failure so we can fix it later.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3432-rebase-fast-forward.sh | 72 ++++++++++++++++++++++++++++++++++\n 1 file changed, 72 insertions(+)\n create mode 100755 t/t3432-rebase-fast-forward.sh\n\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nnew file mode 100755\nindex 0000000000..4f04d67fd7\n--- /dev/null\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -0,0 +1,72 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2019 Denton Liu\n+#\n+\n+test_description='ensure rebase fast-forwards commits when possible'\n+\n+. ./test-lib.sh\n+\n+test_expect_success setup '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\ttest_commit C &&\n+\ttest_commit D &&\n+\tgit checkout -t -b side\n+'\n+\n+test_rebase_same_head() {\n+\tstatus=\"$1\" &&\n+\tshift &&\n+\ttest_expect_$status \"git rebase $* with $changes is no-op\" \"\n+\t\toldhead=\\$(git rev-parse HEAD) &&\n+\t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n+\t\tgit rebase $* &&\n+\t\tnewhead=\\$(git rev-parse HEAD) &&\n+\t\ttest_cmp_rev \\$oldhead \\$newhead\n+\t\"\n+}\n+\n+changes='no changes'\n+test_rebase_same_head success\n+test_rebase_same_head success master\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --no-fork-point\n+test_rebase_same_head success --fork-point master\n+test_rebase_same_head failure --fork-point --onto B B\n+test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head success --fork-point --onto master... master\n+\n+test_expect_success 'add work to side' '\n+\ttest_commit E\n+'\n+\n+changes='our changes'\n+test_rebase_same_head success\n+test_rebase_same_head success master\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --no-fork-point\n+test_rebase_same_head success --fork-point master\n+test_rebase_same_head failure --fork-point --onto B B\n+test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head success --fork-point --onto master... master\n+\n+test_expect_success 'add work to upstream' '\n+\tgit checkout master &&\n+\ttest_commit F &&\n+\tgit checkout side\n+'\n+\n+changes='our and their changes'\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head failure --onto master... master\n+test_rebase_same_head failure --fork-point --onto B B\n+test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head failure --fork-point --onto master... master\n+\n+test_done\n-- \n2.21.0.921.gb27c68c4e9\n\n"},{"id":"373922","messageId":"ec55da07191e7f0a1d31342053c1496405ba7d3a.1555366891.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1555366891.git.liu.denton@gmail.com","subject":"[PATCH v5 3/5] rebase: fast-forward --onto in more cases","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-15T22:29:24Z","receivedAt":"2019-04-15T22:29:28Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Before, when we had the following graph,\n\n\tA---B---C (master)\n\t    \\\n\t     D (side)\n\nrunning 'git rebase --onto master... master side' would result in D\nbeing always rebased, no matter what. However, the desired behavior is\nthat rebase should notice that this is fast-forwardable and do that\ninstead.\n\nAdd detection to `can_fast_forward` so that this case can be detected\nand a fast-forward will be performed. First of all, rewrite the function\nto use gotos which simplifies the logic. Next, since the\n\n\toptions.upstream &&\n\t!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)\n\nconditions were removed in `cmd_rebase`, we reintroduce a substitute in\n`can_fast_forward`. In particular, checking the merge bases of\n`upstream` and `head` fixes a failing case in t3416.\n\nThe abbreviated graph for t3416 is as follows:\n\n\t\t    F---G topic\n\t\t   /\n\t  A---B---C---D---E master\n\nand the failing command was\n\n\tgit rebase --onto master...topic F topic\n\nBefore, Git would see that there was one merge base (C), and the merge\nand onto were the same so it would incorrectly return 1, indicating that\nwe could fast-forward. This would cause the rebased graph to be 'ABCFG'\nwhen we were expecting 'ABCG'.\n\nWith the additional logic, we detect that upstream and head's merge base\nis F. Since onto isn't F, it means we're not rebasing the full set of\ncommits from master..topic. Since we're excluding some commits, a\nfast-forward cannot be performed and so we correctly return 0.\n\nAdd '-f' to test cases that failed as a result of this change because\nthey were not expecting a fast-forward so that a rebase is forced.\n\nWhile we're at it, remove a trailing whitespace from rebase.c.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n builtin/rebase.c               | 40 +++++++++++++++++++++++-----------\n t/t3400-rebase.sh              |  2 +-\n t/t3404-rebase-interactive.sh  |  2 +-\n t/t3432-rebase-fast-forward.sh |  4 ++--\n 4 files changed, 31 insertions(+), 17 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 77deebc65c..7aa6a090d4 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -895,12 +895,12 @@ static int is_linear_history(struct commit *from, struct commit *to)\n \treturn 1;\n }\n \n-static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n-\t\t\t    struct object_id *merge_base)\n+static int can_fast_forward(struct commit *onto, struct commit *upstream,\n+\t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n {\n \tstruct commit *head = lookup_commit(the_repository, head_oid);\n-\tstruct commit_list *merge_bases;\n-\tint res;\n+\tstruct commit_list *merge_bases = NULL;\n+\tint res = 0;\n \n \tif (!head)\n \t\treturn 0;\n@@ -908,12 +908,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n \tmerge_bases = get_merge_bases(onto, head);\n \tif (merge_bases && !merge_bases->next) {\n \t\toidcpy(merge_base, &merge_bases->item->object.oid);\n-\t\tres = oideq(merge_base, &onto->object.oid);\n+\t\tif (!oideq(merge_base, &onto->object.oid))\n+\t\t\tgoto done;\n \t} else {\n \t\toidcpy(merge_base, &null_oid);\n-\t\tres = 0;\n+\t\tgoto done;\n \t}\n+\n+\tif (!upstream)\n+\t\tgoto done;\n+\n \tfree_commit_list(merge_bases);\n+\tmerge_bases = get_merge_bases(upstream, head);\n+\tif (merge_bases && !merge_bases->next) {\n+\t\tif (!oideq(&onto->object.oid, &merge_bases->item->object.oid))\n+\t\t\tgoto done;\n+\t} else\n+\t\tgoto done;\n+\n+\tres = 1;\n+\n+done:\n+\tif (merge_bases)\n+\t\tfree_commit_list(merge_bases);\n \treturn res && is_linear_history(onto, head);\n }\n \n@@ -1682,13 +1699,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \t/*\n \t * Check if we are already based on onto with linear history,\n-\t * but this should be done only when upstream and onto are the same\n-\t * and if this is not an interactive rebase.\n+\t * but this should be done if this is not an interactive rebase.\n \t */\n-\tif (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&\n-\t    !is_interactive(&options) && !options.restrict_revision &&\n-\t    options.upstream &&\n-\t    !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {\n+\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head, &merge_base) &&\n+\t    !is_interactive(&options) && !options.restrict_revision) {\n \t\tint flag;\n \n \t\tif (!(options.flags & REBASE_FORCE)) {\n@@ -1782,7 +1796,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tstrbuf_addf(&msg, \"%s: checkout %s\",\n \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);\n \tif (reset_head(&options.onto->object.oid, \"checkout\", NULL,\n-\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD | \n+\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n \t\t       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n \t\t       NULL, msg.buf))\n \t\tdie(_(\"Could not detach HEAD\"));\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 460d0523be..604d624ff8 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' '\n \t\techo two >>init.t &&\n \t\tgit commit -a -m two &&\n \t\tgit tag two &&\n-\t\ttest_must_fail git rebase --onto init HEAD^ &&\n+\t\ttest_must_fail git rebase -f --onto init HEAD^ &&\n \t\tGIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&\n \t\tgrep \"show.*$(git rev-parse two)\" stderr\n \t)\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex b60b11f9f2..f054186cc7 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int\n \tgit reset --hard &&\n \tgit checkout conflict-branch &&\n \tset_fake_editor &&\n-\ttest_must_fail git rebase --onto HEAD~2 HEAD~ &&\n+\ttest_must_fail git rebase -f --onto HEAD~2 HEAD~ &&\n \ttest_must_fail git rebase --edit-todo &&\n \tgit rebase --abort\n '\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 4f04d67fd7..d0e5b1f3e6 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -64,9 +64,9 @@ test_expect_success 'add work to upstream' '\n changes='our and their changes'\n test_rebase_same_head success --onto B B\n test_rebase_same_head success --onto B... B\n-test_rebase_same_head failure --onto master... master\n+test_rebase_same_head success --onto master... master\n test_rebase_same_head failure --fork-point --onto B B\n test_rebase_same_head failure --fork-point --onto B... B\n-test_rebase_same_head failure --fork-point --onto master... master\n+test_rebase_same_head success --fork-point --onto master... master\n \n test_done\n-- \n2.21.0.921.gb27c68c4e9\n\n"},{"id":"373923","messageId":"2256a902c17fb70ddedeb65f5d0395c5767800f2.1555366891.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1555366891.git.liu.denton@gmail.com","subject":"[PATCH v5 4/5] rebase: fast-forward --fork-point in more cases","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-15T22:29:25Z","receivedAt":"2019-04-15T22:29:29Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Before, when we rebased with a --fork-point invocation where the\nfork-point wasn't empty, we would be setting options.restrict_revision.\nThe fast-forward logic would automatically declare that the rebase was\nnot fast-forwardable if it was set. However, this was painting with a\nvery broad brush.\n\nRefine the logic so that we can fast-forward in the case where the\nrestricted revision is equal to the merge base, since we stop rebasing\nat the merge base anyway.\n\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n builtin/rebase.c               |  9 +++++++--\n t/t3432-rebase-fast-forward.sh | 12 ++++++------\n 2 files changed, 13 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 7aa6a090d4..5f00d91b68 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -896,6 +896,7 @@ static int is_linear_history(struct commit *from, struct commit *to)\n }\n \n static int can_fast_forward(struct commit *onto, struct commit *upstream,\n+\t\t\t    struct commit *restrict_revision,\n \t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n {\n \tstruct commit *head = lookup_commit(the_repository, head_oid);\n@@ -915,6 +916,9 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,\n \t\tgoto done;\n \t}\n \n+\tif (restrict_revision && !oideq(&restrict_revision->object.oid, merge_base))\n+\t\tgoto done;\n+\n \tif (!upstream)\n \t\tgoto done;\n \n@@ -1701,8 +1705,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t * Check if we are already based on onto with linear history,\n \t * but this should be done if this is not an interactive rebase.\n \t */\n-\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head, &merge_base) &&\n-\t    !is_interactive(&options) && !options.restrict_revision) {\n+\tif (can_fast_forward(options.onto, options.upstream, options.restrict_revision,\n+\t\t    &options.orig_head, &merge_base) &&\n+\t    !is_interactive(&options)) {\n \t\tint flag;\n \n \t\tif (!(options.flags & REBASE_FORCE)) {\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex d0e5b1f3e6..1cb2896fb4 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -35,8 +35,8 @@ test_rebase_same_head success --onto B... B\n test_rebase_same_head success --onto master... master\n test_rebase_same_head success --no-fork-point\n test_rebase_same_head success --fork-point master\n-test_rebase_same_head failure --fork-point --onto B B\n-test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head success --fork-point --onto B B\n+test_rebase_same_head success --fork-point --onto B... B\n test_rebase_same_head success --fork-point --onto master... master\n \n test_expect_success 'add work to side' '\n@@ -51,8 +51,8 @@ test_rebase_same_head success --onto B... B\n test_rebase_same_head success --onto master... master\n test_rebase_same_head success --no-fork-point\n test_rebase_same_head success --fork-point master\n-test_rebase_same_head failure --fork-point --onto B B\n-test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head success --fork-point --onto B B\n+test_rebase_same_head success --fork-point --onto B... B\n test_rebase_same_head success --fork-point --onto master... master\n \n test_expect_success 'add work to upstream' '\n@@ -65,8 +65,8 @@ changes='our and their changes'\n test_rebase_same_head success --onto B B\n test_rebase_same_head success --onto B... B\n test_rebase_same_head success --onto master... master\n-test_rebase_same_head failure --fork-point --onto B B\n-test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head success --fork-point --onto B B\n+test_rebase_same_head success --fork-point --onto B... B\n test_rebase_same_head success --fork-point --onto master... master\n \n test_done\n-- \n2.21.0.921.gb27c68c4e9\n\n"},{"id":"373924","messageId":"d6e7e1ca4b8d9c644181adef6ab1e4be77722b99.1555366891.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1555366891.git.liu.denton@gmail.com","subject":"[PATCH v5 5/5] rebase: teach rebase --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-15T22:29:27Z","receivedAt":"2019-04-15T22:29:31Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"A common scenario is if a user is working on a topic branch and they\nwish to make some changes to intermediate commits or autosquash, they\nwould run something such as\n\n\tgit rebase -i --onto master... master\n\nin order to preserve the merge base. This is useful when contributing a\npatch series to the Git mailing list, one often starts on top of the\ncurrent 'master'. However, while developing the patches, 'master' is\nalso developed further and it is sometimes not the bst idea to keep\nrebasing on top of 'master', but to keep the base commit as-is.\n\nAlternatively, a user wishing to test individual commits in a topic\nbranch without changing anything may run\n\n\tgit rebase -x ./test.sh master... master\n\nSince rebasing onto the merge base of the branch and the upstream is\nsuch a common case, introduce the --keep-base option as a shortcut.\n\nThis allows us to rewrite the above as\n\n\tgit rebase -i --keep-base master\n\nand\n\n\tgit rebase -x ./test.sh --keep-base master\n\nrespectively.\n\nAdd tests to ensure --keep-base works correctly in the normal case and\nfails when there are multiple merge bases, both in regular and\ninteractive mode. Also, test to make sure conflicting options cause\nrebase to fail. While we're adding test cases, add a missing\nset_fake_editor call to 'rebase -i --onto master...side'.\n\nWhile we're documenting the --keep-base option, change an instance of\n\"merge-base\" to \"merge base\", which is the consistent spelling.\n\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nHelped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n Documentation/git-rebase.txt     | 30 +++++++++++++++--\n builtin/rebase.c                 | 32 ++++++++++++++----\n t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++++++++\n t/t3431-rebase-fork-point.sh     |  4 +++\n t/t3432-rebase-fast-forward.sh   | 11 ++++++\n 5 files changed, 125 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 6363d674b7..569ab708d4 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip\n SYNOPSIS\n --------\n [verse]\n-'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n-\t[<upstream> [<branch>]]\n+'git rebase' [-i | --interactive] [<options>] [--exec <cmd>]\n+\t[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\n 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n \t--root [<branch>]\n 'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch\n@@ -217,6 +217,24 @@ As a special case, you may use \"A\\...B\" as a shortcut for the\n merge base of A and B if there is exactly one merge base. You can\n leave out at most one of A and B, in which case it defaults to HEAD.\n \n+--keep-base::\n+\tSet the starting point at which to create the new commits to the\n+\tmerge base of <upstream> <branch>. Running\n+\t'git rebase --keep-base <upstream> <branch>' is equivalent to\n+\trunning 'git rebase --onto <upstream>... <upstream>'.\n++\n+This option is useful in the case where one is developing a feature on\n+top of an upstream branch. While the feature is being worked on, the\n+upstream branch may advance and it may not be the best idea to keep\n+rebasing on top of the upstream but to keep the base commit as-is.\n++\n+Although both this option and --fork-point find the merge base between\n+<upstream> and <branch>, this option uses the merge base as the _starting\n+point_ on which new commits will be created, whereas --fork-point uses\n+the merge base to determine the _set of commits_ which will be rebased.\n++\n+See also INCOMPATIBLE OPTIONS below.\n+\n <upstream>::\n \tUpstream branch to compare against.  May be any valid commit,\n \tnot just an existing branch name. Defaults to the configured\n@@ -364,6 +382,10 @@ ends up being empty, the <upstream> will be used as a fallback.\n +\n If either <upstream> or --root is given on the command line, then the\n default is `--no-fork-point`, otherwise the default is `--fork-point`.\n++\n+If your branch was based on <upstream> but <upstream> was rewound and\n+your branch contains commits which were dropped, this option can be used\n+with `--keep-base` in order to drop those commits from your branch.\n \n --ignore-whitespace::\n --whitespace=<option>::\n@@ -539,6 +561,8 @@ In addition, the following pairs of options are incompatible:\n  * --preserve-merges and --rebase-merges\n  * --rebase-merges and --strategy\n  * --rebase-merges and --strategy-option\n+ * --keep-base and --onto\n+ * --keep-base and --root\n \n BEHAVIORAL DIFFERENCES\n -----------------------\n@@ -863,7 +887,7 @@ NOTE: While an \"easy case recovery\" sometimes appears to be successful\n       --interactive` will be **resurrected**!\n \n The idea is to manually tell 'git rebase' \"where the old 'subsystem'\n-ended and your 'topic' began\", that is, what the old merge-base\n+ended and your 'topic' began\", that is, what the old merge base\n between them was.  You will have to find a way to name the last commit\n of the old 'subsystem', for example:\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 5f00d91b68..289763651e 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -27,8 +27,8 @@\n #include \"branch.h\"\n \n static char const * const builtin_rebase_usage[] = {\n-\tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n-\t\t\"[<upstream>] [<branch>]\"),\n+\tN_(\"git rebase [-i] [options] [--exec <cmd>] \"\n+\t\t\"[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\"),\n \tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n \t\t\"--root [<branch>]\"),\n \tN_(\"git rebase --continue | --abort | --skip | --edit-todo\"),\n@@ -1039,6 +1039,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t};\n \tconst char *branch_name;\n \tint ret, flags, total_argc, in_progress = 0;\n+\tint keep_base = 0;\n \tint ok_to_skip_pre_rebase = 0;\n \tstruct strbuf msg = STRBUF_INIT;\n \tstruct strbuf revisions = STRBUF_INIT;\n@@ -1072,6 +1073,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"rebase onto given branch instead of upstream\")),\n+\t\tOPT_BOOL(0, \"keep-base\", &keep_base,\n+\t\t\t N_(\"use the merge-base of upstream and branch as the current base\")),\n \t\tOPT_BOOL(0, \"no-verify\", &ok_to_skip_pre_rebase,\n \t\t\t N_(\"allow pre-rebase hook to run\")),\n \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n@@ -1238,6 +1241,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(builtin_rebase_usage,\n \t\t\t\t   builtin_rebase_options);\n \n+\tif (keep_base) {\n+\t\tif (options.onto_name)\n+\t\t\tdie(_(\"cannot combine '--keep-base' with '--onto'\"));\n+\t\tif (options.root)\n+\t\t\tdie(_(\"cannot combine '--keep-base' with '--root'\"));\n+\t}\n+\n \tif (action != NO_ACTION && !in_progress)\n \t\tdie(_(\"No rebase in progress?\"));\n \tsetenv(GIT_REFLOG_ACTION_ENVIRONMENT, \"rebase\", 0);\n@@ -1562,12 +1572,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t}\n \n \t/* Make sure the branch to rebase onto is valid. */\n-\tif (!options.onto_name)\n+\tif (keep_base) {\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addstr(&buf, options.upstream_name);\n+\t\tstrbuf_addstr(&buf, \"...\");\n+\t\toptions.onto_name = xstrdup(buf.buf);\n+\t} else if (!options.onto_name)\n \t\toptions.onto_name = options.upstream_name;\n \tif (strstr(options.onto_name, \"...\")) {\n-\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0)\n-\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n-\t\t\t    options.onto_name);\n+\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0) {\n+\t\t\tif (keep_base)\n+\t\t\t\tdie(_(\"'%s': need exactly one merge base with branch\"),\n+\t\t\t\t    options.upstream_name);\n+\t\t\telse\n+\t\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n+\t\t\t\t    options.onto_name);\n+\t\t}\n \t\toptions.onto = lookup_commit_or_die(&merge_base,\n \t\t\t\t\t\t    options.onto_name);\n \t} else {\ndiff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh\nindex ddf2f64853..9c2548423b 100755\n--- a/t/t3416-rebase-onto-threedots.sh\n+++ b/t/t3416-rebase-onto-threedots.sh\n@@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' '\n \tgit checkout side &&\n \tgit reset --hard K &&\n \n+\tset_fake_editor &&\n \ttest_must_fail git rebase -i --onto master...side J\n '\n \n+test_expect_success 'rebase --keep-base --onto incompatible' '\n+\ttest_must_fail git rebase --keep-base --onto master...\n+'\n+\n+test_expect_success 'rebase --keep-base --root incompatible' '\n+\ttest_must_fail git rebase --keep-base --root\n+'\n+\n+test_expect_success 'rebase --keep-base master from topic' '\n+\tgit reset --hard &&\n+\tgit checkout topic &&\n+\tgit reset --hard G &&\n+\n+\tgit rebase --keep-base master &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base master HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --keep-base master from side' '\n+\tgit reset --hard &&\n+\tgit checkout side &&\n+\tgit reset --hard K &&\n+\n+\ttest_must_fail git rebase --keep-base master\n+'\n+\n+test_expect_success 'rebase -i --keep-base master from topic' '\n+\tgit reset --hard &&\n+\tgit checkout topic &&\n+\tgit reset --hard G &&\n+\n+\tset_fake_editor &&\n+\tEXPECT_COUNT=2 git rebase -i --keep-base master &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base master HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase -i --keep-base master from side' '\n+\tgit reset --hard &&\n+\tgit checkout side &&\n+\tgit reset --hard K &&\n+\n+\tset_fake_editor &&\n+\ttest_must_fail git rebase -i --keep-base master\n+'\n+\n test_done\ndiff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\nindex 6d523123d0..e63040932f 100755\n--- a/t/t3431-rebase-fork-point.sh\n+++ b/t/t3431-rebase-fork-point.sh\n@@ -43,11 +43,15 @@ test_rebase() {\n \n test_rebase 'G F E D B A'\n test_rebase 'G F D B A' --onto D\n+test_rebase 'G F B A' --keep-base\n test_rebase 'G F C E D B A' --no-fork-point\n test_rebase 'G F C D B A' --no-fork-point --onto D\n+test_rebase 'G F C B A' --no-fork-point --keep-base\n test_rebase 'G F E D B A' --fork-point refs/heads/master\n test_rebase 'G F D B A' --fork-point --onto D refs/heads/master\n+test_rebase 'G F B A' --fork-point --keep-base refs/heads/master\n test_rebase 'G F C E D B A' refs/heads/master\n test_rebase 'G F C D B A' --onto D refs/heads/master\n+test_rebase 'G F C B A' --keep-base refs/heads/master\n \n test_done\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 1cb2896fb4..1ba02cc95f 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -33,11 +33,15 @@ test_rebase_same_head success master\n test_rebase_same_head success --onto B B\n test_rebase_same_head success --onto B... B\n test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --keep-base master\n+test_rebase_same_head success --keep-base\n test_rebase_same_head success --no-fork-point\n+test_rebase_same_head success --keep-base --no-fork-point\n test_rebase_same_head success --fork-point master\n test_rebase_same_head success --fork-point --onto B B\n test_rebase_same_head success --fork-point --onto B... B\n test_rebase_same_head success --fork-point --onto master... master\n+test_rebase_same_head success --fork-point --keep-base master\n \n test_expect_success 'add work to side' '\n \ttest_commit E\n@@ -49,11 +53,15 @@ test_rebase_same_head success master\n test_rebase_same_head success --onto B B\n test_rebase_same_head success --onto B... B\n test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --keep-base master\n+test_rebase_same_head success --keep-base\n test_rebase_same_head success --no-fork-point\n+test_rebase_same_head success --keep-base --no-fork-point\n test_rebase_same_head success --fork-point master\n test_rebase_same_head success --fork-point --onto B B\n test_rebase_same_head success --fork-point --onto B... B\n test_rebase_same_head success --fork-point --onto master... master\n+test_rebase_same_head success --fork-point --keep-base master\n \n test_expect_success 'add work to upstream' '\n \tgit checkout master &&\n@@ -65,8 +73,11 @@ changes='our and their changes'\n test_rebase_same_head success --onto B B\n test_rebase_same_head success --onto B... B\n test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --keep-base master\n+test_rebase_same_head success --keep-base\n test_rebase_same_head success --fork-point --onto B B\n test_rebase_same_head success --fork-point --onto B... B\n test_rebase_same_head success --fork-point --onto master... master\n+test_rebase_same_head success --fork-point --keep-base master\n \n test_done\n-- \n2.21.0.921.gb27c68c4e9\n\n"},{"id":"373929","messageId":"xmqqo956pit2.fsf@gitster-ct.c.googlers.com","threadId":"50810","inReplyTo":"ec55da07191e7f0a1d31342053c1496405ba7d3a.1555366891.git.liu.denton@gmail.com","subject":"Re: [PATCH v5 3/5] rebase: fast-forward --onto in more cases","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-04-16T06:26:17Z","receivedAt":"2019-04-16T06:26:23Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n> Before, when we had the following graph,\n>\n> \tA---B---C (master)\n> \t    \\\n> \t     D (side)\n\nThis is minor, but comparing the above with below\n\n> \t\t    F---G topic\n> \t\t   /\n> \t  A---B---C---D---E master\n\nyou'll notice that branches growing downwards in your picture (this\napplies also to an illustration in your tests) are off by one\ncolumn.\n\t\t  D\n                 /\n\tA---B---C\n                 \\\n                  E\n\n> @@ -1682,13 +1699,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \n>  \t/*\n>  \t * Check if we are already based on onto with linear history,\n> -\t * but this should be done only when upstream and onto are the same\n> -\t * and if this is not an interactive rebase.\n> +\t * but this should be done if this is not an interactive rebase.\n>  \t */\n\nTwo issues.\n\n - One is shared with the original (i.e. not a problem with this\n   patch), but what \"this\" refers to is not what has already been\n   stated in the sentence.  We check, to see if we are in a\n   situation where a specific optimization is possible.  But this\n   (== optimization to fast-forward without actually replaying the\n   commit's changes) should be done only under such and such\n   condition.\n\n - The other is more grave. \"should be done if this is not an\n   interactive rebase\" drops \"only\" from \"only if\" in the original,\n   which changes the meaning of the sentence (it can be read as \"we\n   check if we can optimize, but the optimization should be done for\n   rebase-i regardless of the result of that said check\", which is\n   not what you mean).\n\nPerhaps\n\n\tCheck if we are ..., in which case we could fast-forward\n\twithout replacing the commits with new commits recreated by\n\treplaying their changes.  This optimization must not be done\n\tif this is an interactive rebase.\n\n"},{"id":"373972","messageId":"216aa032-d28e-79ba-6bc7-c4bd3de4321d@gmail.com","threadId":"50810","inReplyTo":"ec55da07191e7f0a1d31342053c1496405ba7d3a.1555366891.git.liu.denton@gmail.com","subject":"Re: [PATCH v5 3/5] rebase: fast-forward --onto in more cases","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2019-04-16T13:59:12Z","receivedAt":"2019-04-16T13:59:18Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Denton\n\nIt's good to see rebase fast-forwarding properly when it should\n\nOn 15/04/2019 23:29, Denton Liu wrote:\n> Before, when we had the following graph,\n> \n> \tA---B---C (master)\n> \t    \\\n> \t     D (side)\n> \n> running 'git rebase --onto master... master side' would result in D\n> being always rebased, no matter what. However, the desired behavior is\n> that rebase should notice that this is fast-forwardable and do that\n> instead.\n> \n> Add detection to `can_fast_forward` so that this case can be detected\n> and a fast-forward will be performed. First of all, rewrite the function\n> to use gotos which simplifies the logic. Next, since the\n> \n> \toptions.upstream &&\n> \t!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)\n> \n> conditions were removed in `cmd_rebase`, we reintroduce a substitute in\n> `can_fast_forward`. In particular, checking the merge bases of\n> `upstream` and `head` fixes a failing case in t3416.\n> \n> The abbreviated graph for t3416 is as follows:\n> \n> \t\t    F---G topic\n> \t\t   /\n> \t  A---B---C---D---E master\n> \n> and the failing command was\n> \n> \tgit rebase --onto master...topic F topic\n> \n> Before, Git would see that there was one merge base (C), and the merge\n> and onto were the same so it would incorrectly return 1, indicating that\n> we could fast-forward. This would cause the rebased graph to be 'ABCFG'\n> when we were expecting 'ABCG'.\n> \n> With the additional logic, we detect that upstream and head's merge base\n> is F. Since onto isn't F, it means we're not rebasing the full set of\n> commits from master..topic. Since we're excluding some commits, a\n> fast-forward cannot be performed and so we correctly return 0.\n> \n> Add '-f' to test cases that failed as a result of this change because\n> they were not expecting a fast-forward so that a rebase is forced.\n> \n> While we're at it, remove a trailing whitespace from rebase.c.\n> \n> Signed-off-by: Denton Liu <liu.denton@gmail.com>\n> ---\n>   builtin/rebase.c               | 40 +++++++++++++++++++++++-----------\n>   t/t3400-rebase.sh              |  2 +-\n>   t/t3404-rebase-interactive.sh  |  2 +-\n>   t/t3432-rebase-fast-forward.sh |  4 ++--\n>   4 files changed, 31 insertions(+), 17 deletions(-)\n> \n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index 77deebc65c..7aa6a090d4 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -895,12 +895,12 @@ static int is_linear_history(struct commit *from, struct commit *to)\n>   \treturn 1;\n>   }\n>   \n> -static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n> -\t\t\t    struct object_id *merge_base)\n> +static int can_fast_forward(struct commit *onto, struct commit *upstream,\n> +\t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n>   {\n>   \tstruct commit *head = lookup_commit(the_repository, head_oid);\n> -\tstruct commit_list *merge_bases;\n> -\tint res;\n> +\tstruct commit_list *merge_bases = NULL;\n> +\tint res = 0;\n>   \n>   \tif (!head)\n>   \t\treturn 0;\n> @@ -908,12 +908,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n>   \tmerge_bases = get_merge_bases(onto, head);\n>   \tif (merge_bases && !merge_bases->next) {\n>   \t\toidcpy(merge_base, &merge_bases->item->object.oid);\n> -\t\tres = oideq(merge_base, &onto->object.oid);\n> +\t\tif (!oideq(merge_base, &onto->object.oid))\n> +\t\t\tgoto done;\n>   \t} else {\n>   \t\toidcpy(merge_base, &null_oid);\n> -\t\tres = 0;\n> +\t\tgoto done;\n>   \t}\n> +\n> +\tif (!upstream)\n> +\t\tgoto done;\n> +\n>   \tfree_commit_list(merge_bases);\n> +\tmerge_bases = get_merge_bases(upstream, head);\n> +\tif (merge_bases && !merge_bases->next) {\n> +\t\tif (!oideq(&onto->object.oid, &merge_bases->item->object.oid))\n> +\t\t\tgoto done;\n> +\t} else\n> +\t\tgoto done;\n> +\n> +\tres = 1;\n> +\n> +done:\n> +\tif (merge_bases)\n> +\t\tfree_commit_list(merge_bases);\n>   \treturn res && is_linear_history(onto, head);\n>   }\n>   \n\nI had a hard time following all those gotos. When you 'goto done' in \nboth branches of an if statement it is hard to work out which cases fall \nthrough to the rest of the code. If I've understood it correctly I think \nit is clearer as\n\n         merge_bases = get_merge_bases(onto, head);\n         if (merge_bases && !merge_bases->next) {\n                 oidcpy(merge_base, &merge_bases->item->object.oid);\n                 if (oideq(merge_base, &onto->object.oid) && upstream) {\n                         free_commit_list(merge_bases);\n                         merge_bases = get_merge_bases(upstream, head);\n                         if (merge_bases && !merge_bases->next)\n                                 if (oideq(&onto->object.oid,\n                                            &merge_bases->item->object.oid))\n                                         res = 1;\n                 }\n         } else {\n                 oidcpy(merge_base, &null_oid);\n         }\n\n         if (merge_bases)\n                 free_commit_list(merge_bases);\n         return res && is_linear_history(onto, head);\n}\n\nThe nested if's aren't great but I think it is easier to follow\n\n> @@ -1682,13 +1699,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>   \n>   \t/*\n>   \t * Check if we are already based on onto with linear history,\n> -\t * but this should be done only when upstream and onto are the same\n> -\t * and if this is not an interactive rebase.\n> +\t * but this should be done if this is not an interactive rebase.\n>   \t */\n> -\tif (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&\n> -\t    !is_interactive(&options) && !options.restrict_revision &&\n> -\t    options.upstream &&\n> -\t    !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {\n> +\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head, &merge_base) &&\n\nThis is rather long, perhaps break the argument list\n\nBest Wishes\n\nPhillip\n> +\t    !is_interactive(&options) && !options.restrict_revision) {\n>   \t\tint flag;\n>   \n>   \t\tif (!(options.flags & REBASE_FORCE)) {\n> @@ -1782,7 +1796,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>   \tstrbuf_addf(&msg, \"%s: checkout %s\",\n>   \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);\n>   \tif (reset_head(&options.onto->object.oid, \"checkout\", NULL,\n> -\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n> +\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n>   \t\t       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n>   \t\t       NULL, msg.buf))\n>   \t\tdie(_(\"Could not detach HEAD\"));\n> diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\n> index 460d0523be..604d624ff8 100755\n> --- a/t/t3400-rebase.sh\n> +++ b/t/t3400-rebase.sh\n> @@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' '\n>   \t\techo two >>init.t &&\n>   \t\tgit commit -a -m two &&\n>   \t\tgit tag two &&\n> -\t\ttest_must_fail git rebase --onto init HEAD^ &&\n> +\t\ttest_must_fail git rebase -f --onto init HEAD^ &&\n>   \t\tGIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&\n>   \t\tgrep \"show.*$(git rev-parse two)\" stderr\n>   \t)\n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index b60b11f9f2..f054186cc7 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int\n>   \tgit reset --hard &&\n>   \tgit checkout conflict-branch &&\n>   \tset_fake_editor &&\n> -\ttest_must_fail git rebase --onto HEAD~2 HEAD~ &&\n> +\ttest_must_fail git rebase -f --onto HEAD~2 HEAD~ &&\n>   \ttest_must_fail git rebase --edit-todo &&\n>   \tgit rebase --abort\n>   '\n> diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\n> index 4f04d67fd7..d0e5b1f3e6 100755\n> --- a/t/t3432-rebase-fast-forward.sh\n> +++ b/t/t3432-rebase-fast-forward.sh\n> @@ -64,9 +64,9 @@ test_expect_success 'add work to upstream' '\n>   changes='our and their changes'\n>   test_rebase_same_head success --onto B B\n>   test_rebase_same_head success --onto B... B\n> -test_rebase_same_head failure --onto master... master\n> +test_rebase_same_head success --onto master... master\n>   test_rebase_same_head failure --fork-point --onto B B\n>   test_rebase_same_head failure --fork-point --onto B... B\n> -test_rebase_same_head failure --fork-point --onto master... master\n> +test_rebase_same_head success --fork-point --onto master... master\n>   \n>   test_done\n> \n"},{"id":"374001","messageId":"20190417064420.GA12818@archbookpro.localdomain","threadId":"50810","inReplyTo":"216aa032-d28e-79ba-6bc7-c4bd3de4321d@gmail.com","subject":"Re: [PATCH v5 3/5] rebase: fast-forward --onto in more cases","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-17T06:44:20Z","receivedAt":"2019-04-17T06:44:25Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"On Tue, Apr 16, 2019 at 02:59:12PM +0100, Phillip Wood wrote:\n> Hi Denton\n> \n> It's good to see rebase fast-forwarding properly when it should\n> \n> On 15/04/2019 23:29, Denton Liu wrote:\n> > Before, when we had the following graph,\n> > \n> > \tA---B---C (master)\n> > \t    \\\n> > \t     D (side)\n> > \n> > running 'git rebase --onto master... master side' would result in D\n> > being always rebased, no matter what. However, the desired behavior is\n> > that rebase should notice that this is fast-forwardable and do that\n> > instead.\n> > \n> > Add detection to `can_fast_forward` so that this case can be detected\n> > and a fast-forward will be performed. First of all, rewrite the function\n> > to use gotos which simplifies the logic. Next, since the\n> > \n> > \toptions.upstream &&\n> > \t!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)\n> > \n> > conditions were removed in `cmd_rebase`, we reintroduce a substitute in\n> > `can_fast_forward`. In particular, checking the merge bases of\n> > `upstream` and `head` fixes a failing case in t3416.\n> > \n> > The abbreviated graph for t3416 is as follows:\n> > \n> > \t\t    F---G topic\n> > \t\t   /\n> > \t  A---B---C---D---E master\n> > \n> > and the failing command was\n> > \n> > \tgit rebase --onto master...topic F topic\n> > \n> > Before, Git would see that there was one merge base (C), and the merge\n> > and onto were the same so it would incorrectly return 1, indicating that\n> > we could fast-forward. This would cause the rebased graph to be 'ABCFG'\n> > when we were expecting 'ABCG'.\n> > \n> > With the additional logic, we detect that upstream and head's merge base\n> > is F. Since onto isn't F, it means we're not rebasing the full set of\n> > commits from master..topic. Since we're excluding some commits, a\n> > fast-forward cannot be performed and so we correctly return 0.\n> > \n> > Add '-f' to test cases that failed as a result of this change because\n> > they were not expecting a fast-forward so that a rebase is forced.\n> > \n> > While we're at it, remove a trailing whitespace from rebase.c.\n> > \n> > Signed-off-by: Denton Liu <liu.denton@gmail.com>\n> > ---\n> >   builtin/rebase.c               | 40 +++++++++++++++++++++++-----------\n> >   t/t3400-rebase.sh              |  2 +-\n> >   t/t3404-rebase-interactive.sh  |  2 +-\n> >   t/t3432-rebase-fast-forward.sh |  4 ++--\n> >   4 files changed, 31 insertions(+), 17 deletions(-)\n> > \n> > diff --git a/builtin/rebase.c b/builtin/rebase.c\n> > index 77deebc65c..7aa6a090d4 100644\n> > --- a/builtin/rebase.c\n> > +++ b/builtin/rebase.c\n> > @@ -895,12 +895,12 @@ static int is_linear_history(struct commit *from, struct commit *to)\n> >   \treturn 1;\n> >   }\n> > -static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n> > -\t\t\t    struct object_id *merge_base)\n> > +static int can_fast_forward(struct commit *onto, struct commit *upstream,\n> > +\t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n> >   {\n> >   \tstruct commit *head = lookup_commit(the_repository, head_oid);\n> > -\tstruct commit_list *merge_bases;\n> > -\tint res;\n> > +\tstruct commit_list *merge_bases = NULL;\n> > +\tint res = 0;\n> >   \tif (!head)\n> >   \t\treturn 0;\n> > @@ -908,12 +908,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n> >   \tmerge_bases = get_merge_bases(onto, head);\n> >   \tif (merge_bases && !merge_bases->next) {\n> >   \t\toidcpy(merge_base, &merge_bases->item->object.oid);\n> > -\t\tres = oideq(merge_base, &onto->object.oid);\n> > +\t\tif (!oideq(merge_base, &onto->object.oid))\n> > +\t\t\tgoto done;\n> >   \t} else {\n> >   \t\toidcpy(merge_base, &null_oid);\n> > -\t\tres = 0;\n> > +\t\tgoto done;\n> >   \t}\n> > +\n> > +\tif (!upstream)\n> > +\t\tgoto done;\n> > +\n> >   \tfree_commit_list(merge_bases);\n> > +\tmerge_bases = get_merge_bases(upstream, head);\n> > +\tif (merge_bases && !merge_bases->next) {\n> > +\t\tif (!oideq(&onto->object.oid, &merge_bases->item->object.oid))\n> > +\t\t\tgoto done;\n> > +\t} else\n> > +\t\tgoto done;\n> > +\n> > +\tres = 1;\n> > +\n> > +done:\n> > +\tif (merge_bases)\n> > +\t\tfree_commit_list(merge_bases);\n> >   \treturn res && is_linear_history(onto, head);\n> >   }\n> \n> I had a hard time following all those gotos. When you 'goto done' in both\n> branches of an if statement it is hard to work out which cases fall through\n> to the rest of the code. If I've understood it correctly I think it is\n> clearer as\n> \n>         merge_bases = get_merge_bases(onto, head);\n>         if (merge_bases && !merge_bases->next) {\n>                 oidcpy(merge_base, &merge_bases->item->object.oid);\n>                 if (oideq(merge_base, &onto->object.oid) && upstream) {\n>                         free_commit_list(merge_bases);\n>                         merge_bases = get_merge_bases(upstream, head);\n>                         if (merge_bases && !merge_bases->next)\n>                                 if (oideq(&onto->object.oid,\n>                                            &merge_bases->item->object.oid))\n>                                         res = 1;\n>                 }\n>         } else {\n>                 oidcpy(merge_base, &null_oid);\n>         }\n> \n>         if (merge_bases)\n>                 free_commit_list(merge_bases);\n>         return res && is_linear_history(onto, head);\n> }\n> \n> The nested if's aren't great but I think it is easier to follow\n\nI am pretty impartial between gotos and ifs. If no one else has any\nstrong opinions between the two, I'll reroll with ifs.\n\n> \n> > @@ -1682,13 +1699,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n> >   \t/*\n> >   \t * Check if we are already based on onto with linear history,\n> > -\t * but this should be done only when upstream and onto are the same\n> > -\t * and if this is not an interactive rebase.\n> > +\t * but this should be done if this is not an interactive rebase.\n> >   \t */\n> > -\tif (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&\n> > -\t    !is_interactive(&options) && !options.restrict_revision &&\n> > -\t    options.upstream &&\n> > -\t    !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {\n> > +\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head, &merge_base) &&\n> \n> This is rather long, perhaps break the argument list\n\nThanks, will do.\n\n> \n> Best Wishes\n> \n> Phillip\n> > +\t    !is_interactive(&options) && !options.restrict_revision) {\n> >   \t\tint flag;\n> >   \t\tif (!(options.flags & REBASE_FORCE)) {\n> > @@ -1782,7 +1796,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n> >   \tstrbuf_addf(&msg, \"%s: checkout %s\",\n> >   \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);\n> >   \tif (reset_head(&options.onto->object.oid, \"checkout\", NULL,\n> > -\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n> > +\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n> >   \t\t       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n> >   \t\t       NULL, msg.buf))\n> >   \t\tdie(_(\"Could not detach HEAD\"));\n> > diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\n> > index 460d0523be..604d624ff8 100755\n> > --- a/t/t3400-rebase.sh\n> > +++ b/t/t3400-rebase.sh\n> > @@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' '\n> >   \t\techo two >>init.t &&\n> >   \t\tgit commit -a -m two &&\n> >   \t\tgit tag two &&\n> > -\t\ttest_must_fail git rebase --onto init HEAD^ &&\n> > +\t\ttest_must_fail git rebase -f --onto init HEAD^ &&\n> >   \t\tGIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&\n> >   \t\tgrep \"show.*$(git rev-parse two)\" stderr\n> >   \t)\n> > diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> > index b60b11f9f2..f054186cc7 100755\n> > --- a/t/t3404-rebase-interactive.sh\n> > +++ b/t/t3404-rebase-interactive.sh\n> > @@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int\n> >   \tgit reset --hard &&\n> >   \tgit checkout conflict-branch &&\n> >   \tset_fake_editor &&\n> > -\ttest_must_fail git rebase --onto HEAD~2 HEAD~ &&\n> > +\ttest_must_fail git rebase -f --onto HEAD~2 HEAD~ &&\n> >   \ttest_must_fail git rebase --edit-todo &&\n> >   \tgit rebase --abort\n> >   '\n> > diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\n> > index 4f04d67fd7..d0e5b1f3e6 100755\n> > --- a/t/t3432-rebase-fast-forward.sh\n> > +++ b/t/t3432-rebase-fast-forward.sh\n> > @@ -64,9 +64,9 @@ test_expect_success 'add work to upstream' '\n> >   changes='our and their changes'\n> >   test_rebase_same_head success --onto B B\n> >   test_rebase_same_head success --onto B... B\n> > -test_rebase_same_head failure --onto master... master\n> > +test_rebase_same_head success --onto master... master\n> >   test_rebase_same_head failure --fork-point --onto B B\n> >   test_rebase_same_head failure --fork-point --onto B... B\n> > -test_rebase_same_head failure --fork-point --onto master... master\n> > +test_rebase_same_head success --fork-point --onto master... master\n> >   test_done\n> > \n"},{"id":"374036","messageId":"a6e2f500-7106-b828-d4a6-132aceabcc5e@gmail.com","threadId":"50810","inReplyTo":"20190417064420.GA12818@archbookpro.localdomain","subject":"Re: [PATCH v5 3/5] rebase: fast-forward --onto in more cases","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2019-04-17T14:14:15Z","receivedAt":"2019-04-17T14:14:21Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 17/04/2019 07:44, Denton Liu wrote:\n> On Tue, Apr 16, 2019 at 02:59:12PM +0100, Phillip Wood wrote:\n>> Hi Denton\n>>\n>> It's good to see rebase fast-forwarding properly when it should\n>>\n>> On 15/04/2019 23:29, Denton Liu wrote:\n>>> Before, when we had the following graph,\n>>>\n>>> \tA---B---C (master)\n>>> \t    \\\n>>> \t     D (side)\n>>>\n>>> running 'git rebase --onto master... master side' would result in D\n>>> being always rebased, no matter what. However, the desired behavior is\n>>> that rebase should notice that this is fast-forwardable and do that\n>>> instead.\n>>>\n>>> Add detection to `can_fast_forward` so that this case can be detected\n>>> and a fast-forward will be performed. First of all, rewrite the function\n>>> to use gotos which simplifies the logic. Next, since the\n>>>\n>>> \toptions.upstream &&\n>>> \t!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)\n>>>\n>>> conditions were removed in `cmd_rebase`, we reintroduce a substitute in\n>>> `can_fast_forward`. In particular, checking the merge bases of\n>>> `upstream` and `head` fixes a failing case in t3416.\n>>>\n>>> The abbreviated graph for t3416 is as follows:\n>>>\n>>> \t\t    F---G topic\n>>> \t\t   /\n>>> \t  A---B---C---D---E master\n>>>\n>>> and the failing command was\n>>>\n>>> \tgit rebase --onto master...topic F topic\n>>>\n>>> Before, Git would see that there was one merge base (C), and the merge\n>>> and onto were the same so it would incorrectly return 1, indicating that\n>>> we could fast-forward. This would cause the rebased graph to be 'ABCFG'\n>>> when we were expecting 'ABCG'.\n>>>\n>>> With the additional logic, we detect that upstream and head's merge base\n>>> is F. Since onto isn't F, it means we're not rebasing the full set of\n>>> commits from master..topic. Since we're excluding some commits, a\n>>> fast-forward cannot be performed and so we correctly return 0.\n>>>\n>>> Add '-f' to test cases that failed as a result of this change because\n>>> they were not expecting a fast-forward so that a rebase is forced.\n>>>\n>>> While we're at it, remove a trailing whitespace from rebase.c.\n>>>\n>>> Signed-off-by: Denton Liu <liu.denton@gmail.com>\n>>> ---\n>>>    builtin/rebase.c               | 40 +++++++++++++++++++++++-----------\n>>>    t/t3400-rebase.sh              |  2 +-\n>>>    t/t3404-rebase-interactive.sh  |  2 +-\n>>>    t/t3432-rebase-fast-forward.sh |  4 ++--\n>>>    4 files changed, 31 insertions(+), 17 deletions(-)\n>>>\n>>> diff --git a/builtin/rebase.c b/builtin/rebase.c\n>>> index 77deebc65c..7aa6a090d4 100644\n>>> --- a/builtin/rebase.c\n>>> +++ b/builtin/rebase.c\n>>> @@ -895,12 +895,12 @@ static int is_linear_history(struct commit *from, struct commit *to)\n>>>    \treturn 1;\n>>>    }\n>>> -static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n>>> -\t\t\t    struct object_id *merge_base)\n>>> +static int can_fast_forward(struct commit *onto, struct commit *upstream,\n>>> +\t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n>>>    {\n>>>    \tstruct commit *head = lookup_commit(the_repository, head_oid);\n>>> -\tstruct commit_list *merge_bases;\n>>> -\tint res;\n>>> +\tstruct commit_list *merge_bases = NULL;\n>>> +\tint res = 0;\n>>>    \tif (!head)\n>>>    \t\treturn 0;\n>>> @@ -908,12 +908,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n>>>    \tmerge_bases = get_merge_bases(onto, head);\n>>>    \tif (merge_bases && !merge_bases->next) {\n>>>    \t\toidcpy(merge_base, &merge_bases->item->object.oid);\n>>> -\t\tres = oideq(merge_base, &onto->object.oid);\n>>> +\t\tif (!oideq(merge_base, &onto->object.oid))\n>>> +\t\t\tgoto done;\n>>>    \t} else {\n>>>    \t\toidcpy(merge_base, &null_oid);\n>>> -\t\tres = 0;\n>>> +\t\tgoto done;\n>>>    \t}\n>>> +\n>>> +\tif (!upstream)\n>>> +\t\tgoto done;\n>>> +\n>>>    \tfree_commit_list(merge_bases);\n>>> +\tmerge_bases = get_merge_bases(upstream, head);\n>>> +\tif (merge_bases && !merge_bases->next) {\n>>> +\t\tif (!oideq(&onto->object.oid, &merge_bases->item->object.oid))\n>>> +\t\t\tgoto done;\n>>> +\t} else\n>>> +\t\tgoto done;\n>>> +\n>>> +\tres = 1;\n>>> +\n>>> +done:\n>>> +\tif (merge_bases)\n>>> +\t\tfree_commit_list(merge_bases);\n>>>    \treturn res && is_linear_history(onto, head);\n>>>    }\n>>\n>> I had a hard time following all those gotos. When you 'goto done' in both\n>> branches of an if statement it is hard to work out which cases fall through\n>> to the rest of the code. If I've understood it correctly I think it is\n>> clearer as\n>>\n>>          merge_bases = get_merge_bases(onto, head);\n>>          if (merge_bases && !merge_bases->next) {\n>>                  oidcpy(merge_base, &merge_bases->item->object.oid);\n>>                  if (oideq(merge_base, &onto->object.oid) && upstream) {\n>>                          free_commit_list(merge_bases);\n>>                          merge_bases = get_merge_bases(upstream, head);\n>>                          if (merge_bases && !merge_bases->next)\n>>                                  if (oideq(&onto->object.oid,\n>>                                             &merge_bases->item->object.oid))\n>>                                          res = 1;\n\nthat would be better as\n\tres = oideq(&onto->object.oid, &merge_bases->item->object.oid);\nwithout the last if\n\n>>                  }\n>>          } else {\n>>                  oidcpy(merge_base, &null_oid);\n>>          }\n>>\n>>          if (merge_bases)\n>>                  free_commit_list(merge_bases);\n>>          return res && is_linear_history(onto, head);\n>> }\n>>\n>> The nested if's aren't great but I think it is easier to follow\n> \n> I am pretty impartial between gotos and ifs. If no one else has any\n> strong opinions between the two, I'll reroll with ifs.\n\nIf you want to keep the goto approach then refactoring the ifs as \nfollows is clearer as it avoids jumping from both arms, each stage is a \nsimple single armed if (something) goto done\n\n\tmerge_bases = get_merge_bases(onto, head);\n\tif (!merge_bases || merge_bases->next) {\n\t\toidcpy(merge_base, &null_oid);\n\t\tgoto done;\n\t}\n\n\toidcpy(merge_base, &merge_bases->item->object.oid);\n\n\tif (!oideq(merge_base, &onto->object.oid))\n\t\tgoto done;\n\n\tif (!upstream)\n\t\tgoto done;\n\n\tfree_commit_list(merge_bases);\n\tmerge_bases = get_merge_bases(upstream, head);\n\tif (merge_bases && !merge_bases->next)\n\t\tres = oideq(&onto->object.oid, &merge_bases->item->object.oid);\n\ndone:\n\tif (merge_bases)\n\t\tfree_commit_list(merge_bases);\n\treturn res && is_linear_history(onto, head);\n}\n\nBest Wishes\n\nPhillip\n>>\n>>> @@ -1682,13 +1699,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>>>    \t/*\n>>>    \t * Check if we are already based on onto with linear history,\n>>> -\t * but this should be done only when upstream and onto are the same\n>>> -\t * and if this is not an interactive rebase.\n>>> +\t * but this should be done if this is not an interactive rebase.\n>>>    \t */\n>>> -\tif (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&\n>>> -\t    !is_interactive(&options) && !options.restrict_revision &&\n>>> -\t    options.upstream &&\n>>> -\t    !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {\n>>> +\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head, &merge_base) &&\n>>\n>> This is rather long, perhaps break the argument list\n> \n> Thanks, will do.\n> \n>>\n>> Best Wishes\n>>\n>> Phillip\n>>> +\t    !is_interactive(&options) && !options.restrict_revision) {\n>>>    \t\tint flag;\n>>>    \t\tif (!(options.flags & REBASE_FORCE)) {\n>>> @@ -1782,7 +1796,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>>>    \tstrbuf_addf(&msg, \"%s: checkout %s\",\n>>>    \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);\n>>>    \tif (reset_head(&options.onto->object.oid, \"checkout\", NULL,\n>>> -\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n>>> +\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n>>>    \t\t       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n>>>    \t\t       NULL, msg.buf))\n>>>    \t\tdie(_(\"Could not detach HEAD\"));\n>>> diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\n>>> index 460d0523be..604d624ff8 100755\n>>> --- a/t/t3400-rebase.sh\n>>> +++ b/t/t3400-rebase.sh\n>>> @@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' '\n>>>    \t\techo two >>init.t &&\n>>>    \t\tgit commit -a -m two &&\n>>>    \t\tgit tag two &&\n>>> -\t\ttest_must_fail git rebase --onto init HEAD^ &&\n>>> +\t\ttest_must_fail git rebase -f --onto init HEAD^ &&\n>>>    \t\tGIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&\n>>>    \t\tgrep \"show.*$(git rev-parse two)\" stderr\n>>>    \t)\n>>> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n>>> index b60b11f9f2..f054186cc7 100755\n>>> --- a/t/t3404-rebase-interactive.sh\n>>> +++ b/t/t3404-rebase-interactive.sh\n>>> @@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int\n>>>    \tgit reset --hard &&\n>>>    \tgit checkout conflict-branch &&\n>>>    \tset_fake_editor &&\n>>> -\ttest_must_fail git rebase --onto HEAD~2 HEAD~ &&\n>>> +\ttest_must_fail git rebase -f --onto HEAD~2 HEAD~ &&\n>>>    \ttest_must_fail git rebase --edit-todo &&\n>>>    \tgit rebase --abort\n>>>    '\n>>> diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\n>>> index 4f04d67fd7..d0e5b1f3e6 100755\n>>> --- a/t/t3432-rebase-fast-forward.sh\n>>> +++ b/t/t3432-rebase-fast-forward.sh\n>>> @@ -64,9 +64,9 @@ test_expect_success 'add work to upstream' '\n>>>    changes='our and their changes'\n>>>    test_rebase_same_head success --onto B B\n>>>    test_rebase_same_head success --onto B... B\n>>> -test_rebase_same_head failure --onto master... master\n>>> +test_rebase_same_head success --onto master... master\n>>>    test_rebase_same_head failure --fork-point --onto B B\n>>>    test_rebase_same_head failure --fork-point --onto B... B\n>>> -test_rebase_same_head failure --fork-point --onto master... master\n>>> +test_rebase_same_head success --fork-point --onto master... master\n>>>    test_done\n>>>\n"},{"id":"374059","messageId":"cover.1555523176.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1554500051.git.liu.denton@gmail.com","subject":"[PATCH v6 0/5] rebase: teach rebase --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-17T18:01:22Z","receivedAt":"2019-04-17T18:01:27Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Thanks for the comments, Junio and Phillip.\n\nI've fixed the ASCII art graphs and also refactored the if-else into a\ngoto tower.\n\nHopefully, this will be the last reroll of this series ;)\n\n---\n\nThis patchset now depends \"[PATCH 1/8] tests (rebase): spell out the\n`--keep-empty` option\" which is the first patch of Johannes's \"Do not\nuse abbreviated options in tests\" patchset[1]. (Thanks for catching\nthat, Johannes!)\n\nChanges since v1:\n\n* Squashed old set into one patch\n* Fixed indentation style and dangling else\n* Added more documentation after discussion with Ævar\n\nChanges since v2:\n\n* Add testing for rebase --fork-point behaviour\n* Add testing for rebase fast-forward behaviour\n* Make rebase --onto fast-forward in more cases\n* Update documentation to include use-case\n\nChanges since v3:\n\n* Fix tests failing on bash 4.2\n* Fix typo in t3431 comment\n\nChanges since v4:\n\n* Make rebase --fork-point fast-forward in more cases\n\nChanges since v5:\n\n* Fix graph illustrations so that the \"branch off\" is visually in the\n  correct place\n* Refactor if-else in can_fast_forward into one-level-deep ifs to\n  increase clarity\n\n[1]: https://public-inbox.org/git/a1b4b74b9167e279dae4cd8c58fb28d8a714a66a.1553537656.git.gitgitgadget@gmail.com/\n\nDenton Liu (6):\n  t3431: add rebase --fork-point tests\n  t3432: test rebase fast-forward behavior\n  rebase: refactor can_fast_forward into goto tower\n  rebase: fast-forward --onto in more cases\n  rebase: fast-forward --fork-point in more cases\n  rebase: teach rebase --keep-base\n\n Documentation/git-rebase.txt     | 30 +++++++++--\n builtin/rebase.c                 | 86 +++++++++++++++++++++++---------\n t/t3400-rebase.sh                |  2 +-\n t/t3404-rebase-interactive.sh    |  2 +-\n t/t3416-rebase-onto-threedots.sh | 57 +++++++++++++++++++++\n t/t3431-rebase-fork-point.sh     | 57 +++++++++++++++++++++\n t/t3432-rebase-fast-forward.sh   | 83 ++++++++++++++++++++++++++++++\n 7 files changed, 289 insertions(+), 28 deletions(-)\n create mode 100755 t/t3431-rebase-fork-point.sh\n create mode 100755 t/t3432-rebase-fast-forward.sh\n\nRange-diff against v5:\n1:  0f1e9ac5c8 ! 1:  eb64f6c91d t3431: add rebase --fork-point tests\n    @@ -18,9 +18,9 @@\n     +\n     +. ./test-lib.sh\n     +\n    -+# A---B---D---E       (master)\n    -+#     \\\n    -+#      C*---F---G (side)\n    ++# A---B---D---E    (master)\n    ++#      \\\n    ++#       C*---F---G (side)\n     +#\n     +# C was formerly part of master but master was rewound to remove C\n     +#\n2:  148027a14b = 2:  4c087ec041 t3432: test rebase fast-forward behavior\n-:  ---------- > 3:  3d348d2c37 rebase: refactor can_fast_forward into goto tower\n3:  ec55da0719 ! 4:  2559ab54a2 rebase: fast-forward --onto in more cases\n    @@ -5,8 +5,8 @@\n         Before, when we had the following graph,\n     \n                 A---B---C (master)\n    -                \\\n    -                 D (side)\n    +                 \\\n    +                  D (side)\n     \n         running 'git rebase --onto master... master side' would result in D\n         being always rebased, no matter what. However, the desired behavior is\n    @@ -49,6 +49,7 @@\n     \n         While we're at it, remove a trailing whitespace from rebase.c.\n     \n    +    Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n         Signed-off-by: Denton Liu <liu.denton@gmail.com>\n     \n      diff --git a/builtin/rebase.c b/builtin/rebase.c\n    @@ -64,45 +65,26 @@\n     +\t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n      {\n      \tstruct commit *head = lookup_commit(the_repository, head_oid);\n    --\tstruct commit_list *merge_bases;\n    --\tint res;\n    -+\tstruct commit_list *merge_bases = NULL;\n    -+\tint res = 0;\n    - \n    - \tif (!head)\n    - \t\treturn 0;\n    + \tstruct commit_list *merge_bases = NULL;\n     @@\n    - \tmerge_bases = get_merge_bases(onto, head);\n    - \tif (merge_bases && !merge_bases->next) {\n    - \t\toidcpy(merge_base, &merge_bases->item->object.oid);\n    --\t\tres = oideq(merge_base, &onto->object.oid);\n    -+\t\tif (!oideq(merge_base, &onto->object.oid))\n    -+\t\t\tgoto done;\n    - \t} else {\n    - \t\toidcpy(merge_base, &null_oid);\n    --\t\tres = 0;\n    -+\t\tgoto done;\n    - \t}\n    -+\n    + \tif (!oideq(merge_base, &onto->object.oid))\n    + \t\tgoto done;\n    + \n     +\tif (!upstream)\n     +\t\tgoto done;\n     +\n    - \tfree_commit_list(merge_bases);\n    ++\tfree_commit_list(merge_bases);\n     +\tmerge_bases = get_merge_bases(upstream, head);\n    -+\tif (merge_bases && !merge_bases->next) {\n    -+\t\tif (!oideq(&onto->object.oid, &merge_bases->item->object.oid))\n    -+\t\t\tgoto done;\n    -+\t} else\n    ++\tif (!merge_bases || merge_bases->next) {\n     +\t\tgoto done;\n    ++\t}\n     +\n    -+\tres = 1;\n    ++\tif (!oideq(&onto->object.oid, &merge_bases->item->object.oid))\n    ++\t\tgoto done;\n     +\n    -+done:\n    -+\tif (merge_bases)\n    -+\t\tfree_commit_list(merge_bases);\n    - \treturn res && is_linear_history(onto, head);\n    - }\n    + \tres = 1;\n      \n    + done:\n     @@\n      \n      \t/*\n    @@ -115,7 +97,8 @@\n     -\t    !is_interactive(&options) && !options.restrict_revision &&\n     -\t    options.upstream &&\n     -\t    !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {\n    -+\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head, &merge_base) &&\n    ++\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head,\n    ++\t\t    &merge_base) &&\n     +\t    !is_interactive(&options) && !options.restrict_revision) {\n      \t\tint flag;\n      \n4:  2256a902c1 ! 5:  0a466e830f rebase: fast-forward --fork-point in more cases\n    @@ -27,8 +27,8 @@\n      {\n      \tstruct commit *head = lookup_commit(the_repository, head_oid);\n     @@\n    + \tif (!oideq(merge_base, &onto->object.oid))\n      \t\tgoto done;\n    - \t}\n      \n     +\tif (restrict_revision && !oideq(&restrict_revision->object.oid, merge_base))\n     +\t\tgoto done;\n    @@ -40,7 +40,8 @@\n      \t * Check if we are already based on onto with linear history,\n      \t * but this should be done if this is not an interactive rebase.\n      \t */\n    --\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head, &merge_base) &&\n    +-\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head,\n    +-\t\t    &merge_base) &&\n     -\t    !is_interactive(&options) && !options.restrict_revision) {\n     +\tif (can_fast_forward(options.onto, options.upstream, options.restrict_revision,\n     +\t\t    &options.orig_head, &merge_base) &&\n5:  d6e7e1ca4b = 6:  c542c7afc1 rebase: teach rebase --keep-base\n-- \n2.21.0.944.gce45564dfd\n\n"},{"id":"374060","messageId":"eb64f6c91d27a6f9666c9829050995d9f9b52925.1555523176.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1555523176.git.liu.denton@gmail.com","subject":"[PATCH v6 1/6] t3431: add rebase --fork-point tests","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-17T18:01:24Z","receivedAt":"2019-04-17T18:01:29Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Signed-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3431-rebase-fork-point.sh | 53 ++++++++++++++++++++++++++++++++++++\n 1 file changed, 53 insertions(+)\n create mode 100755 t/t3431-rebase-fork-point.sh\n\ndiff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\nnew file mode 100755\nindex 0000000000..9b517d87a3\n--- /dev/null\n+++ b/t/t3431-rebase-fork-point.sh\n@@ -0,0 +1,53 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2019 Denton Liu\n+#\n+\n+test_description='git rebase --fork-point test'\n+\n+. ./test-lib.sh\n+\n+# A---B---D---E    (master)\n+#      \\\n+#       C*---F---G (side)\n+#\n+# C was formerly part of master but master was rewound to remove C\n+#\n+test_expect_success setup '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\ttest_commit C &&\n+\tgit branch -t side &&\n+\tgit reset --hard HEAD^ &&\n+\ttest_commit D &&\n+\ttest_commit E &&\n+\tgit checkout side &&\n+\ttest_commit F &&\n+\ttest_commit G\n+'\n+\n+test_rebase() {\n+\texpected=\"$1\" &&\n+\tshift &&\n+\ttest_expect_success \"git rebase $*\" \"\n+\t\tgit checkout master &&\n+\t\tgit reset --hard E &&\n+\t\tgit checkout side &&\n+\t\tgit reset --hard G &&\n+\t\tgit rebase $* &&\n+\t\ttest_write_lines $expected >expect &&\n+\t\tgit log --pretty=%s >actual &&\n+\t\ttest_cmp expect actual\n+\t\"\n+}\n+\n+test_rebase 'G F E D B A'\n+test_rebase 'G F D B A' --onto D\n+test_rebase 'G F C E D B A' --no-fork-point\n+test_rebase 'G F C D B A' --no-fork-point --onto D\n+test_rebase 'G F E D B A' --fork-point refs/heads/master\n+test_rebase 'G F D B A' --fork-point --onto D refs/heads/master\n+test_rebase 'G F C E D B A' refs/heads/master\n+test_rebase 'G F C D B A' --onto D refs/heads/master\n+\n+test_done\n-- \n2.21.0.944.gce45564dfd\n\n"},{"id":"374061","messageId":"4c087ec0413880e1d3e6cbba95ff62224a4d95e6.1555523176.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1555523176.git.liu.denton@gmail.com","subject":"[PATCH v6 2/6] t3432: test rebase fast-forward behavior","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-17T18:01:26Z","receivedAt":"2019-04-17T18:01:30Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"When rebase is run on a branch that can be fast-forwarded, this should\nautomatically be done. Create test to ensure this behavior happens.\n\nThere are some cases that currently don't pass. The first case is where\na feature and master have diverged, running\n\"git rebase master... master\" causes a full rebase to happen even though\na fast-forward should happen.\n\nThe second case is when we are doing \"git rebase --fork-point\" and a\nfork-point commit is found. Once again, a full rebase happens even\nthough a fast-forward should happen.\n\nMark these cases as failure so we can fix it later.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3432-rebase-fast-forward.sh | 72 ++++++++++++++++++++++++++++++++++\n 1 file changed, 72 insertions(+)\n create mode 100755 t/t3432-rebase-fast-forward.sh\n\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nnew file mode 100755\nindex 0000000000..4f04d67fd7\n--- /dev/null\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -0,0 +1,72 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2019 Denton Liu\n+#\n+\n+test_description='ensure rebase fast-forwards commits when possible'\n+\n+. ./test-lib.sh\n+\n+test_expect_success setup '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\ttest_commit C &&\n+\ttest_commit D &&\n+\tgit checkout -t -b side\n+'\n+\n+test_rebase_same_head() {\n+\tstatus=\"$1\" &&\n+\tshift &&\n+\ttest_expect_$status \"git rebase $* with $changes is no-op\" \"\n+\t\toldhead=\\$(git rev-parse HEAD) &&\n+\t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n+\t\tgit rebase $* &&\n+\t\tnewhead=\\$(git rev-parse HEAD) &&\n+\t\ttest_cmp_rev \\$oldhead \\$newhead\n+\t\"\n+}\n+\n+changes='no changes'\n+test_rebase_same_head success\n+test_rebase_same_head success master\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --no-fork-point\n+test_rebase_same_head success --fork-point master\n+test_rebase_same_head failure --fork-point --onto B B\n+test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head success --fork-point --onto master... master\n+\n+test_expect_success 'add work to side' '\n+\ttest_commit E\n+'\n+\n+changes='our changes'\n+test_rebase_same_head success\n+test_rebase_same_head success master\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --no-fork-point\n+test_rebase_same_head success --fork-point master\n+test_rebase_same_head failure --fork-point --onto B B\n+test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head success --fork-point --onto master... master\n+\n+test_expect_success 'add work to upstream' '\n+\tgit checkout master &&\n+\ttest_commit F &&\n+\tgit checkout side\n+'\n+\n+changes='our and their changes'\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head failure --onto master... master\n+test_rebase_same_head failure --fork-point --onto B B\n+test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head failure --fork-point --onto master... master\n+\n+test_done\n-- \n2.21.0.944.gce45564dfd\n\n"},{"id":"374062","messageId":"3d348d2c37751b8ecbbd244e03bd9fdd860290e2.1555523176.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1555523176.git.liu.denton@gmail.com","subject":"[PATCH v6 3/6] rebase: refactor can_fast_forward into goto tower","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-17T18:01:28Z","receivedAt":"2019-04-17T18:01:32Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Before, can_fast_forward was written with an if-else statement. However,\nin the future, we may be adding more termination cases which would lead\nto deeply nested if statements.\n\nRefactor to use a goto tower so that future cases can be easily\ninserted.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n builtin/rebase.c | 24 +++++++++++++++---------\n 1 file changed, 15 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 77deebc65c..de10b6f5ad 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -899,21 +899,27 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n \t\t\t    struct object_id *merge_base)\n {\n \tstruct commit *head = lookup_commit(the_repository, head_oid);\n-\tstruct commit_list *merge_bases;\n-\tint res;\n+\tstruct commit_list *merge_bases = NULL;\n+\tint res = 0;\n \n \tif (!head)\n-\t\treturn 0;\n+\t\tgoto done;\n \n \tmerge_bases = get_merge_bases(onto, head);\n-\tif (merge_bases && !merge_bases->next) {\n-\t\toidcpy(merge_base, &merge_bases->item->object.oid);\n-\t\tres = oideq(merge_base, &onto->object.oid);\n-\t} else {\n+\tif (!merge_bases || merge_bases->next) {\n \t\toidcpy(merge_base, &null_oid);\n-\t\tres = 0;\n+\t\tgoto done;\n \t}\n-\tfree_commit_list(merge_bases);\n+\n+\toidcpy(merge_base, &merge_bases->item->object.oid);\n+\tif (!oideq(merge_base, &onto->object.oid))\n+\t\tgoto done;\n+\n+\tres = 1;\n+\n+done:\n+\tif (merge_bases)\n+\t\tfree_commit_list(merge_bases);\n \treturn res && is_linear_history(onto, head);\n }\n \n-- \n2.21.0.944.gce45564dfd\n\n"},{"id":"374063","messageId":"2559ab54a2c6e5544595e0d66a431e249bf73ca8.1555523176.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1555523176.git.liu.denton@gmail.com","subject":"[PATCH v6 4/6] rebase: fast-forward --onto in more cases","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-17T18:01:29Z","receivedAt":"2019-04-17T18:01:35Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Before, when we had the following graph,\n\n\tA---B---C (master)\n\t     \\\n\t      D (side)\n\nrunning 'git rebase --onto master... master side' would result in D\nbeing always rebased, no matter what. However, the desired behavior is\nthat rebase should notice that this is fast-forwardable and do that\ninstead.\n\nAdd detection to `can_fast_forward` so that this case can be detected\nand a fast-forward will be performed. First of all, rewrite the function\nto use gotos which simplifies the logic. Next, since the\n\n\toptions.upstream &&\n\t!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)\n\nconditions were removed in `cmd_rebase`, we reintroduce a substitute in\n`can_fast_forward`. In particular, checking the merge bases of\n`upstream` and `head` fixes a failing case in t3416.\n\nThe abbreviated graph for t3416 is as follows:\n\n\t\t    F---G topic\n\t\t   /\n\t  A---B---C---D---E master\n\nand the failing command was\n\n\tgit rebase --onto master...topic F topic\n\nBefore, Git would see that there was one merge base (C), and the merge\nand onto were the same so it would incorrectly return 1, indicating that\nwe could fast-forward. This would cause the rebased graph to be 'ABCFG'\nwhen we were expecting 'ABCG'.\n\nWith the additional logic, we detect that upstream and head's merge base\nis F. Since onto isn't F, it means we're not rebasing the full set of\ncommits from master..topic. Since we're excluding some commits, a\nfast-forward cannot be performed and so we correctly return 0.\n\nAdd '-f' to test cases that failed as a result of this change because\nthey were not expecting a fast-forward so that a rebase is forced.\n\nWhile we're at it, remove a trailing whitespace from rebase.c.\n\nHelped-by: Phillip Wood <phillip.wood@dunelm.org.uk>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n builtin/rebase.c               | 28 +++++++++++++++++++---------\n t/t3400-rebase.sh              |  2 +-\n t/t3404-rebase-interactive.sh  |  2 +-\n t/t3432-rebase-fast-forward.sh |  4 ++--\n 4 files changed, 23 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex de10b6f5ad..f5aca1eee0 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -895,8 +895,8 @@ static int is_linear_history(struct commit *from, struct commit *to)\n \treturn 1;\n }\n \n-static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n-\t\t\t    struct object_id *merge_base)\n+static int can_fast_forward(struct commit *onto, struct commit *upstream,\n+\t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n {\n \tstruct commit *head = lookup_commit(the_repository, head_oid);\n \tstruct commit_list *merge_bases = NULL;\n@@ -915,6 +915,18 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n \tif (!oideq(merge_base, &onto->object.oid))\n \t\tgoto done;\n \n+\tif (!upstream)\n+\t\tgoto done;\n+\n+\tfree_commit_list(merge_bases);\n+\tmerge_bases = get_merge_bases(upstream, head);\n+\tif (!merge_bases || merge_bases->next) {\n+\t\tgoto done;\n+\t}\n+\n+\tif (!oideq(&onto->object.oid, &merge_bases->item->object.oid))\n+\t\tgoto done;\n+\n \tres = 1;\n \n done:\n@@ -1688,13 +1700,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \t/*\n \t * Check if we are already based on onto with linear history,\n-\t * but this should be done only when upstream and onto are the same\n-\t * and if this is not an interactive rebase.\n+\t * but this should be done if this is not an interactive rebase.\n \t */\n-\tif (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&\n-\t    !is_interactive(&options) && !options.restrict_revision &&\n-\t    options.upstream &&\n-\t    !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {\n+\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head,\n+\t\t    &merge_base) &&\n+\t    !is_interactive(&options) && !options.restrict_revision) {\n \t\tint flag;\n \n \t\tif (!(options.flags & REBASE_FORCE)) {\n@@ -1788,7 +1798,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tstrbuf_addf(&msg, \"%s: checkout %s\",\n \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);\n \tif (reset_head(&options.onto->object.oid, \"checkout\", NULL,\n-\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD | \n+\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n \t\t       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n \t\t       NULL, msg.buf))\n \t\tdie(_(\"Could not detach HEAD\"));\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 460d0523be..604d624ff8 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' '\n \t\techo two >>init.t &&\n \t\tgit commit -a -m two &&\n \t\tgit tag two &&\n-\t\ttest_must_fail git rebase --onto init HEAD^ &&\n+\t\ttest_must_fail git rebase -f --onto init HEAD^ &&\n \t\tGIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&\n \t\tgrep \"show.*$(git rev-parse two)\" stderr\n \t)\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex b60b11f9f2..f054186cc7 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int\n \tgit reset --hard &&\n \tgit checkout conflict-branch &&\n \tset_fake_editor &&\n-\ttest_must_fail git rebase --onto HEAD~2 HEAD~ &&\n+\ttest_must_fail git rebase -f --onto HEAD~2 HEAD~ &&\n \ttest_must_fail git rebase --edit-todo &&\n \tgit rebase --abort\n '\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 4f04d67fd7..d0e5b1f3e6 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -64,9 +64,9 @@ test_expect_success 'add work to upstream' '\n changes='our and their changes'\n test_rebase_same_head success --onto B B\n test_rebase_same_head success --onto B... B\n-test_rebase_same_head failure --onto master... master\n+test_rebase_same_head success --onto master... master\n test_rebase_same_head failure --fork-point --onto B B\n test_rebase_same_head failure --fork-point --onto B... B\n-test_rebase_same_head failure --fork-point --onto master... master\n+test_rebase_same_head success --fork-point --onto master... master\n \n test_done\n-- \n2.21.0.944.gce45564dfd\n\n"},{"id":"374064","messageId":"0a466e830f7704f3ae75691db535946a3d57ddc9.1555523176.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1555523176.git.liu.denton@gmail.com","subject":"[PATCH v6 5/6] rebase: fast-forward --fork-point in more cases","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-17T18:01:31Z","receivedAt":"2019-04-17T18:01:36Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Before, when we rebased with a --fork-point invocation where the\nfork-point wasn't empty, we would be setting options.restrict_revision.\nThe fast-forward logic would automatically declare that the rebase was\nnot fast-forwardable if it was set. However, this was painting with a\nvery broad brush.\n\nRefine the logic so that we can fast-forward in the case where the\nrestricted revision is equal to the merge base, since we stop rebasing\nat the merge base anyway.\n\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n builtin/rebase.c               | 10 +++++++---\n t/t3432-rebase-fast-forward.sh | 12 ++++++------\n 2 files changed, 13 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex f5aca1eee0..2e29ea652f 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -896,6 +896,7 @@ static int is_linear_history(struct commit *from, struct commit *to)\n }\n \n static int can_fast_forward(struct commit *onto, struct commit *upstream,\n+\t\t\t    struct commit *restrict_revision,\n \t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n {\n \tstruct commit *head = lookup_commit(the_repository, head_oid);\n@@ -915,6 +916,9 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,\n \tif (!oideq(merge_base, &onto->object.oid))\n \t\tgoto done;\n \n+\tif (restrict_revision && !oideq(&restrict_revision->object.oid, merge_base))\n+\t\tgoto done;\n+\n \tif (!upstream)\n \t\tgoto done;\n \n@@ -1702,9 +1706,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t * Check if we are already based on onto with linear history,\n \t * but this should be done if this is not an interactive rebase.\n \t */\n-\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head,\n-\t\t    &merge_base) &&\n-\t    !is_interactive(&options) && !options.restrict_revision) {\n+\tif (can_fast_forward(options.onto, options.upstream, options.restrict_revision,\n+\t\t    &options.orig_head, &merge_base) &&\n+\t    !is_interactive(&options)) {\n \t\tint flag;\n \n \t\tif (!(options.flags & REBASE_FORCE)) {\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex d0e5b1f3e6..1cb2896fb4 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -35,8 +35,8 @@ test_rebase_same_head success --onto B... B\n test_rebase_same_head success --onto master... master\n test_rebase_same_head success --no-fork-point\n test_rebase_same_head success --fork-point master\n-test_rebase_same_head failure --fork-point --onto B B\n-test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head success --fork-point --onto B B\n+test_rebase_same_head success --fork-point --onto B... B\n test_rebase_same_head success --fork-point --onto master... master\n \n test_expect_success 'add work to side' '\n@@ -51,8 +51,8 @@ test_rebase_same_head success --onto B... B\n test_rebase_same_head success --onto master... master\n test_rebase_same_head success --no-fork-point\n test_rebase_same_head success --fork-point master\n-test_rebase_same_head failure --fork-point --onto B B\n-test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head success --fork-point --onto B B\n+test_rebase_same_head success --fork-point --onto B... B\n test_rebase_same_head success --fork-point --onto master... master\n \n test_expect_success 'add work to upstream' '\n@@ -65,8 +65,8 @@ changes='our and their changes'\n test_rebase_same_head success --onto B B\n test_rebase_same_head success --onto B... B\n test_rebase_same_head success --onto master... master\n-test_rebase_same_head failure --fork-point --onto B B\n-test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head success --fork-point --onto B B\n+test_rebase_same_head success --fork-point --onto B... B\n test_rebase_same_head success --fork-point --onto master... master\n \n test_done\n-- \n2.21.0.944.gce45564dfd\n\n"},{"id":"374065","messageId":"c542c7afc1af942dc6ca0d05582ff43e9eda2cbe.1555523176.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1555523176.git.liu.denton@gmail.com","subject":"[PATCH v6 6/6] rebase: teach rebase --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-17T18:01:32Z","receivedAt":"2019-04-17T18:01:38Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"A common scenario is if a user is working on a topic branch and they\nwish to make some changes to intermediate commits or autosquash, they\nwould run something such as\n\n\tgit rebase -i --onto master... master\n\nin order to preserve the merge base. This is useful when contributing a\npatch series to the Git mailing list, one often starts on top of the\ncurrent 'master'. However, while developing the patches, 'master' is\nalso developed further and it is sometimes not the bst idea to keep\nrebasing on top of 'master', but to keep the base commit as-is.\n\nAlternatively, a user wishing to test individual commits in a topic\nbranch without changing anything may run\n\n\tgit rebase -x ./test.sh master... master\n\nSince rebasing onto the merge base of the branch and the upstream is\nsuch a common case, introduce the --keep-base option as a shortcut.\n\nThis allows us to rewrite the above as\n\n\tgit rebase -i --keep-base master\n\nand\n\n\tgit rebase -x ./test.sh --keep-base master\n\nrespectively.\n\nAdd tests to ensure --keep-base works correctly in the normal case and\nfails when there are multiple merge bases, both in regular and\ninteractive mode. Also, test to make sure conflicting options cause\nrebase to fail. While we're adding test cases, add a missing\nset_fake_editor call to 'rebase -i --onto master...side'.\n\nWhile we're documenting the --keep-base option, change an instance of\n\"merge-base\" to \"merge base\", which is the consistent spelling.\n\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nHelped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n Documentation/git-rebase.txt     | 30 +++++++++++++++--\n builtin/rebase.c                 | 32 ++++++++++++++----\n t/t3416-rebase-onto-threedots.sh | 57 ++++++++++++++++++++++++++++++++\n t/t3431-rebase-fork-point.sh     |  4 +++\n t/t3432-rebase-fast-forward.sh   | 11 ++++++\n 5 files changed, 125 insertions(+), 9 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 6363d674b7..569ab708d4 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip\n SYNOPSIS\n --------\n [verse]\n-'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n-\t[<upstream> [<branch>]]\n+'git rebase' [-i | --interactive] [<options>] [--exec <cmd>]\n+\t[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\n 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n \t--root [<branch>]\n 'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch\n@@ -217,6 +217,24 @@ As a special case, you may use \"A\\...B\" as a shortcut for the\n merge base of A and B if there is exactly one merge base. You can\n leave out at most one of A and B, in which case it defaults to HEAD.\n \n+--keep-base::\n+\tSet the starting point at which to create the new commits to the\n+\tmerge base of <upstream> <branch>. Running\n+\t'git rebase --keep-base <upstream> <branch>' is equivalent to\n+\trunning 'git rebase --onto <upstream>... <upstream>'.\n++\n+This option is useful in the case where one is developing a feature on\n+top of an upstream branch. While the feature is being worked on, the\n+upstream branch may advance and it may not be the best idea to keep\n+rebasing on top of the upstream but to keep the base commit as-is.\n++\n+Although both this option and --fork-point find the merge base between\n+<upstream> and <branch>, this option uses the merge base as the _starting\n+point_ on which new commits will be created, whereas --fork-point uses\n+the merge base to determine the _set of commits_ which will be rebased.\n++\n+See also INCOMPATIBLE OPTIONS below.\n+\n <upstream>::\n \tUpstream branch to compare against.  May be any valid commit,\n \tnot just an existing branch name. Defaults to the configured\n@@ -364,6 +382,10 @@ ends up being empty, the <upstream> will be used as a fallback.\n +\n If either <upstream> or --root is given on the command line, then the\n default is `--no-fork-point`, otherwise the default is `--fork-point`.\n++\n+If your branch was based on <upstream> but <upstream> was rewound and\n+your branch contains commits which were dropped, this option can be used\n+with `--keep-base` in order to drop those commits from your branch.\n \n --ignore-whitespace::\n --whitespace=<option>::\n@@ -539,6 +561,8 @@ In addition, the following pairs of options are incompatible:\n  * --preserve-merges and --rebase-merges\n  * --rebase-merges and --strategy\n  * --rebase-merges and --strategy-option\n+ * --keep-base and --onto\n+ * --keep-base and --root\n \n BEHAVIORAL DIFFERENCES\n -----------------------\n@@ -863,7 +887,7 @@ NOTE: While an \"easy case recovery\" sometimes appears to be successful\n       --interactive` will be **resurrected**!\n \n The idea is to manually tell 'git rebase' \"where the old 'subsystem'\n-ended and your 'topic' began\", that is, what the old merge-base\n+ended and your 'topic' began\", that is, what the old merge base\n between them was.  You will have to find a way to name the last commit\n of the old 'subsystem', for example:\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 2e29ea652f..87a23f17eb 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -27,8 +27,8 @@\n #include \"branch.h\"\n \n static char const * const builtin_rebase_usage[] = {\n-\tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n-\t\t\"[<upstream>] [<branch>]\"),\n+\tN_(\"git rebase [-i] [options] [--exec <cmd>] \"\n+\t\t\"[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\"),\n \tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n \t\t\"--root [<branch>]\"),\n \tN_(\"git rebase --continue | --abort | --skip | --edit-todo\"),\n@@ -1040,6 +1040,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t};\n \tconst char *branch_name;\n \tint ret, flags, total_argc, in_progress = 0;\n+\tint keep_base = 0;\n \tint ok_to_skip_pre_rebase = 0;\n \tstruct strbuf msg = STRBUF_INIT;\n \tstruct strbuf revisions = STRBUF_INIT;\n@@ -1073,6 +1074,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"rebase onto given branch instead of upstream\")),\n+\t\tOPT_BOOL(0, \"keep-base\", &keep_base,\n+\t\t\t N_(\"use the merge-base of upstream and branch as the current base\")),\n \t\tOPT_BOOL(0, \"no-verify\", &ok_to_skip_pre_rebase,\n \t\t\t N_(\"allow pre-rebase hook to run\")),\n \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n@@ -1239,6 +1242,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(builtin_rebase_usage,\n \t\t\t\t   builtin_rebase_options);\n \n+\tif (keep_base) {\n+\t\tif (options.onto_name)\n+\t\t\tdie(_(\"cannot combine '--keep-base' with '--onto'\"));\n+\t\tif (options.root)\n+\t\t\tdie(_(\"cannot combine '--keep-base' with '--root'\"));\n+\t}\n+\n \tif (action != NO_ACTION && !in_progress)\n \t\tdie(_(\"No rebase in progress?\"));\n \tsetenv(GIT_REFLOG_ACTION_ENVIRONMENT, \"rebase\", 0);\n@@ -1563,12 +1573,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t}\n \n \t/* Make sure the branch to rebase onto is valid. */\n-\tif (!options.onto_name)\n+\tif (keep_base) {\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addstr(&buf, options.upstream_name);\n+\t\tstrbuf_addstr(&buf, \"...\");\n+\t\toptions.onto_name = xstrdup(buf.buf);\n+\t} else if (!options.onto_name)\n \t\toptions.onto_name = options.upstream_name;\n \tif (strstr(options.onto_name, \"...\")) {\n-\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0)\n-\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n-\t\t\t    options.onto_name);\n+\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0) {\n+\t\t\tif (keep_base)\n+\t\t\t\tdie(_(\"'%s': need exactly one merge base with branch\"),\n+\t\t\t\t    options.upstream_name);\n+\t\t\telse\n+\t\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n+\t\t\t\t    options.onto_name);\n+\t\t}\n \t\toptions.onto = lookup_commit_or_die(&merge_base,\n \t\t\t\t\t\t    options.onto_name);\n \t} else {\ndiff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh\nindex ddf2f64853..9c2548423b 100755\n--- a/t/t3416-rebase-onto-threedots.sh\n+++ b/t/t3416-rebase-onto-threedots.sh\n@@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' '\n \tgit checkout side &&\n \tgit reset --hard K &&\n \n+\tset_fake_editor &&\n \ttest_must_fail git rebase -i --onto master...side J\n '\n \n+test_expect_success 'rebase --keep-base --onto incompatible' '\n+\ttest_must_fail git rebase --keep-base --onto master...\n+'\n+\n+test_expect_success 'rebase --keep-base --root incompatible' '\n+\ttest_must_fail git rebase --keep-base --root\n+'\n+\n+test_expect_success 'rebase --keep-base master from topic' '\n+\tgit reset --hard &&\n+\tgit checkout topic &&\n+\tgit reset --hard G &&\n+\n+\tgit rebase --keep-base master &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base master HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --keep-base master from side' '\n+\tgit reset --hard &&\n+\tgit checkout side &&\n+\tgit reset --hard K &&\n+\n+\ttest_must_fail git rebase --keep-base master\n+'\n+\n+test_expect_success 'rebase -i --keep-base master from topic' '\n+\tgit reset --hard &&\n+\tgit checkout topic &&\n+\tgit reset --hard G &&\n+\n+\tset_fake_editor &&\n+\tEXPECT_COUNT=2 git rebase -i --keep-base master &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base master HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase -i --keep-base master from side' '\n+\tgit reset --hard &&\n+\tgit checkout side &&\n+\tgit reset --hard K &&\n+\n+\tset_fake_editor &&\n+\ttest_must_fail git rebase -i --keep-base master\n+'\n+\n test_done\ndiff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\nindex 9b517d87a3..f848946f5a 100755\n--- a/t/t3431-rebase-fork-point.sh\n+++ b/t/t3431-rebase-fork-point.sh\n@@ -43,11 +43,15 @@ test_rebase() {\n \n test_rebase 'G F E D B A'\n test_rebase 'G F D B A' --onto D\n+test_rebase 'G F B A' --keep-base\n test_rebase 'G F C E D B A' --no-fork-point\n test_rebase 'G F C D B A' --no-fork-point --onto D\n+test_rebase 'G F C B A' --no-fork-point --keep-base\n test_rebase 'G F E D B A' --fork-point refs/heads/master\n test_rebase 'G F D B A' --fork-point --onto D refs/heads/master\n+test_rebase 'G F B A' --fork-point --keep-base refs/heads/master\n test_rebase 'G F C E D B A' refs/heads/master\n test_rebase 'G F C D B A' --onto D refs/heads/master\n+test_rebase 'G F C B A' --keep-base refs/heads/master\n \n test_done\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 1cb2896fb4..1ba02cc95f 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -33,11 +33,15 @@ test_rebase_same_head success master\n test_rebase_same_head success --onto B B\n test_rebase_same_head success --onto B... B\n test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --keep-base master\n+test_rebase_same_head success --keep-base\n test_rebase_same_head success --no-fork-point\n+test_rebase_same_head success --keep-base --no-fork-point\n test_rebase_same_head success --fork-point master\n test_rebase_same_head success --fork-point --onto B B\n test_rebase_same_head success --fork-point --onto B... B\n test_rebase_same_head success --fork-point --onto master... master\n+test_rebase_same_head success --fork-point --keep-base master\n \n test_expect_success 'add work to side' '\n \ttest_commit E\n@@ -49,11 +53,15 @@ test_rebase_same_head success master\n test_rebase_same_head success --onto B B\n test_rebase_same_head success --onto B... B\n test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --keep-base master\n+test_rebase_same_head success --keep-base\n test_rebase_same_head success --no-fork-point\n+test_rebase_same_head success --keep-base --no-fork-point\n test_rebase_same_head success --fork-point master\n test_rebase_same_head success --fork-point --onto B B\n test_rebase_same_head success --fork-point --onto B... B\n test_rebase_same_head success --fork-point --onto master... master\n+test_rebase_same_head success --fork-point --keep-base master\n \n test_expect_success 'add work to upstream' '\n \tgit checkout master &&\n@@ -65,8 +73,11 @@ changes='our and their changes'\n test_rebase_same_head success --onto B B\n test_rebase_same_head success --onto B... B\n test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --keep-base master\n+test_rebase_same_head success --keep-base\n test_rebase_same_head success --fork-point --onto B B\n test_rebase_same_head success --fork-point --onto B... B\n test_rebase_same_head success --fork-point --onto master... master\n+test_rebase_same_head success --fork-point --keep-base master\n \n test_done\n-- \n2.21.0.944.gce45564dfd\n\n"},{"id":"374068","messageId":"da5b0efb-1245-7f34-be4e-4e936e9d1d20@gmail.com","threadId":"50810","inReplyTo":"2559ab54a2c6e5544595e0d66a431e249bf73ca8.1555523176.git.liu.denton@gmail.com","subject":"Re: [PATCH v6 4/6] rebase: fast-forward --onto in more cases","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2019-04-17T19:59:09Z","receivedAt":"2019-04-17T19:59:15Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Denton\n\nOn 17/04/2019 19:01, Denton Liu wrote:\n> Before, when we had the following graph,\n> \n> \tA---B---C (master)\n> \t     \\\n> \t      D (side)\n> \n> running 'git rebase --onto master... master side' would result in D\n> being always rebased, no matter what. However, the desired behavior is\n> that rebase should notice that this is fast-forwardable and do that\n> instead.\n> \n> Add detection to `can_fast_forward` so that this case can be detected\n> and a fast-forward will be performed. First of all, rewrite the function\n> to use gotos which simplifies the logic. Next, since the\n> \n> \toptions.upstream &&\n> \t!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)\n> \n> conditions were removed in `cmd_rebase`, we reintroduce a substitute in\n> `can_fast_forward`. In particular, checking the merge bases of\n> `upstream` and `head` fixes a failing case in t3416.\n> \n> The abbreviated graph for t3416 is as follows:\n> \n> \t\t    F---G topic\n> \t\t   /\n> \t  A---B---C---D---E master\n> \n> and the failing command was\n> \n> \tgit rebase --onto master...topic F topic\n> \n> Before, Git would see that there was one merge base (C), and the merge\n> and onto were the same so it would incorrectly return 1, indicating that\n> we could fast-forward. This would cause the rebased graph to be 'ABCFG'\n> when we were expecting 'ABCG'.\n> \n> With the additional logic, we detect that upstream and head's merge base\n> is F. Since onto isn't F, it means we're not rebasing the full set of\n> commits from master..topic. Since we're excluding some commits, a\n> fast-forward cannot be performed and so we correctly return 0.\n> \n> Add '-f' to test cases that failed as a result of this change because\n> they were not expecting a fast-forward so that a rebase is forced.\n> \n> While we're at it, remove a trailing whitespace from rebase.c.\n> \n> Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n> Signed-off-by: Denton Liu <liu.denton@gmail.com>\n> ---\n>   builtin/rebase.c               | 28 +++++++++++++++++++---------\n>   t/t3400-rebase.sh              |  2 +-\n>   t/t3404-rebase-interactive.sh  |  2 +-\n>   t/t3432-rebase-fast-forward.sh |  4 ++--\n>   4 files changed, 23 insertions(+), 13 deletions(-)\n> \n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index de10b6f5ad..f5aca1eee0 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -895,8 +895,8 @@ static int is_linear_history(struct commit *from, struct commit *to)\n>   \treturn 1;\n>   }\n>   \n> -static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n> -\t\t\t    struct object_id *merge_base)\n> +static int can_fast_forward(struct commit *onto, struct commit *upstream,\n> +\t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n>   {\n>   \tstruct commit *head = lookup_commit(the_repository, head_oid);\n>   \tstruct commit_list *merge_bases = NULL;\n> @@ -915,6 +915,18 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n>   \tif (!oideq(merge_base, &onto->object.oid))\n>   \t\tgoto done;\n>   \n> +\tif (!upstream)\n> +\t\tgoto done;\n> +\n> +\tfree_commit_list(merge_bases);\n> +\tmerge_bases = get_merge_bases(upstream, head);\n> +\tif (!merge_bases || merge_bases->next) {\n> +\t\tgoto done;\n> +\t}\n\nThanks for changing the ifs in this patch and the previous one, I find \nit much easier to follow now. Just one style nit (probably not worth a \nreroll) - we don't put braces around a single conditional statement like \nthis unless another branch of the same if requires them (then all \nbranches should have them).\n\nBest Wishes\n\nPhillip\n\n\n> +\n> +\tif (!oideq(&onto->object.oid, &merge_bases->item->object.oid))\n> +\t\tgoto done;\n> +\n>   \tres = 1;\n>   \n>   done:\n> @@ -1688,13 +1700,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>   \n>   \t/*\n>   \t * Check if we are already based on onto with linear history,\n> -\t * but this should be done only when upstream and onto are the same\n> -\t * and if this is not an interactive rebase.\n> +\t * but this should be done if this is not an interactive rebase.\n>   \t */\n> -\tif (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&\n> -\t    !is_interactive(&options) && !options.restrict_revision &&\n> -\t    options.upstream &&\n> -\t    !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {\n> +\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head,\n> +\t\t    &merge_base) &&\n> +\t    !is_interactive(&options) && !options.restrict_revision) {\n>   \t\tint flag;\n>   \n>   \t\tif (!(options.flags & REBASE_FORCE)) {\n> @@ -1788,7 +1798,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>   \tstrbuf_addf(&msg, \"%s: checkout %s\",\n>   \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);\n>   \tif (reset_head(&options.onto->object.oid, \"checkout\", NULL,\n> -\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n> +\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n>   \t\t       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n>   \t\t       NULL, msg.buf))\n>   \t\tdie(_(\"Could not detach HEAD\"));\n> diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\n> index 460d0523be..604d624ff8 100755\n> --- a/t/t3400-rebase.sh\n> +++ b/t/t3400-rebase.sh\n> @@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' '\n>   \t\techo two >>init.t &&\n>   \t\tgit commit -a -m two &&\n>   \t\tgit tag two &&\n> -\t\ttest_must_fail git rebase --onto init HEAD^ &&\n> +\t\ttest_must_fail git rebase -f --onto init HEAD^ &&\n>   \t\tGIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&\n>   \t\tgrep \"show.*$(git rev-parse two)\" stderr\n>   \t)\n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index b60b11f9f2..f054186cc7 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int\n>   \tgit reset --hard &&\n>   \tgit checkout conflict-branch &&\n>   \tset_fake_editor &&\n> -\ttest_must_fail git rebase --onto HEAD~2 HEAD~ &&\n> +\ttest_must_fail git rebase -f --onto HEAD~2 HEAD~ &&\n>   \ttest_must_fail git rebase --edit-todo &&\n>   \tgit rebase --abort\n>   '\n> diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\n> index 4f04d67fd7..d0e5b1f3e6 100755\n> --- a/t/t3432-rebase-fast-forward.sh\n> +++ b/t/t3432-rebase-fast-forward.sh\n> @@ -64,9 +64,9 @@ test_expect_success 'add work to upstream' '\n>   changes='our and their changes'\n>   test_rebase_same_head success --onto B B\n>   test_rebase_same_head success --onto B... B\n> -test_rebase_same_head failure --onto master... master\n> +test_rebase_same_head success --onto master... master\n>   test_rebase_same_head failure --fork-point --onto B B\n>   test_rebase_same_head failure --fork-point --onto B... B\n> -test_rebase_same_head failure --fork-point --onto master... master\n> +test_rebase_same_head success --fork-point --onto master... master\n>   \n>   test_done\n> \n"},{"id":"374153","messageId":"20190419170834.GA11635@dev-l","threadId":"50810","inReplyTo":"ec55da07191e7f0a1d31342053c1496405ba7d3a.1555366891.git.liu.denton@gmail.com","subject":"Re: [PATCH v5 3/5] rebase: fast-forward --onto in more cases","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-19T17:08:34Z","receivedAt":"2019-04-19T19:08:51Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Hi Junio,\n\nOn Mon, Apr 15, 2019 at 03:29:24PM -0700, Denton Liu wrote:\n> Before, when we had the following graph,\n> \n> \tA---B---C (master)\n> \t    \\\n> \t     D (side)\n> \n> running 'git rebase --onto master... master side' would result in D\n> being always rebased, no matter what. However, the desired behavior is\n> that rebase should notice that this is fast-forwardable and do that\n> instead.\n> \n> Add detection to `can_fast_forward` so that this case can be detected\n> and a fast-forward will be performed. First of all, rewrite the function\n> to use gotos which simplifies the logic. Next, since the\n> \n> \toptions.upstream &&\n> \t!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)\n> \n> conditions were removed in `cmd_rebase`, we reintroduce a substitute in\n> `can_fast_forward`. In particular, checking the merge bases of\n> `upstream` and `head` fixes a failing case in t3416.\n> \n> The abbreviated graph for t3416 is as follows:\n> \n> \t\t    F---G topic\n> \t\t   /\n> \t  A---B---C---D---E master\n> \n> and the failing command was\n> \n> \tgit rebase --onto master...topic F topic\n> \n> Before, Git would see that there was one merge base (C), and the merge\n> and onto were the same so it would incorrectly return 1, indicating that\n> we could fast-forward. This would cause the rebased graph to be 'ABCFG'\n> when we were expecting 'ABCG'.\n> \n> With the additional logic, we detect that upstream and head's merge base\n> is F. Since onto isn't F, it means we're not rebasing the full set of\n> commits from master..topic. Since we're excluding some commits, a\n> fast-forward cannot be performed and so we correctly return 0.\n> \n> Add '-f' to test cases that failed as a result of this change because\n> they were not expecting a fast-forward so that a rebase is forced.\n> \n> While we're at it, remove a trailing whitespace from rebase.c.\n> \n> Signed-off-by: Denton Liu <liu.denton@gmail.com>\n> ---\n>  builtin/rebase.c               | 40 +++++++++++++++++++++++-----------\n>  t/t3400-rebase.sh              |  2 +-\n>  t/t3404-rebase-interactive.sh  |  2 +-\n>  t/t3432-rebase-fast-forward.sh |  4 ++--\n>  4 files changed, 31 insertions(+), 17 deletions(-)\n> \n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index 77deebc65c..7aa6a090d4 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -895,12 +895,12 @@ static int is_linear_history(struct commit *from, struct commit *to)\n>  \treturn 1;\n>  }\n>  \n> -static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n> -\t\t\t    struct object_id *merge_base)\n> +static int can_fast_forward(struct commit *onto, struct commit *upstream,\n> +\t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n>  {\n>  \tstruct commit *head = lookup_commit(the_repository, head_oid);\n> -\tstruct commit_list *merge_bases;\n> -\tint res;\n> +\tstruct commit_list *merge_bases = NULL;\n> +\tint res = 0;\n>  \n>  \tif (!head)\n>  \t\treturn 0;\n> @@ -908,12 +908,29 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n>  \tmerge_bases = get_merge_bases(onto, head);\n>  \tif (merge_bases && !merge_bases->next) {\n>  \t\toidcpy(merge_base, &merge_bases->item->object.oid);\n> -\t\tres = oideq(merge_base, &onto->object.oid);\n> +\t\tif (!oideq(merge_base, &onto->object.oid))\n> +\t\t\tgoto done;\n>  \t} else {\n>  \t\toidcpy(merge_base, &null_oid);\n> -\t\tres = 0;\n> +\t\tgoto done;\n>  \t}\n> +\n> +\tif (!upstream)\n> +\t\tgoto done;\n> +\n>  \tfree_commit_list(merge_bases);\n> +\tmerge_bases = get_merge_bases(upstream, head);\n> +\tif (merge_bases && !merge_bases->next) {\n> +\t\tif (!oideq(&onto->object.oid, &merge_bases->item->object.oid))\n> +\t\t\tgoto done;\n> +\t} else\n> +\t\tgoto done;\n> +\n> +\tres = 1;\n> +\n> +done:\n> +\tif (merge_bases)\n> +\t\tfree_commit_list(merge_bases);\n>  \treturn res && is_linear_history(onto, head);\n>  }\n>  \n> @@ -1682,13 +1699,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \n>  \t/*\n>  \t * Check if we are already based on onto with linear history,\n> -\t * but this should be done only when upstream and onto are the same\n> -\t * and if this is not an interactive rebase.\n> +\t * but this should be done if this is not an interactive rebase.\n>  \t */\n\nI forgot to incorporate your comment about this comment block in the\nlast reroll. I'm not sure if this is worth another reroll so could you\nplease change the comment block to this:\n\n\t/*\n\t * Check if we are already based on onto with linear history,\n\t * in which case we could fast-forward without replacing the commits\n\t * with new commits recreated by replaying their changes. This\n\t * optimization must not be done if this is an interactive rebase.\n\t */\n\nThanks,\n\nDenton\n> -\tif (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&\n> -\t    !is_interactive(&options) && !options.restrict_revision &&\n> -\t    options.upstream &&\n> -\t    !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {\n> +\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head, &merge_base) &&\n> +\t    !is_interactive(&options) && !options.restrict_revision) {\n>  \t\tint flag;\n>  \n>  \t\tif (!(options.flags & REBASE_FORCE)) {\n> @@ -1782,7 +1796,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \tstrbuf_addf(&msg, \"%s: checkout %s\",\n>  \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);\n>  \tif (reset_head(&options.onto->object.oid, \"checkout\", NULL,\n> -\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD | \n> +\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n>  \t\t       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n>  \t\t       NULL, msg.buf))\n>  \t\tdie(_(\"Could not detach HEAD\"));\n> diff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\n> index 460d0523be..604d624ff8 100755\n> --- a/t/t3400-rebase.sh\n> +++ b/t/t3400-rebase.sh\n> @@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' '\n>  \t\techo two >>init.t &&\n>  \t\tgit commit -a -m two &&\n>  \t\tgit tag two &&\n> -\t\ttest_must_fail git rebase --onto init HEAD^ &&\n> +\t\ttest_must_fail git rebase -f --onto init HEAD^ &&\n>  \t\tGIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&\n>  \t\tgrep \"show.*$(git rev-parse two)\" stderr\n>  \t)\n> diff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\n> index b60b11f9f2..f054186cc7 100755\n> --- a/t/t3404-rebase-interactive.sh\n> +++ b/t/t3404-rebase-interactive.sh\n> @@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int\n>  \tgit reset --hard &&\n>  \tgit checkout conflict-branch &&\n>  \tset_fake_editor &&\n> -\ttest_must_fail git rebase --onto HEAD~2 HEAD~ &&\n> +\ttest_must_fail git rebase -f --onto HEAD~2 HEAD~ &&\n>  \ttest_must_fail git rebase --edit-todo &&\n>  \tgit rebase --abort\n>  '\n> diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\n> index 4f04d67fd7..d0e5b1f3e6 100755\n> --- a/t/t3432-rebase-fast-forward.sh\n> +++ b/t/t3432-rebase-fast-forward.sh\n> @@ -64,9 +64,9 @@ test_expect_success 'add work to upstream' '\n>  changes='our and their changes'\n>  test_rebase_same_head success --onto B B\n>  test_rebase_same_head success --onto B... B\n> -test_rebase_same_head failure --onto master... master\n> +test_rebase_same_head success --onto master... master\n>  test_rebase_same_head failure --fork-point --onto B B\n>  test_rebase_same_head failure --fork-point --onto B... B\n> -test_rebase_same_head failure --fork-point --onto master... master\n> +test_rebase_same_head success --fork-point --onto master... master\n>  \n>  test_done\n> -- \n> 2.21.0.921.gb27c68c4e9\n> \n"},{"id":"374185","messageId":"cover.1555832783.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1555523176.git.liu.denton@gmail.com","subject":"[PATCH v7 0/6] rebase: learn --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-21T08:11:15Z","receivedAt":"2019-04-21T08:11:21Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Hi all,\n\nSorry for yet another reroll. I realised earlier that rebase doesn't use\n--git-completion-helper for completion so I had to manually insert\n--keep-base into the completion script.\n\nBetween this and forgetting to update the comment around the\ncan_fast_forward call, I figured with the number of mistakes to fix, it\nwas worth another reroll.\n\nThanks,\n\nDenton\n\n---\n\nThis patchset now depends \"[PATCH 1/8] tests (rebase): spell out the\n`--keep-empty` option\" which is the first patch of Johannes's \"Do not\nuse abbreviated options in tests\" patchset[1]. (Thanks for catching\nthat, Johannes!)\n\nChanges since v1:\n\n* Squashed old set into one patch\n* Fixed indentation style and dangling else\n* Added more documentation after discussion with Ævar\n\nChanges since v2:\n\n* Add testing for rebase --fork-point behaviour\n* Add testing for rebase fast-forward behaviour\n* Make rebase --onto fast-forward in more cases\n* Update documentation to include use-case\n\nChanges since v3:\n\n* Fix tests failing on bash 4.2\n* Fix typo in t3431 comment\n\nChanges since v4:\n\n* Make rebase --fork-point fast-forward in more cases\n\nChanges since v5:\n\n* Fix graph illustrations so that the \"branch off\" is visually in the\n  correct place\n* Refactor if-else in can_fast_forward into one-level-deep ifs to\n  increase clarity\n\nChanges since v6:\n\n* Remove redundant braces around if\n* Update comment around can_fast_forward call\n* Add completion for rebase\n\n[1]: https://public-inbox.org/git/a1b4b74b9167e279dae4cd8c58fb28d8a714a66a.1553537656.git.gitgitgadget@gmail.com/\n\n\nDenton Liu (6):\n  t3431: add rebase --fork-point tests\n  t3432: test rebase fast-forward behavior\n  rebase: refactor can_fast_forward into goto tower\n  rebase: fast-forward --onto in more cases\n  rebase: fast-forward --fork-point in more cases\n  rebase: teach rebase --keep-base\n\n Documentation/git-rebase.txt           | 30 ++++++++-\n builtin/rebase.c                       | 87 +++++++++++++++++++-------\n contrib/completion/git-completion.bash |  2 +-\n t/t3400-rebase.sh                      |  2 +-\n t/t3404-rebase-interactive.sh          |  2 +-\n t/t3416-rebase-onto-threedots.sh       | 57 +++++++++++++++++\n t/t3431-rebase-fork-point.sh           | 57 +++++++++++++++++\n t/t3432-rebase-fast-forward.sh         | 83 ++++++++++++++++++++++++\n 8 files changed, 291 insertions(+), 29 deletions(-)\n create mode 100755 t/t3431-rebase-fork-point.sh\n create mode 100755 t/t3432-rebase-fast-forward.sh\n\nInterdiff against v6:\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex cf63195e7d..19e7e2a1c9 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1723,7 +1723,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \t/*\n \t * Check if we are already based on onto with linear history,\n-\t * but this should be done if this is not an interactive rebase.\n+\t * in which case we could fast-forward without replacing the commits\n+\t * with new commits recreated by replaying their changes. This\n+\t * optimization must not be done if this is an interactive rebase.\n \t */\n \tif (can_fast_forward(options.onto, options.upstream, options.restrict_revision,\n \t\t    &options.orig_head, &merge_base) &&\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 976e4a6548..f9dc431a39 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2027,7 +2027,7 @@ _git_rebase ()\n \t\t\t--autosquash --no-autosquash\n \t\t\t--fork-point --no-fork-point\n \t\t\t--autostash --no-autostash\n-\t\t\t--verify --no-verify\n+\t\t\t--verify --no-verify --keep-base\n \t\t\t--keep-empty --root --force-rebase --no-ff\n \t\t\t--rerere-autoupdate\n \t\t\t--exec\n-- \n2.21.0.967.gf85e14fd49\n\n"},{"id":"374186","messageId":"eb64f6c91d27a6f9666c9829050995d9f9b52925.1555832783.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1555832783.git.liu.denton@gmail.com","subject":"[PATCH v7 1/6] t3431: add rebase --fork-point tests","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-21T08:11:18Z","receivedAt":"2019-04-21T08:11:23Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Signed-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3431-rebase-fork-point.sh | 53 ++++++++++++++++++++++++++++++++++++\n 1 file changed, 53 insertions(+)\n create mode 100755 t/t3431-rebase-fork-point.sh\n\ndiff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\nnew file mode 100755\nindex 0000000000..9b517d87a3\n--- /dev/null\n+++ b/t/t3431-rebase-fork-point.sh\n@@ -0,0 +1,53 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2019 Denton Liu\n+#\n+\n+test_description='git rebase --fork-point test'\n+\n+. ./test-lib.sh\n+\n+# A---B---D---E    (master)\n+#      \\\n+#       C*---F---G (side)\n+#\n+# C was formerly part of master but master was rewound to remove C\n+#\n+test_expect_success setup '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\ttest_commit C &&\n+\tgit branch -t side &&\n+\tgit reset --hard HEAD^ &&\n+\ttest_commit D &&\n+\ttest_commit E &&\n+\tgit checkout side &&\n+\ttest_commit F &&\n+\ttest_commit G\n+'\n+\n+test_rebase() {\n+\texpected=\"$1\" &&\n+\tshift &&\n+\ttest_expect_success \"git rebase $*\" \"\n+\t\tgit checkout master &&\n+\t\tgit reset --hard E &&\n+\t\tgit checkout side &&\n+\t\tgit reset --hard G &&\n+\t\tgit rebase $* &&\n+\t\ttest_write_lines $expected >expect &&\n+\t\tgit log --pretty=%s >actual &&\n+\t\ttest_cmp expect actual\n+\t\"\n+}\n+\n+test_rebase 'G F E D B A'\n+test_rebase 'G F D B A' --onto D\n+test_rebase 'G F C E D B A' --no-fork-point\n+test_rebase 'G F C D B A' --no-fork-point --onto D\n+test_rebase 'G F E D B A' --fork-point refs/heads/master\n+test_rebase 'G F D B A' --fork-point --onto D refs/heads/master\n+test_rebase 'G F C E D B A' refs/heads/master\n+test_rebase 'G F C D B A' --onto D refs/heads/master\n+\n+test_done\n-- \n2.21.0.967.gf85e14fd49\n\n"},{"id":"374187","messageId":"4c087ec0413880e1d3e6cbba95ff62224a4d95e6.1555832783.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1555832783.git.liu.denton@gmail.com","subject":"[PATCH v7 2/6] t3432: test rebase fast-forward behavior","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-21T08:11:21Z","receivedAt":"2019-04-21T08:11:29Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"When rebase is run on a branch that can be fast-forwarded, this should\nautomatically be done. Create test to ensure this behavior happens.\n\nThere are some cases that currently don't pass. The first case is where\na feature and master have diverged, running\n\"git rebase master... master\" causes a full rebase to happen even though\na fast-forward should happen.\n\nThe second case is when we are doing \"git rebase --fork-point\" and a\nfork-point commit is found. Once again, a full rebase happens even\nthough a fast-forward should happen.\n\nMark these cases as failure so we can fix it later.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3432-rebase-fast-forward.sh | 72 ++++++++++++++++++++++++++++++++++\n 1 file changed, 72 insertions(+)\n create mode 100755 t/t3432-rebase-fast-forward.sh\n\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nnew file mode 100755\nindex 0000000000..4f04d67fd7\n--- /dev/null\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -0,0 +1,72 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2019 Denton Liu\n+#\n+\n+test_description='ensure rebase fast-forwards commits when possible'\n+\n+. ./test-lib.sh\n+\n+test_expect_success setup '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\ttest_commit C &&\n+\ttest_commit D &&\n+\tgit checkout -t -b side\n+'\n+\n+test_rebase_same_head() {\n+\tstatus=\"$1\" &&\n+\tshift &&\n+\ttest_expect_$status \"git rebase $* with $changes is no-op\" \"\n+\t\toldhead=\\$(git rev-parse HEAD) &&\n+\t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n+\t\tgit rebase $* &&\n+\t\tnewhead=\\$(git rev-parse HEAD) &&\n+\t\ttest_cmp_rev \\$oldhead \\$newhead\n+\t\"\n+}\n+\n+changes='no changes'\n+test_rebase_same_head success\n+test_rebase_same_head success master\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --no-fork-point\n+test_rebase_same_head success --fork-point master\n+test_rebase_same_head failure --fork-point --onto B B\n+test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head success --fork-point --onto master... master\n+\n+test_expect_success 'add work to side' '\n+\ttest_commit E\n+'\n+\n+changes='our changes'\n+test_rebase_same_head success\n+test_rebase_same_head success master\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --no-fork-point\n+test_rebase_same_head success --fork-point master\n+test_rebase_same_head failure --fork-point --onto B B\n+test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head success --fork-point --onto master... master\n+\n+test_expect_success 'add work to upstream' '\n+\tgit checkout master &&\n+\ttest_commit F &&\n+\tgit checkout side\n+'\n+\n+changes='our and their changes'\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head failure --onto master... master\n+test_rebase_same_head failure --fork-point --onto B B\n+test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head failure --fork-point --onto master... master\n+\n+test_done\n-- \n2.21.0.967.gf85e14fd49\n\n"},{"id":"374188","messageId":"3d348d2c37751b8ecbbd244e03bd9fdd860290e2.1555832783.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1555832783.git.liu.denton@gmail.com","subject":"[PATCH v7 3/6] rebase: refactor can_fast_forward into goto tower","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-21T08:11:24Z","receivedAt":"2019-04-21T08:11:29Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Before, can_fast_forward was written with an if-else statement. However,\nin the future, we may be adding more termination cases which would lead\nto deeply nested if statements.\n\nRefactor to use a goto tower so that future cases can be easily\ninserted.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n builtin/rebase.c | 24 +++++++++++++++---------\n 1 file changed, 15 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 77deebc65c..de10b6f5ad 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -899,21 +899,27 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n \t\t\t    struct object_id *merge_base)\n {\n \tstruct commit *head = lookup_commit(the_repository, head_oid);\n-\tstruct commit_list *merge_bases;\n-\tint res;\n+\tstruct commit_list *merge_bases = NULL;\n+\tint res = 0;\n \n \tif (!head)\n-\t\treturn 0;\n+\t\tgoto done;\n \n \tmerge_bases = get_merge_bases(onto, head);\n-\tif (merge_bases && !merge_bases->next) {\n-\t\toidcpy(merge_base, &merge_bases->item->object.oid);\n-\t\tres = oideq(merge_base, &onto->object.oid);\n-\t} else {\n+\tif (!merge_bases || merge_bases->next) {\n \t\toidcpy(merge_base, &null_oid);\n-\t\tres = 0;\n+\t\tgoto done;\n \t}\n-\tfree_commit_list(merge_bases);\n+\n+\toidcpy(merge_base, &merge_bases->item->object.oid);\n+\tif (!oideq(merge_base, &onto->object.oid))\n+\t\tgoto done;\n+\n+\tres = 1;\n+\n+done:\n+\tif (merge_bases)\n+\t\tfree_commit_list(merge_bases);\n \treturn res && is_linear_history(onto, head);\n }\n \n-- \n2.21.0.967.gf85e14fd49\n\n"},{"id":"374189","messageId":"27cbcfaeae91b9abe2726995b5f26d23892cb7ec.1555832783.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1555832783.git.liu.denton@gmail.com","subject":"[PATCH v7 4/6] rebase: fast-forward --onto in more cases","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-21T08:11:26Z","receivedAt":"2019-04-21T08:11:32Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Before, when we had the following graph,\n\n\tA---B---C (master)\n\t     \\\n\t      D (side)\n\nrunning 'git rebase --onto master... master side' would result in D\nbeing always rebased, no matter what. However, the desired behavior is\nthat rebase should notice that this is fast-forwardable and do that\ninstead.\n\nAdd detection to `can_fast_forward` so that this case can be detected\nand a fast-forward will be performed. First of all, rewrite the function\nto use gotos which simplifies the logic. Next, since the\n\n\toptions.upstream &&\n\t!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)\n\nconditions were removed in `cmd_rebase`, we reintroduce a substitute in\n`can_fast_forward`. In particular, checking the merge bases of\n`upstream` and `head` fixes a failing case in t3416.\n\nThe abbreviated graph for t3416 is as follows:\n\n\t\t    F---G topic\n\t\t   /\n\t  A---B---C---D---E master\n\nand the failing command was\n\n\tgit rebase --onto master...topic F topic\n\nBefore, Git would see that there was one merge base (C), and the merge\nand onto were the same so it would incorrectly return 1, indicating that\nwe could fast-forward. This would cause the rebased graph to be 'ABCFG'\nwhen we were expecting 'ABCG'.\n\nWith the additional logic, we detect that upstream and head's merge base\nis F. Since onto isn't F, it means we're not rebasing the full set of\ncommits from master..topic. Since we're excluding some commits, a\nfast-forward cannot be performed and so we correctly return 0.\n\nAdd '-f' to test cases that failed as a result of this change because\nthey were not expecting a fast-forward so that a rebase is forced.\n\nWhile we're at it, remove a trailing whitespace from rebase.c.\n\nHelped-by: Phillip Wood <phillip.wood@dunelm.org.uk>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n builtin/rebase.c               | 29 ++++++++++++++++++++---------\n t/t3400-rebase.sh              |  2 +-\n t/t3404-rebase-interactive.sh  |  2 +-\n t/t3432-rebase-fast-forward.sh |  4 ++--\n 4 files changed, 24 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex de10b6f5ad..fdb42eb09f 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -895,8 +895,8 @@ static int is_linear_history(struct commit *from, struct commit *to)\n \treturn 1;\n }\n \n-static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n-\t\t\t    struct object_id *merge_base)\n+static int can_fast_forward(struct commit *onto, struct commit *upstream,\n+\t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n {\n \tstruct commit *head = lookup_commit(the_repository, head_oid);\n \tstruct commit_list *merge_bases = NULL;\n@@ -915,6 +915,17 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n \tif (!oideq(merge_base, &onto->object.oid))\n \t\tgoto done;\n \n+\tif (!upstream)\n+\t\tgoto done;\n+\n+\tfree_commit_list(merge_bases);\n+\tmerge_bases = get_merge_bases(upstream, head);\n+\tif (!merge_bases || merge_bases->next)\n+\t\tgoto done;\n+\n+\tif (!oideq(&onto->object.oid, &merge_bases->item->object.oid))\n+\t\tgoto done;\n+\n \tres = 1;\n \n done:\n@@ -1688,13 +1699,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \t/*\n \t * Check if we are already based on onto with linear history,\n-\t * but this should be done only when upstream and onto are the same\n-\t * and if this is not an interactive rebase.\n+\t * in which case we could fast-forward without replacing the commits\n+\t * with new commits recreated by replaying their changes. This\n+\t * optimization must not be done if this is an interactive rebase.\n \t */\n-\tif (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&\n-\t    !is_interactive(&options) && !options.restrict_revision &&\n-\t    options.upstream &&\n-\t    !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {\n+\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head,\n+\t\t    &merge_base) &&\n+\t    !is_interactive(&options) && !options.restrict_revision) {\n \t\tint flag;\n \n \t\tif (!(options.flags & REBASE_FORCE)) {\n@@ -1788,7 +1799,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tstrbuf_addf(&msg, \"%s: checkout %s\",\n \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);\n \tif (reset_head(&options.onto->object.oid, \"checkout\", NULL,\n-\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD | \n+\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n \t\t       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n \t\t       NULL, msg.buf))\n \t\tdie(_(\"Could not detach HEAD\"));\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 460d0523be..604d624ff8 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' '\n \t\techo two >>init.t &&\n \t\tgit commit -a -m two &&\n \t\tgit tag two &&\n-\t\ttest_must_fail git rebase --onto init HEAD^ &&\n+\t\ttest_must_fail git rebase -f --onto init HEAD^ &&\n \t\tGIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&\n \t\tgrep \"show.*$(git rev-parse two)\" stderr\n \t)\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex b60b11f9f2..f054186cc7 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1066,7 +1066,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int\n \tgit reset --hard &&\n \tgit checkout conflict-branch &&\n \tset_fake_editor &&\n-\ttest_must_fail git rebase --onto HEAD~2 HEAD~ &&\n+\ttest_must_fail git rebase -f --onto HEAD~2 HEAD~ &&\n \ttest_must_fail git rebase --edit-todo &&\n \tgit rebase --abort\n '\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 4f04d67fd7..d0e5b1f3e6 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -64,9 +64,9 @@ test_expect_success 'add work to upstream' '\n changes='our and their changes'\n test_rebase_same_head success --onto B B\n test_rebase_same_head success --onto B... B\n-test_rebase_same_head failure --onto master... master\n+test_rebase_same_head success --onto master... master\n test_rebase_same_head failure --fork-point --onto B B\n test_rebase_same_head failure --fork-point --onto B... B\n-test_rebase_same_head failure --fork-point --onto master... master\n+test_rebase_same_head success --fork-point --onto master... master\n \n test_done\n-- \n2.21.0.967.gf85e14fd49\n\n"},{"id":"374190","messageId":"8254730810eed7b413baebe918efb43e26e04af8.1555832783.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1555832783.git.liu.denton@gmail.com","subject":"[PATCH v7 5/6] rebase: fast-forward --fork-point in more cases","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-21T08:11:29Z","receivedAt":"2019-04-21T08:11:35Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Before, when we rebased with a --fork-point invocation where the\nfork-point wasn't empty, we would be setting options.restrict_revision.\nThe fast-forward logic would automatically declare that the rebase was\nnot fast-forwardable if it was set. However, this was painting with a\nvery broad brush.\n\nRefine the logic so that we can fast-forward in the case where the\nrestricted revision is equal to the merge base, since we stop rebasing\nat the merge base anyway.\n\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n builtin/rebase.c               | 10 +++++++---\n t/t3432-rebase-fast-forward.sh | 12 ++++++------\n 2 files changed, 13 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex fdb42eb09f..66c59ebe22 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -896,6 +896,7 @@ static int is_linear_history(struct commit *from, struct commit *to)\n }\n \n static int can_fast_forward(struct commit *onto, struct commit *upstream,\n+\t\t\t    struct commit *restrict_revision,\n \t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n {\n \tstruct commit *head = lookup_commit(the_repository, head_oid);\n@@ -915,6 +916,9 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,\n \tif (!oideq(merge_base, &onto->object.oid))\n \t\tgoto done;\n \n+\tif (restrict_revision && !oideq(&restrict_revision->object.oid, merge_base))\n+\t\tgoto done;\n+\n \tif (!upstream)\n \t\tgoto done;\n \n@@ -1703,9 +1707,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t * with new commits recreated by replaying their changes. This\n \t * optimization must not be done if this is an interactive rebase.\n \t */\n-\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head,\n-\t\t    &merge_base) &&\n-\t    !is_interactive(&options) && !options.restrict_revision) {\n+\tif (can_fast_forward(options.onto, options.upstream, options.restrict_revision,\n+\t\t    &options.orig_head, &merge_base) &&\n+\t    !is_interactive(&options)) {\n \t\tint flag;\n \n \t\tif (!(options.flags & REBASE_FORCE)) {\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex d0e5b1f3e6..1cb2896fb4 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -35,8 +35,8 @@ test_rebase_same_head success --onto B... B\n test_rebase_same_head success --onto master... master\n test_rebase_same_head success --no-fork-point\n test_rebase_same_head success --fork-point master\n-test_rebase_same_head failure --fork-point --onto B B\n-test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head success --fork-point --onto B B\n+test_rebase_same_head success --fork-point --onto B... B\n test_rebase_same_head success --fork-point --onto master... master\n \n test_expect_success 'add work to side' '\n@@ -51,8 +51,8 @@ test_rebase_same_head success --onto B... B\n test_rebase_same_head success --onto master... master\n test_rebase_same_head success --no-fork-point\n test_rebase_same_head success --fork-point master\n-test_rebase_same_head failure --fork-point --onto B B\n-test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head success --fork-point --onto B B\n+test_rebase_same_head success --fork-point --onto B... B\n test_rebase_same_head success --fork-point --onto master... master\n \n test_expect_success 'add work to upstream' '\n@@ -65,8 +65,8 @@ changes='our and their changes'\n test_rebase_same_head success --onto B B\n test_rebase_same_head success --onto B... B\n test_rebase_same_head success --onto master... master\n-test_rebase_same_head failure --fork-point --onto B B\n-test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head success --fork-point --onto B B\n+test_rebase_same_head success --fork-point --onto B... B\n test_rebase_same_head success --fork-point --onto master... master\n \n test_done\n-- \n2.21.0.967.gf85e14fd49\n\n"},{"id":"374191","messageId":"f18d3ee3fa6f69b79ca86ec6de12a47f0d51bf66.1555832783.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1555832783.git.liu.denton@gmail.com","subject":"[PATCH v7 6/6] rebase: teach rebase --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-21T08:11:32Z","receivedAt":"2019-04-21T08:11:37Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"A common scenario is if a user is working on a topic branch and they\nwish to make some changes to intermediate commits or autosquash, they\nwould run something such as\n\n\tgit rebase -i --onto master... master\n\nin order to preserve the merge base. This is useful when contributing a\npatch series to the Git mailing list, one often starts on top of the\ncurrent 'master'. However, while developing the patches, 'master' is\nalso developed further and it is sometimes not the bst idea to keep\nrebasing on top of 'master', but to keep the base commit as-is.\n\nAlternatively, a user wishing to test individual commits in a topic\nbranch without changing anything may run\n\n\tgit rebase -x ./test.sh master... master\n\nSince rebasing onto the merge base of the branch and the upstream is\nsuch a common case, introduce the --keep-base option as a shortcut.\n\nThis allows us to rewrite the above as\n\n\tgit rebase -i --keep-base master\n\nand\n\n\tgit rebase -x ./test.sh --keep-base master\n\nrespectively.\n\nAdd tests to ensure --keep-base works correctly in the normal case and\nfails when there are multiple merge bases, both in regular and\ninteractive mode. Also, test to make sure conflicting options cause\nrebase to fail. While we're adding test cases, add a missing\nset_fake_editor call to 'rebase -i --onto master...side'.\n\nWhile we're documenting the --keep-base option, change an instance of\n\"merge-base\" to \"merge base\", which is the consistent spelling.\n\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nHelped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n Documentation/git-rebase.txt           | 30 ++++++++++++--\n builtin/rebase.c                       | 32 ++++++++++++---\n contrib/completion/git-completion.bash |  2 +-\n t/t3416-rebase-onto-threedots.sh       | 57 ++++++++++++++++++++++++++\n t/t3431-rebase-fork-point.sh           |  4 ++\n t/t3432-rebase-fast-forward.sh         | 11 +++++\n 6 files changed, 126 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 6363d674b7..569ab708d4 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip\n SYNOPSIS\n --------\n [verse]\n-'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n-\t[<upstream> [<branch>]]\n+'git rebase' [-i | --interactive] [<options>] [--exec <cmd>]\n+\t[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\n 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n \t--root [<branch>]\n 'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch\n@@ -217,6 +217,24 @@ As a special case, you may use \"A\\...B\" as a shortcut for the\n merge base of A and B if there is exactly one merge base. You can\n leave out at most one of A and B, in which case it defaults to HEAD.\n \n+--keep-base::\n+\tSet the starting point at which to create the new commits to the\n+\tmerge base of <upstream> <branch>. Running\n+\t'git rebase --keep-base <upstream> <branch>' is equivalent to\n+\trunning 'git rebase --onto <upstream>... <upstream>'.\n++\n+This option is useful in the case where one is developing a feature on\n+top of an upstream branch. While the feature is being worked on, the\n+upstream branch may advance and it may not be the best idea to keep\n+rebasing on top of the upstream but to keep the base commit as-is.\n++\n+Although both this option and --fork-point find the merge base between\n+<upstream> and <branch>, this option uses the merge base as the _starting\n+point_ on which new commits will be created, whereas --fork-point uses\n+the merge base to determine the _set of commits_ which will be rebased.\n++\n+See also INCOMPATIBLE OPTIONS below.\n+\n <upstream>::\n \tUpstream branch to compare against.  May be any valid commit,\n \tnot just an existing branch name. Defaults to the configured\n@@ -364,6 +382,10 @@ ends up being empty, the <upstream> will be used as a fallback.\n +\n If either <upstream> or --root is given on the command line, then the\n default is `--no-fork-point`, otherwise the default is `--fork-point`.\n++\n+If your branch was based on <upstream> but <upstream> was rewound and\n+your branch contains commits which were dropped, this option can be used\n+with `--keep-base` in order to drop those commits from your branch.\n \n --ignore-whitespace::\n --whitespace=<option>::\n@@ -539,6 +561,8 @@ In addition, the following pairs of options are incompatible:\n  * --preserve-merges and --rebase-merges\n  * --rebase-merges and --strategy\n  * --rebase-merges and --strategy-option\n+ * --keep-base and --onto\n+ * --keep-base and --root\n \n BEHAVIORAL DIFFERENCES\n -----------------------\n@@ -863,7 +887,7 @@ NOTE: While an \"easy case recovery\" sometimes appears to be successful\n       --interactive` will be **resurrected**!\n \n The idea is to manually tell 'git rebase' \"where the old 'subsystem'\n-ended and your 'topic' began\", that is, what the old merge-base\n+ended and your 'topic' began\", that is, what the old merge base\n between them was.  You will have to find a way to name the last commit\n of the old 'subsystem', for example:\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 66c59ebe22..19e7e2a1c9 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -27,8 +27,8 @@\n #include \"branch.h\"\n \n static char const * const builtin_rebase_usage[] = {\n-\tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n-\t\t\"[<upstream>] [<branch>]\"),\n+\tN_(\"git rebase [-i] [options] [--exec <cmd>] \"\n+\t\t\"[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\"),\n \tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n \t\t\"--root [<branch>]\"),\n \tN_(\"git rebase --continue | --abort | --skip | --edit-todo\"),\n@@ -1039,6 +1039,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t};\n \tconst char *branch_name;\n \tint ret, flags, total_argc, in_progress = 0;\n+\tint keep_base = 0;\n \tint ok_to_skip_pre_rebase = 0;\n \tstruct strbuf msg = STRBUF_INIT;\n \tstruct strbuf revisions = STRBUF_INIT;\n@@ -1072,6 +1073,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"rebase onto given branch instead of upstream\")),\n+\t\tOPT_BOOL(0, \"keep-base\", &keep_base,\n+\t\t\t N_(\"use the merge-base of upstream and branch as the current base\")),\n \t\tOPT_BOOL(0, \"no-verify\", &ok_to_skip_pre_rebase,\n \t\t\t N_(\"allow pre-rebase hook to run\")),\n \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n@@ -1238,6 +1241,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tusage_with_options(builtin_rebase_usage,\n \t\t\t\t   builtin_rebase_options);\n \n+\tif (keep_base) {\n+\t\tif (options.onto_name)\n+\t\t\tdie(_(\"cannot combine '--keep-base' with '--onto'\"));\n+\t\tif (options.root)\n+\t\t\tdie(_(\"cannot combine '--keep-base' with '--root'\"));\n+\t}\n+\n \tif (action != NO_ACTION && !in_progress)\n \t\tdie(_(\"No rebase in progress?\"));\n \tsetenv(GIT_REFLOG_ACTION_ENVIRONMENT, \"rebase\", 0);\n@@ -1562,12 +1572,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t}\n \n \t/* Make sure the branch to rebase onto is valid. */\n-\tif (!options.onto_name)\n+\tif (keep_base) {\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addstr(&buf, options.upstream_name);\n+\t\tstrbuf_addstr(&buf, \"...\");\n+\t\toptions.onto_name = xstrdup(buf.buf);\n+\t} else if (!options.onto_name)\n \t\toptions.onto_name = options.upstream_name;\n \tif (strstr(options.onto_name, \"...\")) {\n-\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0)\n-\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n-\t\t\t    options.onto_name);\n+\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0) {\n+\t\t\tif (keep_base)\n+\t\t\t\tdie(_(\"'%s': need exactly one merge base with branch\"),\n+\t\t\t\t    options.upstream_name);\n+\t\t\telse\n+\t\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n+\t\t\t\t    options.onto_name);\n+\t\t}\n \t\toptions.onto = lookup_commit_or_die(&merge_base,\n \t\t\t\t\t\t    options.onto_name);\n \t} else {\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 976e4a6548..f9dc431a39 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2027,7 +2027,7 @@ _git_rebase ()\n \t\t\t--autosquash --no-autosquash\n \t\t\t--fork-point --no-fork-point\n \t\t\t--autostash --no-autostash\n-\t\t\t--verify --no-verify\n+\t\t\t--verify --no-verify --keep-base\n \t\t\t--keep-empty --root --force-rebase --no-ff\n \t\t\t--rerere-autoupdate\n \t\t\t--exec\ndiff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh\nindex ddf2f64853..9c2548423b 100755\n--- a/t/t3416-rebase-onto-threedots.sh\n+++ b/t/t3416-rebase-onto-threedots.sh\n@@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' '\n \tgit checkout side &&\n \tgit reset --hard K &&\n \n+\tset_fake_editor &&\n \ttest_must_fail git rebase -i --onto master...side J\n '\n \n+test_expect_success 'rebase --keep-base --onto incompatible' '\n+\ttest_must_fail git rebase --keep-base --onto master...\n+'\n+\n+test_expect_success 'rebase --keep-base --root incompatible' '\n+\ttest_must_fail git rebase --keep-base --root\n+'\n+\n+test_expect_success 'rebase --keep-base master from topic' '\n+\tgit reset --hard &&\n+\tgit checkout topic &&\n+\tgit reset --hard G &&\n+\n+\tgit rebase --keep-base master &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base master HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --keep-base master from side' '\n+\tgit reset --hard &&\n+\tgit checkout side &&\n+\tgit reset --hard K &&\n+\n+\ttest_must_fail git rebase --keep-base master\n+'\n+\n+test_expect_success 'rebase -i --keep-base master from topic' '\n+\tgit reset --hard &&\n+\tgit checkout topic &&\n+\tgit reset --hard G &&\n+\n+\tset_fake_editor &&\n+\tEXPECT_COUNT=2 git rebase -i --keep-base master &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base master HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase -i --keep-base master from side' '\n+\tgit reset --hard &&\n+\tgit checkout side &&\n+\tgit reset --hard K &&\n+\n+\tset_fake_editor &&\n+\ttest_must_fail git rebase -i --keep-base master\n+'\n+\n test_done\ndiff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\nindex 9b517d87a3..f848946f5a 100755\n--- a/t/t3431-rebase-fork-point.sh\n+++ b/t/t3431-rebase-fork-point.sh\n@@ -43,11 +43,15 @@ test_rebase() {\n \n test_rebase 'G F E D B A'\n test_rebase 'G F D B A' --onto D\n+test_rebase 'G F B A' --keep-base\n test_rebase 'G F C E D B A' --no-fork-point\n test_rebase 'G F C D B A' --no-fork-point --onto D\n+test_rebase 'G F C B A' --no-fork-point --keep-base\n test_rebase 'G F E D B A' --fork-point refs/heads/master\n test_rebase 'G F D B A' --fork-point --onto D refs/heads/master\n+test_rebase 'G F B A' --fork-point --keep-base refs/heads/master\n test_rebase 'G F C E D B A' refs/heads/master\n test_rebase 'G F C D B A' --onto D refs/heads/master\n+test_rebase 'G F C B A' --keep-base refs/heads/master\n \n test_done\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 1cb2896fb4..1ba02cc95f 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -33,11 +33,15 @@ test_rebase_same_head success master\n test_rebase_same_head success --onto B B\n test_rebase_same_head success --onto B... B\n test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --keep-base master\n+test_rebase_same_head success --keep-base\n test_rebase_same_head success --no-fork-point\n+test_rebase_same_head success --keep-base --no-fork-point\n test_rebase_same_head success --fork-point master\n test_rebase_same_head success --fork-point --onto B B\n test_rebase_same_head success --fork-point --onto B... B\n test_rebase_same_head success --fork-point --onto master... master\n+test_rebase_same_head success --fork-point --keep-base master\n \n test_expect_success 'add work to side' '\n \ttest_commit E\n@@ -49,11 +53,15 @@ test_rebase_same_head success master\n test_rebase_same_head success --onto B B\n test_rebase_same_head success --onto B... B\n test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --keep-base master\n+test_rebase_same_head success --keep-base\n test_rebase_same_head success --no-fork-point\n+test_rebase_same_head success --keep-base --no-fork-point\n test_rebase_same_head success --fork-point master\n test_rebase_same_head success --fork-point --onto B B\n test_rebase_same_head success --fork-point --onto B... B\n test_rebase_same_head success --fork-point --onto master... master\n+test_rebase_same_head success --fork-point --keep-base master\n \n test_expect_success 'add work to upstream' '\n \tgit checkout master &&\n@@ -65,8 +73,11 @@ changes='our and their changes'\n test_rebase_same_head success --onto B B\n test_rebase_same_head success --onto B... B\n test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --keep-base master\n+test_rebase_same_head success --keep-base\n test_rebase_same_head success --fork-point --onto B B\n test_rebase_same_head success --fork-point --onto B... B\n test_rebase_same_head success --fork-point --onto master... master\n+test_rebase_same_head success --fork-point --keep-base master\n \n test_done\n-- \n2.21.0.967.gf85e14fd49\n\n"},{"id":"374341","messageId":"20190423231243.GA14157@dev-l","threadId":"50810","inReplyTo":"eb64f6c91d27a6f9666c9829050995d9f9b52925.1555832783.git.liu.denton@gmail.com","subject":"Re: [PATCH v7 1/6] t3431: add rebase --fork-point tests","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-04-23T23:12:43Z","receivedAt":"2019-04-23T23:12:48Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Hi Junio,\n\nOn Sun, Apr 21, 2019 at 01:11:18AM -0700, Denton Liu wrote:\n> Signed-off-by: Denton Liu <liu.denton@gmail.com>\n> ---\n>  t/t3431-rebase-fork-point.sh | 53 ++++++++++++++++++++++++++++++++++++\n>  1 file changed, 53 insertions(+)\n>  create mode 100755 t/t3431-rebase-fork-point.sh\n> \n> diff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\n> new file mode 100755\n> index 0000000000..9b517d87a3\n> --- /dev/null\n> +++ b/t/t3431-rebase-fork-point.sh\n> @@ -0,0 +1,53 @@\n> +#!/bin/sh\n> +#\n> +# Copyright (c) 2019 Denton Liu\n> +#\n> +\n> +test_description='git rebase --fork-point test'\n> +\n> +. ./test-lib.sh\n> +\n> +# A---B---D---E    (master)\n> +#      \\\n> +#       C*---F---G (side)\n> +#\n> +# C was formerly part of master but master was rewound to remove C\n> +#\n> +test_expect_success setup '\n> +\ttest_commit A &&\n> +\ttest_commit B &&\n> +\ttest_commit C &&\n> +\tgit branch -t side &&\n> +\tgit reset --hard HEAD^ &&\n> +\ttest_commit D &&\n> +\ttest_commit E &&\n> +\tgit checkout side &&\n> +\ttest_commit F &&\n> +\ttest_commit G\n> +'\n> +\n> +test_rebase() {\n\nI read in an email thread earlier that the preferred style for function\ndefinitions is to include a space after the name. Sorry for not catching\nthis earlier.\n\nCould you please change this to `test_rebase () {` for me?\n\nAlso, same comment applies to `test_rebase_same_head` in 2/6.\n\nThanks so much,\n\nDenton\n\n> +\texpected=\"$1\" &&\n> +\tshift &&\n> +\ttest_expect_success \"git rebase $*\" \"\n> +\t\tgit checkout master &&\n> +\t\tgit reset --hard E &&\n> +\t\tgit checkout side &&\n> +\t\tgit reset --hard G &&\n> +\t\tgit rebase $* &&\n> +\t\ttest_write_lines $expected >expect &&\n> +\t\tgit log --pretty=%s >actual &&\n> +\t\ttest_cmp expect actual\n> +\t\"\n> +}\n> +\n> +test_rebase 'G F E D B A'\n> +test_rebase 'G F D B A' --onto D\n> +test_rebase 'G F C E D B A' --no-fork-point\n> +test_rebase 'G F C D B A' --no-fork-point --onto D\n> +test_rebase 'G F E D B A' --fork-point refs/heads/master\n> +test_rebase 'G F D B A' --fork-point --onto D refs/heads/master\n> +test_rebase 'G F C E D B A' refs/heads/master\n> +test_rebase 'G F C D B A' --onto D refs/heads/master\n> +\n> +test_done\n> -- \n> 2.21.0.967.gf85e14fd49\n> \n"},{"id":"375068","messageId":"20190508001252.15752-1-avarab@gmail.com","threadId":"50810","inReplyTo":"cover.1555832783.git.liu.denton@gmail.com","subject":"[RFC WIP PATCH v8 00/13] learn --keep-base & more","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-05-08T00:12:39Z","receivedAt":"2019-05-08T00:13:09Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"This is a WIP series I have that I figured I'd send out as-is for\ncomment since Junio said he'd be merging dl/rebase-i-keep-base down.\n\nSo I wanted to test it, and as seen early in this series in 3/13 and\n4/13 we had significant blindspots in our tests, i.e. there were no\ntests for whether --no-ff bypassed the amended logic as it should.\n\nAs seen from those tests we may have some bugs here, either existing\nor new, needs more poking at it. We are also inconsistent with whether\na --no-ff yields a different SHA-1 after the forced rebase, surely it\nalways should, no?\n\nThen in 9/13 and 10/13 I re-added the incomplete patches I had in\nhttps://public-inbox.org/git/20190221214059.9195-1-avarab@gmail.com/\nto see if my tests passed with Denton's --fork-point code, they\ndo. Yay!\n\nLeft them there because I was wondering if I needed to port some/all\nof the tests over, and maybe amend a commit message to reword some of\nmy findings in\nhttps://public-inbox.org/git/871s3z6a4q.fsf@evledraar.gmail.com/\n\nThen I have 11/13 and 12/13 which seem pretty sensible to me as-is,\nand finally I wanted --preserve-merges and --rebase-merges to also\nbenefit from this logic, so 13/13 is a WIP patch for that. The code\nshould be done (although maybe there's a better way to do it...), but\nit needs a better commit message & tests.\n\nDenton Liu (6):\n  t3431: add rebase --fork-point tests\n  t3432: test rebase fast-forward behavior\n  rebase: refactor can_fast_forward into goto tower\n  rebase: fast-forward --onto in more cases\n  rebase: fast-forward --fork-point in more cases\n  rebase: teach rebase --keep-base\n\nÆvar Arnfjörð Bjarmason (7):\n  t3432: distinguish \"noop-same\" v.s. \"work-same\" in \"same head\" tests\n  t3432: test for --no-ff's interaction with fast-forward\n  rebase tests: test linear branch topology\n  rebase: don't rebase linear topology with --fork-point\n  rebase: eliminate side-effects from can_fast_forward()\n  rebase: add a should_fast_forward() utility function\n  WIP: can_fast_forward() support for --preserve-merges and\n    --rebase-merges\n\n Documentation/git-rebase.txt           |  30 +++++-\n builtin/rebase.c                       | 117 +++++++++++++++++-----\n contrib/completion/git-completion.bash |   2 +-\n t/t3400-rebase.sh                      |   2 +-\n t/t3404-rebase-interactive.sh          |   2 +-\n t/t3416-rebase-onto-threedots.sh       |  57 +++++++++++\n t/t3421-rebase-topology-linear.sh      |  44 +++++++++\n t/t3431-rebase-fork-point.sh           |  57 +++++++++++\n t/t3432-rebase-fast-forward.sh         | 128 +++++++++++++++++++++++++\n 9 files changed, 407 insertions(+), 32 deletions(-)\n create mode 100755 t/t3431-rebase-fork-point.sh\n create mode 100755 t/t3432-rebase-fast-forward.sh\n\nRange-diff:\n 1:  aececab3b4 =  1:  0c931f6275 t3431: add rebase --fork-point tests\n 2:  238bf1db83 =  2:  b5bdca58db t3432: test rebase fast-forward behavior\n -:  ---------- >  3:  5d057a1240 t3432: distinguish \"noop-same\" v.s. \"work-same\" in \"same head\" tests\n -:  ---------- >  4:  06084d9891 t3432: test for --no-ff's interaction with fast-forward\n 3:  526c03b5a9 =  5:  f5a2172398 rebase: refactor can_fast_forward into goto tower\n 4:  10572de16f !  6:  ea90934368 rebase: fast-forward --onto in more cases\n    @@ -146,13 +146,13 @@\n      +++ b/t/t3432-rebase-fast-forward.sh\n     @@\n      changes='our and their changes'\n    - test_rebase_same_head success --onto B B\n    - test_rebase_same_head success --onto B... B\n    --test_rebase_same_head failure --onto master... master\n    -+test_rebase_same_head success --onto master... master\n    - test_rebase_same_head failure --fork-point --onto B B\n    - test_rebase_same_head failure --fork-point --onto B... B\n    --test_rebase_same_head failure --fork-point --onto master... master\n    -+test_rebase_same_head success --fork-point --onto master... master\n    + test_rebase_same_head success noop same success noop-force diff --onto B B\n    + test_rebase_same_head success noop same success noop-force diff --onto B... B\n    +-test_rebase_same_head failure work same success work diff --onto master... master\n    ++test_rebase_same_head success noop same success work diff --onto master... master\n    + test_rebase_same_head failure work same success work diff --fork-point --onto B B\n    + test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n    +-test_rebase_same_head failure work same success work diff --fork-point --onto master... master\n    ++test_rebase_same_head success noop same success work diff --fork-point --onto master... master\n      \n      test_done\n 5:  446985193c !  7:  46660cefe0 rebase: fast-forward --fork-point in more cases\n    @@ -55,35 +55,45 @@\n      --- a/t/t3432-rebase-fast-forward.sh\n      +++ b/t/t3432-rebase-fast-forward.sh\n     @@\n    - test_rebase_same_head success --onto master... master\n    - test_rebase_same_head success --no-fork-point\n    - test_rebase_same_head success --fork-point master\n    --test_rebase_same_head failure --fork-point --onto B B\n    --test_rebase_same_head failure --fork-point --onto B... B\n    -+test_rebase_same_head success --fork-point --onto B B\n    -+test_rebase_same_head success --fork-point --onto B... B\n    - test_rebase_same_head success --fork-point --onto master... master\n    + }\n      \n    - test_expect_success 'add work to side' '\n    -@@\n    - test_rebase_same_head success --onto master... master\n    - test_rebase_same_head success --no-fork-point\n    - test_rebase_same_head success --fork-point master\n    --test_rebase_same_head failure --fork-point --onto B B\n    --test_rebase_same_head failure --fork-point --onto B... B\n    -+test_rebase_same_head success --fork-point --onto B B\n    -+test_rebase_same_head success --fork-point --onto B... B\n    - test_rebase_same_head success --fork-point --onto master... master\n    + changes='no changes'\n    +-test_rebase_same_head success work same success work same\n    ++test_rebase_same_head success noop same success work same\n    + test_rebase_same_head success noop same success noop-force same master\n    + test_rebase_same_head success noop same success noop-force diff --onto B B\n    + test_rebase_same_head success noop same success noop-force diff --onto B... B\n    + test_rebase_same_head success noop same success noop-force same --onto master... master\n    + test_rebase_same_head success noop same success noop-force same --no-fork-point\n    +-test_rebase_same_head success work same success work same --fork-point master\n    +-test_rebase_same_head failure noop same success work diff --fork-point --onto B B\n    +-test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n    +-test_rebase_same_head success work same success work same --fork-point --onto master... master\n    ++test_rebase_same_head success noop same success work same --fork-point master\n    ++test_rebase_same_head success noop same success work diff --fork-point --onto B B\n    ++test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n    ++test_rebase_same_head success noop same success work same --fork-point --onto master... master\n      \n    - test_expect_success 'add work to upstream' '\n    -@@\n    - test_rebase_same_head success --onto B B\n    - test_rebase_same_head success --onto B... B\n    - test_rebase_same_head success --onto master... master\n    --test_rebase_same_head failure --fork-point --onto B B\n    --test_rebase_same_head failure --fork-point --onto B... B\n    -+test_rebase_same_head success --fork-point --onto B B\n    -+test_rebase_same_head success --fork-point --onto B... B\n    - test_rebase_same_head success --fork-point --onto master... master\n    + test_expect_success 'add work same  to side' '\n    + \ttest_commit E\n    + '\n    + \n    + changes='our changes'\n    +-test_rebase_same_head success work same success work same\n    ++test_rebase_same_head success noop same success work same\n    + test_rebase_same_head success noop same success noop-force same master\n    + test_rebase_same_head success noop same success noop-force diff --onto B B\n    + test_rebase_same_head success noop same success noop-force diff --onto B... B\n    + test_rebase_same_head success noop same success noop-force same --onto master... master\n    + test_rebase_same_head success noop same success noop-force same --no-fork-point\n    +-test_rebase_same_head success work same success work same --fork-point master\n    +-test_rebase_same_head failure work same success work diff --fork-point --onto B B\n    +-test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n    +-test_rebase_same_head success work same success work same --fork-point --onto master... master\n    ++test_rebase_same_head success noop same success work same --fork-point master\n    ++test_rebase_same_head success noop same success work diff --fork-point --onto B B\n    ++test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n    ++test_rebase_same_head success noop same success work same --fork-point --onto master... master\n      \n    - test_done\n    + test_expect_success 'add work same  to upstream' '\n    + \tgit checkout master &&\n 6:  88096495c2 !  8:  a59ff76704 rebase: teach rebase --keep-base\n    @@ -149,8 +149,8 @@\n      \t\t\t N_(\"allow pre-rebase hook to run\")),\n      \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n     @@\n    - \t\tusage_with_options(builtin_rebase_usage,\n    - \t\t\t\t   builtin_rebase_options);\n    + \t\twarning(_(\"git rebase --preserve-merges is deprecated. \"\n    + \t\t\t  \"Use --rebase-merges instead.\"));\n      \n     +\tif (keep_base) {\n     +\t\tif (options.onto_name)\n    @@ -296,46 +296,46 @@\n      --- a/t/t3432-rebase-fast-forward.sh\n      +++ b/t/t3432-rebase-fast-forward.sh\n     @@\n    - test_rebase_same_head success --onto B B\n    - test_rebase_same_head success --onto B... B\n    - test_rebase_same_head success --onto master... master\n    -+test_rebase_same_head success --keep-base master\n    -+test_rebase_same_head success --keep-base\n    - test_rebase_same_head success --no-fork-point\n    -+test_rebase_same_head success --keep-base --no-fork-point\n    - test_rebase_same_head success --fork-point master\n    - test_rebase_same_head success --fork-point --onto B B\n    - test_rebase_same_head success --fork-point --onto B... B\n    - test_rebase_same_head success --fork-point --onto master... master\n    -+test_rebase_same_head success --fork-point --keep-base master\n    + test_rebase_same_head success noop same success noop-force diff --onto B B\n    + test_rebase_same_head success noop same success noop-force diff --onto B... B\n    + test_rebase_same_head success noop same success noop-force same --onto master... master\n    ++test_rebase_same_head success noop same success noop-force same --keep-base master\n    ++test_rebase_same_head success noop same success noop-force same --keep-base\n    + test_rebase_same_head success noop same success noop-force same --no-fork-point\n    ++test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point\n    + test_rebase_same_head success noop same success work same --fork-point master\n    + test_rebase_same_head success noop same success work diff --fork-point --onto B B\n    + test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n    + test_rebase_same_head success noop same success work same --fork-point --onto master... master\n    ++test_rebase_same_head success noop same success work same --keep-base --keep-base master\n      \n    - test_expect_success 'add work to side' '\n    + test_expect_success 'add work same  to side' '\n      \ttest_commit E\n     @@\n    - test_rebase_same_head success --onto B B\n    - test_rebase_same_head success --onto B... B\n    - test_rebase_same_head success --onto master... master\n    -+test_rebase_same_head success --keep-base master\n    -+test_rebase_same_head success --keep-base\n    - test_rebase_same_head success --no-fork-point\n    -+test_rebase_same_head success --keep-base --no-fork-point\n    - test_rebase_same_head success --fork-point master\n    - test_rebase_same_head success --fork-point --onto B B\n    - test_rebase_same_head success --fork-point --onto B... B\n    - test_rebase_same_head success --fork-point --onto master... master\n    -+test_rebase_same_head success --fork-point --keep-base master\n    + test_rebase_same_head success noop same success noop-force diff --onto B B\n    + test_rebase_same_head success noop same success noop-force diff --onto B... B\n    + test_rebase_same_head success noop same success noop-force same --onto master... master\n    ++test_rebase_same_head success noop same success noop-force same --keep-base master\n    ++test_rebase_same_head success noop same success noop-force same --keep-base\n    + test_rebase_same_head success noop same success noop-force same --no-fork-point\n    ++test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point\n    + test_rebase_same_head success noop same success work same --fork-point master\n    + test_rebase_same_head success noop same success work diff --fork-point --onto B B\n    + test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n    + test_rebase_same_head success noop same success work same --fork-point --onto master... master\n    ++test_rebase_same_head success noop same success work same --fork-point --keep-base master\n      \n    - test_expect_success 'add work to upstream' '\n    + test_expect_success 'add work same  to upstream' '\n      \tgit checkout master &&\n     @@\n    - test_rebase_same_head success --onto B B\n    - test_rebase_same_head success --onto B... B\n    - test_rebase_same_head success --onto master... master\n    -+test_rebase_same_head success --keep-base master\n    -+test_rebase_same_head success --keep-base\n    - test_rebase_same_head success --fork-point --onto B B\n    - test_rebase_same_head success --fork-point --onto B... B\n    - test_rebase_same_head success --fork-point --onto master... master\n    -+test_rebase_same_head success --fork-point --keep-base master\n    + test_rebase_same_head success noop same success noop-force diff --onto B B\n    + test_rebase_same_head success noop same success noop-force diff --onto B... B\n    + test_rebase_same_head success noop same success work diff --onto master... master\n    ++test_rebase_same_head success noop same success work diff --keep-base master\n    ++test_rebase_same_head success noop same success work diff --keep-base\n    + test_rebase_same_head failure work same success work diff --fork-point --onto B B\n    + test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n    + test_rebase_same_head success noop same success work diff --fork-point --onto master... master\n    ++test_rebase_same_head success noop same success work diff --fork-point --keep-base master\n      \n      test_done\n -:  ---------- >  9:  46ccfca308 rebase tests: test linear branch topology\n -:  ---------- > 10:  b06e84c6d6 rebase: don't rebase linear topology with --fork-point\n -:  ---------- > 11:  bf13aa8a80 rebase: eliminate side-effects from can_fast_forward()\n -:  ---------- > 12:  2ffbb6c342 rebase: add a should_fast_forward() utility function\n -:  ---------- > 13:  20c38b78c7 WIP: can_fast_forward() support for --preserve-merges and --rebase-merges\n-- \n2.21.0.1020.gf2820cf01a\n\n"},{"id":"375069","messageId":"20190508001252.15752-2-avarab@gmail.com","threadId":"50810","inReplyTo":"cover.1555832783.git.liu.denton@gmail.com","subject":"[RFC WIP PATCH v8 01/13] t3431: add rebase --fork-point tests","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-05-08T00:12:40Z","receivedAt":"2019-05-08T00:13:11Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Denton Liu <liu.denton@gmail.com>\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n t/t3431-rebase-fork-point.sh | 53 ++++++++++++++++++++++++++++++++++++\n 1 file changed, 53 insertions(+)\n create mode 100755 t/t3431-rebase-fork-point.sh\n\ndiff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\nnew file mode 100755\nindex 0000000000..2d5c6e641e\n--- /dev/null\n+++ b/t/t3431-rebase-fork-point.sh\n@@ -0,0 +1,53 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2019 Denton Liu\n+#\n+\n+test_description='git rebase --fork-point test'\n+\n+. ./test-lib.sh\n+\n+# A---B---D---E    (master)\n+#      \\\n+#       C*---F---G (side)\n+#\n+# C was formerly part of master but master was rewound to remove C\n+#\n+test_expect_success setup '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\ttest_commit C &&\n+\tgit branch -t side &&\n+\tgit reset --hard HEAD^ &&\n+\ttest_commit D &&\n+\ttest_commit E &&\n+\tgit checkout side &&\n+\ttest_commit F &&\n+\ttest_commit G\n+'\n+\n+test_rebase () {\n+\texpected=\"$1\" &&\n+\tshift &&\n+\ttest_expect_success \"git rebase $*\" \"\n+\t\tgit checkout master &&\n+\t\tgit reset --hard E &&\n+\t\tgit checkout side &&\n+\t\tgit reset --hard G &&\n+\t\tgit rebase $* &&\n+\t\ttest_write_lines $expected >expect &&\n+\t\tgit log --pretty=%s >actual &&\n+\t\ttest_cmp expect actual\n+\t\"\n+}\n+\n+test_rebase 'G F E D B A'\n+test_rebase 'G F D B A' --onto D\n+test_rebase 'G F C E D B A' --no-fork-point\n+test_rebase 'G F C D B A' --no-fork-point --onto D\n+test_rebase 'G F E D B A' --fork-point refs/heads/master\n+test_rebase 'G F D B A' --fork-point --onto D refs/heads/master\n+test_rebase 'G F C E D B A' refs/heads/master\n+test_rebase 'G F C D B A' --onto D refs/heads/master\n+\n+test_done\n-- \n2.21.0.1020.gf2820cf01a\n\n"},{"id":"375070","messageId":"20190508001252.15752-3-avarab@gmail.com","threadId":"50810","inReplyTo":"cover.1555832783.git.liu.denton@gmail.com","subject":"[RFC WIP PATCH v8 02/13] t3432: test rebase fast-forward behavior","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-05-08T00:12:41Z","receivedAt":"2019-05-08T00:13:13Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Denton Liu <liu.denton@gmail.com>\n\nWhen rebase is run on a branch that can be fast-forwarded, this should\nautomatically be done. Create test to ensure this behavior happens.\n\nThere are some cases that currently don't pass. The first case is where\na feature and master have diverged, running\n\"git rebase master... master\" causes a full rebase to happen even though\na fast-forward should happen.\n\nThe second case is when we are doing \"git rebase --fork-point\" and a\nfork-point commit is found. Once again, a full rebase happens even\nthough a fast-forward should happen.\n\nMark these cases as failure so we can fix it later.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n t/t3432-rebase-fast-forward.sh | 72 ++++++++++++++++++++++++++++++++++\n 1 file changed, 72 insertions(+)\n create mode 100755 t/t3432-rebase-fast-forward.sh\n\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nnew file mode 100755\nindex 0000000000..f49af274e0\n--- /dev/null\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -0,0 +1,72 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2019 Denton Liu\n+#\n+\n+test_description='ensure rebase fast-forwards commits when possible'\n+\n+. ./test-lib.sh\n+\n+test_expect_success setup '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\ttest_commit C &&\n+\ttest_commit D &&\n+\tgit checkout -t -b side\n+'\n+\n+test_rebase_same_head () {\n+\tstatus=\"$1\" &&\n+\tshift &&\n+\ttest_expect_$status \"git rebase $* with $changes is no-op\" \"\n+\t\toldhead=\\$(git rev-parse HEAD) &&\n+\t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n+\t\tgit rebase $* &&\n+\t\tnewhead=\\$(git rev-parse HEAD) &&\n+\t\ttest_cmp_rev \\$oldhead \\$newhead\n+\t\"\n+}\n+\n+changes='no changes'\n+test_rebase_same_head success\n+test_rebase_same_head success master\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --no-fork-point\n+test_rebase_same_head success --fork-point master\n+test_rebase_same_head failure --fork-point --onto B B\n+test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head success --fork-point --onto master... master\n+\n+test_expect_success 'add work to side' '\n+\ttest_commit E\n+'\n+\n+changes='our changes'\n+test_rebase_same_head success\n+test_rebase_same_head success master\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --no-fork-point\n+test_rebase_same_head success --fork-point master\n+test_rebase_same_head failure --fork-point --onto B B\n+test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head success --fork-point --onto master... master\n+\n+test_expect_success 'add work to upstream' '\n+\tgit checkout master &&\n+\ttest_commit F &&\n+\tgit checkout side\n+'\n+\n+changes='our and their changes'\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head failure --onto master... master\n+test_rebase_same_head failure --fork-point --onto B B\n+test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head failure --fork-point --onto master... master\n+\n+test_done\n-- \n2.21.0.1020.gf2820cf01a\n\n"},{"id":"375071","messageId":"20190508001252.15752-4-avarab@gmail.com","threadId":"50810","inReplyTo":"cover.1555832783.git.liu.denton@gmail.com","subject":"[RFC WIP PATCH v8 03/13] t3432: distinguish \"noop-same\" v.s. \"work-same\" in \"same head\" tests","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-05-08T00:12:42Z","receivedAt":"2019-05-08T00:13:14Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Change \"same head\" introduced in the preceding commit to check whether\nthe rebase.c code lands in the can_fast_forward() case in, and thus\nprints out an \"is up to date\" and aborts early.\n\nIn some of these cases we make it past that and to \"rewinding head\",\nthen do a rebase, only to find out there's nothing to change so HEAD\nstays at the same OID.\n\nThese tests presumed these two cases were the same thing. In terms of\nwhere HEAD ends up they are, but we're not only interested in rebase\nsemantics, but also whether or not we're needlessly doing work when we\ncould avoid it entirely.\n\nI'm adding \"same\" and \"diff\" here because I'll follow-up and add\n--no-ff tests, where some of those will be \"diff\"-erent, so add the\n\"diff\" code already.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n t/t3432-rebase-fast-forward.sh | 79 +++++++++++++++++++++-------------\n 1 file changed, 48 insertions(+), 31 deletions(-)\n\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex f49af274e0..dd51e28b56 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -18,55 +18,72 @@ test_expect_success setup '\n test_rebase_same_head () {\n \tstatus=\"$1\" &&\n \tshift &&\n-\ttest_expect_$status \"git rebase $* with $changes is no-op\" \"\n+\twhat=\"$1\" &&\n+\tshift &&\n+\tcmp=\"$1\" &&\n+\tshift &&\n+\ttest_expect_$status \"git rebase $* with $changes is $what\" \"\n \t\toldhead=\\$(git rev-parse HEAD) &&\n \t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n-\t\tgit rebase $* &&\n+\t\tgit rebase $* >stdout &&\n+\t\tif test $what = work\n+\t\tthen\n+\t\t\ttest_i18ngrep 'rewinding head' stdout\n+\t\telif test $what = noop\n+\t\tthen\n+\t\t\ttest_i18ngrep 'is up to date' stdout\n+\t\tfi &&\n \t\tnewhead=\\$(git rev-parse HEAD) &&\n-\t\ttest_cmp_rev \\$oldhead \\$newhead\n+\t\tif test $cmp = same\n+\t\tthen\n+\t\t\ttest_cmp_rev \\$oldhead \\$newhead\n+\t\telif test $cmp = diff\n+\t\tthen\n+\t\t\t! test_cmp_rev \\$oldhead \\$newhead\n+\t\tfi\n \t\"\n }\n \n changes='no changes'\n-test_rebase_same_head success\n-test_rebase_same_head success master\n-test_rebase_same_head success --onto B B\n-test_rebase_same_head success --onto B... B\n-test_rebase_same_head success --onto master... master\n-test_rebase_same_head success --no-fork-point\n-test_rebase_same_head success --fork-point master\n-test_rebase_same_head failure --fork-point --onto B B\n-test_rebase_same_head failure --fork-point --onto B... B\n-test_rebase_same_head success --fork-point --onto master... master\n+test_rebase_same_head success work same\n+test_rebase_same_head success noop same master\n+test_rebase_same_head success noop same --onto B B\n+test_rebase_same_head success noop same --onto B... B\n+test_rebase_same_head success noop same --onto master... master\n+test_rebase_same_head success noop same --no-fork-point\n+test_rebase_same_head success work same --fork-point master\n+test_rebase_same_head failure noop same --fork-point --onto B B\n+test_rebase_same_head failure work same --fork-point --onto B... B\n+test_rebase_same_head success work same --fork-point --onto master... master\n \n-test_expect_success 'add work to side' '\n+test_expect_success 'add work same  to side' '\n \ttest_commit E\n '\n \n changes='our changes'\n-test_rebase_same_head success\n-test_rebase_same_head success master\n-test_rebase_same_head success --onto B B\n-test_rebase_same_head success --onto B... B\n-test_rebase_same_head success --onto master... master\n-test_rebase_same_head success --no-fork-point\n-test_rebase_same_head success --fork-point master\n-test_rebase_same_head failure --fork-point --onto B B\n-test_rebase_same_head failure --fork-point --onto B... B\n-test_rebase_same_head success --fork-point --onto master... master\n+test_rebase_same_head success work same\n+test_rebase_same_head success noop same master\n+test_rebase_same_head success noop same --onto B B\n+test_rebase_same_head success noop same --onto B... B\n+test_rebase_same_head success noop same --onto master... master\n+test_rebase_same_head success noop same --no-fork-point\n+test_rebase_same_head success work same --fork-point master\n+test_rebase_same_head failure work same --fork-point --onto B B\n+test_rebase_same_head failure work same --fork-point --onto B... B\n+test_rebase_same_head success work same --fork-point --onto master... master\n \n-test_expect_success 'add work to upstream' '\n+test_expect_success 'add work same  to upstream' '\n \tgit checkout master &&\n \ttest_commit F &&\n \tgit checkout side\n '\n \n changes='our and their changes'\n-test_rebase_same_head success --onto B B\n-test_rebase_same_head success --onto B... B\n-test_rebase_same_head failure --onto master... master\n-test_rebase_same_head failure --fork-point --onto B B\n-test_rebase_same_head failure --fork-point --onto B... B\n-test_rebase_same_head failure --fork-point --onto master... master\n+test_rebase_same_head success noop same --onto B B\n+test_rebase_same_head success noop same --onto B... B\n+test_rebase_same_head failure work same --onto master... master\n+test_rebase_same_head failure work same --fork-point --onto B B\n+test_rebase_same_head failure work same --fork-point --onto B... B\n+test_rebase_same_head failure work same --fork-point --onto master... master\n \n test_done\n-- \n2.21.0.1020.gf2820cf01a\n\n"},{"id":"375072","messageId":"20190508001252.15752-5-avarab@gmail.com","threadId":"50810","inReplyTo":"cover.1555832783.git.liu.denton@gmail.com","subject":"[RFC WIP PATCH v8 04/13] t3432: test for --no-ff's interaction with fast-forward","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-05-08T00:12:43Z","receivedAt":"2019-05-08T00:13:16Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Add more stress tests for the can_fast_forward() case in\nrebase.c. These tests are getting rather verbose, but now we can see\nunder --ff and --no-ff whether we skip work, or whether we're forced\nto run the rebase.\n\nThese tests aren't supposed to endorse the status quo, just test for\nwhat we're currently doing.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n t/t3432-rebase-fast-forward.sh | 83 ++++++++++++++++++++++------------\n 1 file changed, 54 insertions(+), 29 deletions(-)\n\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex dd51e28b56..e20a8ab9c4 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -16,22 +16,47 @@ test_expect_success setup '\n '\n \n test_rebase_same_head () {\n+\tstatus_n=\"$1\" &&\n+\tshift &&\n+\twhat_n=\"$1\" &&\n+\tshift &&\n+\tcmp_n=\"$1\" &&\n+\tshift &&\n+\tstatus_f=\"$1\" &&\n+\tshift &&\n+\twhat_f=\"$1\" &&\n+\tshift &&\n+\tcmp_f=\"$1\" &&\n+\tshift &&\n+\ttest_rebase_same_head_ $status_n $what_n $cmp_n  \"\" \"$*\" &&\n+\ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --no-ff\" \"$*\"\n+}\n+\n+test_rebase_same_head_ () {\n \tstatus=\"$1\" &&\n \tshift &&\n \twhat=\"$1\" &&\n \tshift &&\n \tcmp=\"$1\" &&\n \tshift &&\n-\ttest_expect_$status \"git rebase $* with $changes is $what\" \"\n+\tflag=\"$1\"\n+\tshift &&\n+\ttest_expect_$status \"git rebase$flag $* with $changes is $what with $cmp HEAD\" \"\n \t\toldhead=\\$(git rev-parse HEAD) &&\n \t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n-\t\tgit rebase $* >stdout &&\n+\t\tgit rebase$flag $* >stdout &&\n \t\tif test $what = work\n \t\tthen\n+\t\t\t# Must check this case first, for 'is up to\n+\t\t\t# date, rebase forced[...]rewinding head' cases\n \t\t\ttest_i18ngrep 'rewinding head' stdout\n \t\telif test $what = noop\n \t\tthen\n-\t\t\ttest_i18ngrep 'is up to date' stdout\n+\t\t\ttest_i18ngrep 'is up to date' stdout &&\n+\t\t\t! test_i18ngrep 'rebase forced' stdout\n+\t\telif test $what = noop-force\n+\t\tthen\n+\t\t\ttest_i18ngrep 'is up to date, rebase forced' stdout\n \t\tfi &&\n \t\tnewhead=\\$(git rev-parse HEAD) &&\n \t\tif test $cmp = same\n@@ -45,32 +70,32 @@ test_rebase_same_head () {\n }\n \n changes='no changes'\n-test_rebase_same_head success work same\n-test_rebase_same_head success noop same master\n-test_rebase_same_head success noop same --onto B B\n-test_rebase_same_head success noop same --onto B... B\n-test_rebase_same_head success noop same --onto master... master\n-test_rebase_same_head success noop same --no-fork-point\n-test_rebase_same_head success work same --fork-point master\n-test_rebase_same_head failure noop same --fork-point --onto B B\n-test_rebase_same_head failure work same --fork-point --onto B... B\n-test_rebase_same_head success work same --fork-point --onto master... master\n+test_rebase_same_head success work same success work same\n+test_rebase_same_head success noop same success noop-force same master\n+test_rebase_same_head success noop same success noop-force diff --onto B B\n+test_rebase_same_head success noop same success noop-force diff --onto B... B\n+test_rebase_same_head success noop same success noop-force same --onto master... master\n+test_rebase_same_head success noop same success noop-force same --no-fork-point\n+test_rebase_same_head success work same success work same --fork-point master\n+test_rebase_same_head failure noop same success work diff --fork-point --onto B B\n+test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n+test_rebase_same_head success work same success work same --fork-point --onto master... master\n \n test_expect_success 'add work same  to side' '\n \ttest_commit E\n '\n \n changes='our changes'\n-test_rebase_same_head success work same\n-test_rebase_same_head success noop same master\n-test_rebase_same_head success noop same --onto B B\n-test_rebase_same_head success noop same --onto B... B\n-test_rebase_same_head success noop same --onto master... master\n-test_rebase_same_head success noop same --no-fork-point\n-test_rebase_same_head success work same --fork-point master\n-test_rebase_same_head failure work same --fork-point --onto B B\n-test_rebase_same_head failure work same --fork-point --onto B... B\n-test_rebase_same_head success work same --fork-point --onto master... master\n+test_rebase_same_head success work same success work same\n+test_rebase_same_head success noop same success noop-force same master\n+test_rebase_same_head success noop same success noop-force diff --onto B B\n+test_rebase_same_head success noop same success noop-force diff --onto B... B\n+test_rebase_same_head success noop same success noop-force same --onto master... master\n+test_rebase_same_head success noop same success noop-force same --no-fork-point\n+test_rebase_same_head success work same success work same --fork-point master\n+test_rebase_same_head failure work same success work diff --fork-point --onto B B\n+test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n+test_rebase_same_head success work same success work same --fork-point --onto master... master\n \n test_expect_success 'add work same  to upstream' '\n \tgit checkout master &&\n@@ -79,11 +104,11 @@ test_expect_success 'add work same  to upstream' '\n '\n \n changes='our and their changes'\n-test_rebase_same_head success noop same --onto B B\n-test_rebase_same_head success noop same --onto B... B\n-test_rebase_same_head failure work same --onto master... master\n-test_rebase_same_head failure work same --fork-point --onto B B\n-test_rebase_same_head failure work same --fork-point --onto B... B\n-test_rebase_same_head failure work same --fork-point --onto master... master\n+test_rebase_same_head success noop same success noop-force diff --onto B B\n+test_rebase_same_head success noop same success noop-force diff --onto B... B\n+test_rebase_same_head failure work same success work diff --onto master... master\n+test_rebase_same_head failure work same success work diff --fork-point --onto B B\n+test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n+test_rebase_same_head failure work same success work diff --fork-point --onto master... master\n \n test_done\n-- \n2.21.0.1020.gf2820cf01a\n\n"},{"id":"375073","messageId":"20190508001252.15752-6-avarab@gmail.com","threadId":"50810","inReplyTo":"cover.1555832783.git.liu.denton@gmail.com","subject":"[RFC WIP PATCH v8 05/13] rebase: refactor can_fast_forward into goto tower","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-05-08T00:12:44Z","receivedAt":"2019-05-08T00:13:17Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Denton Liu <liu.denton@gmail.com>\n\nBefore, can_fast_forward was written with an if-else statement. However,\nin the future, we may be adding more termination cases which would lead\nto deeply nested if statements.\n\nRefactor to use a goto tower so that future cases can be easily\ninserted.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/rebase.c | 24 +++++++++++++++---------\n 1 file changed, 15 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 2e41ad5644..f7008b10af 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -882,21 +882,27 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n \t\t\t    struct object_id *merge_base)\n {\n \tstruct commit *head = lookup_commit(the_repository, head_oid);\n-\tstruct commit_list *merge_bases;\n-\tint res;\n+\tstruct commit_list *merge_bases = NULL;\n+\tint res = 0;\n \n \tif (!head)\n-\t\treturn 0;\n+\t\tgoto done;\n \n \tmerge_bases = get_merge_bases(onto, head);\n-\tif (merge_bases && !merge_bases->next) {\n-\t\toidcpy(merge_base, &merge_bases->item->object.oid);\n-\t\tres = oideq(merge_base, &onto->object.oid);\n-\t} else {\n+\tif (!merge_bases || merge_bases->next) {\n \t\toidcpy(merge_base, &null_oid);\n-\t\tres = 0;\n+\t\tgoto done;\n \t}\n-\tfree_commit_list(merge_bases);\n+\n+\toidcpy(merge_base, &merge_bases->item->object.oid);\n+\tif (!oideq(merge_base, &onto->object.oid))\n+\t\tgoto done;\n+\n+\tres = 1;\n+\n+done:\n+\tif (merge_bases)\n+\t\tfree_commit_list(merge_bases);\n \treturn res && is_linear_history(onto, head);\n }\n \n-- \n2.21.0.1020.gf2820cf01a\n\n"},{"id":"375074","messageId":"20190508001252.15752-7-avarab@gmail.com","threadId":"50810","inReplyTo":"cover.1555832783.git.liu.denton@gmail.com","subject":"[RFC WIP PATCH v8 06/13] rebase: fast-forward --onto in more cases","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-05-08T00:12:45Z","receivedAt":"2019-05-08T00:13:20Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Denton Liu <liu.denton@gmail.com>\n\nBefore, when we had the following graph,\n\n\tA---B---C (master)\n\t     \\\n\t      D (side)\n\nrunning 'git rebase --onto master... master side' would result in D\nbeing always rebased, no matter what. However, the desired behavior is\nthat rebase should notice that this is fast-forwardable and do that\ninstead.\n\nAdd detection to `can_fast_forward` so that this case can be detected\nand a fast-forward will be performed. First of all, rewrite the function\nto use gotos which simplifies the logic. Next, since the\n\n\toptions.upstream &&\n\t!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)\n\nconditions were removed in `cmd_rebase`, we reintroduce a substitute in\n`can_fast_forward`. In particular, checking the merge bases of\n`upstream` and `head` fixes a failing case in t3416.\n\nThe abbreviated graph for t3416 is as follows:\n\n\t\t    F---G topic\n\t\t   /\n\t  A---B---C---D---E master\n\nand the failing command was\n\n\tgit rebase --onto master...topic F topic\n\nBefore, Git would see that there was one merge base (C), and the merge\nand onto were the same so it would incorrectly return 1, indicating that\nwe could fast-forward. This would cause the rebased graph to be 'ABCFG'\nwhen we were expecting 'ABCG'.\n\nWith the additional logic, we detect that upstream and head's merge base\nis F. Since onto isn't F, it means we're not rebasing the full set of\ncommits from master..topic. Since we're excluding some commits, a\nfast-forward cannot be performed and so we correctly return 0.\n\nAdd '-f' to test cases that failed as a result of this change because\nthey were not expecting a fast-forward so that a rebase is forced.\n\nWhile we're at it, remove a trailing whitespace from rebase.c.\n\nHelped-by: Phillip Wood <phillip.wood@dunelm.org.uk>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/rebase.c               | 29 ++++++++++++++++++++---------\n t/t3400-rebase.sh              |  2 +-\n t/t3404-rebase-interactive.sh  |  2 +-\n t/t3432-rebase-fast-forward.sh |  4 ++--\n 4 files changed, 24 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex f7008b10af..c1000c159d 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -878,8 +878,8 @@ static int is_linear_history(struct commit *from, struct commit *to)\n \treturn 1;\n }\n \n-static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n-\t\t\t    struct object_id *merge_base)\n+static int can_fast_forward(struct commit *onto, struct commit *upstream,\n+\t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n {\n \tstruct commit *head = lookup_commit(the_repository, head_oid);\n \tstruct commit_list *merge_bases = NULL;\n@@ -898,6 +898,17 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n \tif (!oideq(merge_base, &onto->object.oid))\n \t\tgoto done;\n \n+\tif (!upstream)\n+\t\tgoto done;\n+\n+\tfree_commit_list(merge_bases);\n+\tmerge_bases = get_merge_bases(upstream, head);\n+\tif (!merge_bases || merge_bases->next)\n+\t\tgoto done;\n+\n+\tif (!oideq(&onto->object.oid, &merge_bases->item->object.oid))\n+\t\tgoto done;\n+\n \tres = 1;\n \n done:\n@@ -1664,13 +1675,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \t/*\n \t * Check if we are already based on onto with linear history,\n-\t * but this should be done only when upstream and onto are the same\n-\t * and if this is not an interactive rebase.\n+\t * in which case we could fast-forward without replacing the commits\n+\t * with new commits recreated by replaying their changes. This\n+\t * optimization must not be done if this is an interactive rebase.\n \t */\n-\tif (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&\n-\t    !is_interactive(&options) && !options.restrict_revision &&\n-\t    options.upstream &&\n-\t    !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {\n+\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head,\n+\t\t    &merge_base) &&\n+\t    !is_interactive(&options) && !options.restrict_revision) {\n \t\tint flag;\n \n \t\tif (!(options.flags & REBASE_FORCE)) {\n@@ -1764,7 +1775,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tstrbuf_addf(&msg, \"%s: checkout %s\",\n \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);\n \tif (reset_head(&options.onto->object.oid, \"checkout\", NULL,\n-\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD | \n+\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n \t\t       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n \t\t       NULL, msg.buf))\n \t\tdie(_(\"Could not detach HEAD\"));\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 42f147858d..45f1f65c7b 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -295,7 +295,7 @@ test_expect_success 'rebase--am.sh and --show-current-patch' '\n \t\techo two >>init.t &&\n \t\tgit commit -a -m two &&\n \t\tgit tag two &&\n-\t\ttest_must_fail git rebase --onto init HEAD^ &&\n+\t\ttest_must_fail git rebase -f --onto init HEAD^ &&\n \t\tGIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&\n \t\tgrep \"show.*$(git rev-parse two)\" stderr\n \t)\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 1723e1a858..c7dbbd16db 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1063,7 +1063,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int\n \tgit reset --hard &&\n \tgit checkout conflict-branch &&\n \tset_fake_editor &&\n-\ttest_must_fail git rebase --onto HEAD~2 HEAD~ &&\n+\ttest_must_fail git rebase -f --onto HEAD~2 HEAD~ &&\n \ttest_must_fail git rebase --edit-todo &&\n \tgit rebase --abort\n '\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex e20a8ab9c4..59cd437301 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -106,9 +106,9 @@ test_expect_success 'add work same  to upstream' '\n changes='our and their changes'\n test_rebase_same_head success noop same success noop-force diff --onto B B\n test_rebase_same_head success noop same success noop-force diff --onto B... B\n-test_rebase_same_head failure work same success work diff --onto master... master\n+test_rebase_same_head success noop same success work diff --onto master... master\n test_rebase_same_head failure work same success work diff --fork-point --onto B B\n test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n-test_rebase_same_head failure work same success work diff --fork-point --onto master... master\n+test_rebase_same_head success noop same success work diff --fork-point --onto master... master\n \n test_done\n-- \n2.21.0.1020.gf2820cf01a\n\n"},{"id":"375075","messageId":"20190508001252.15752-8-avarab@gmail.com","threadId":"50810","inReplyTo":"cover.1555832783.git.liu.denton@gmail.com","subject":"[RFC WIP PATCH v8 07/13] rebase: fast-forward --fork-point in more cases","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-05-08T00:12:46Z","receivedAt":"2019-05-08T00:13:20Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Denton Liu <liu.denton@gmail.com>\n\nBefore, when we rebased with a --fork-point invocation where the\nfork-point wasn't empty, we would be setting options.restrict_revision.\nThe fast-forward logic would automatically declare that the rebase was\nnot fast-forwardable if it was set. However, this was painting with a\nvery broad brush.\n\nRefine the logic so that we can fast-forward in the case where the\nrestricted revision is equal to the merge base, since we stop rebasing\nat the merge base anyway.\n\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n builtin/rebase.c               | 10 +++++++---\n t/t3432-rebase-fast-forward.sh | 20 ++++++++++----------\n 2 files changed, 17 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex c1000c159d..5f9beda46b 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -879,6 +879,7 @@ static int is_linear_history(struct commit *from, struct commit *to)\n }\n \n static int can_fast_forward(struct commit *onto, struct commit *upstream,\n+\t\t\t    struct commit *restrict_revision,\n \t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n {\n \tstruct commit *head = lookup_commit(the_repository, head_oid);\n@@ -898,6 +899,9 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,\n \tif (!oideq(merge_base, &onto->object.oid))\n \t\tgoto done;\n \n+\tif (restrict_revision && !oideq(&restrict_revision->object.oid, merge_base))\n+\t\tgoto done;\n+\n \tif (!upstream)\n \t\tgoto done;\n \n@@ -1679,9 +1683,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t * with new commits recreated by replaying their changes. This\n \t * optimization must not be done if this is an interactive rebase.\n \t */\n-\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head,\n-\t\t    &merge_base) &&\n-\t    !is_interactive(&options) && !options.restrict_revision) {\n+\tif (can_fast_forward(options.onto, options.upstream, options.restrict_revision,\n+\t\t    &options.orig_head, &merge_base) &&\n+\t    !is_interactive(&options)) {\n \t\tint flag;\n \n \t\tif (!(options.flags & REBASE_FORCE)) {\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 59cd437301..8f4996e476 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -70,32 +70,32 @@ test_rebase_same_head_ () {\n }\n \n changes='no changes'\n-test_rebase_same_head success work same success work same\n+test_rebase_same_head success noop same success work same\n test_rebase_same_head success noop same success noop-force same master\n test_rebase_same_head success noop same success noop-force diff --onto B B\n test_rebase_same_head success noop same success noop-force diff --onto B... B\n test_rebase_same_head success noop same success noop-force same --onto master... master\n test_rebase_same_head success noop same success noop-force same --no-fork-point\n-test_rebase_same_head success work same success work same --fork-point master\n-test_rebase_same_head failure noop same success work diff --fork-point --onto B B\n-test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n-test_rebase_same_head success work same success work same --fork-point --onto master... master\n+test_rebase_same_head success noop same success work same --fork-point master\n+test_rebase_same_head success noop same success work diff --fork-point --onto B B\n+test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n+test_rebase_same_head success noop same success work same --fork-point --onto master... master\n \n test_expect_success 'add work same  to side' '\n \ttest_commit E\n '\n \n changes='our changes'\n-test_rebase_same_head success work same success work same\n+test_rebase_same_head success noop same success work same\n test_rebase_same_head success noop same success noop-force same master\n test_rebase_same_head success noop same success noop-force diff --onto B B\n test_rebase_same_head success noop same success noop-force diff --onto B... B\n test_rebase_same_head success noop same success noop-force same --onto master... master\n test_rebase_same_head success noop same success noop-force same --no-fork-point\n-test_rebase_same_head success work same success work same --fork-point master\n-test_rebase_same_head failure work same success work diff --fork-point --onto B B\n-test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n-test_rebase_same_head success work same success work same --fork-point --onto master... master\n+test_rebase_same_head success noop same success work same --fork-point master\n+test_rebase_same_head success noop same success work diff --fork-point --onto B B\n+test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n+test_rebase_same_head success noop same success work same --fork-point --onto master... master\n \n test_expect_success 'add work same  to upstream' '\n \tgit checkout master &&\n-- \n2.21.0.1020.gf2820cf01a\n\n"},{"id":"375076","messageId":"20190508001252.15752-10-avarab@gmail.com","threadId":"50810","inReplyTo":"cover.1555832783.git.liu.denton@gmail.com","subject":"[RFC WIP PATCH v8 09/13] rebase tests: test linear branch topology","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-05-08T00:12:48Z","receivedAt":"2019-05-08T00:13:23Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Add tests rebasing a linear branch topology to linear rebase tests\nadded in 2aad7cace2 (\"add simple tests of consistency across rebase\ntypes\", 2013-06-06).\n\nThese tests are duplicates of two surrounding tests that do the same\nwith tags pointing to the same objects. Right now there's no change in\nbehavior being introduced, but as we'll see in a subsequent change\nrebase can have different behaviors when working implicitly with\nremote tracking branches.\n\nWhile I'm at it add a --fork-point test, strictly speaking this is\nredundant to the existing '' test, as no argument to rebase implies\n--fork-point. But now it's easier to grep for tests that explicitly\nstress --fork-point.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n t/t3421-rebase-topology-linear.sh | 29 +++++++++++++++++++++++++++++\n 1 file changed, 29 insertions(+)\n\ndiff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\nindex 7274dca40b..b847064f91 100755\n--- a/t/t3421-rebase-topology-linear.sh\n+++ b/t/t3421-rebase-topology-linear.sh\n@@ -31,6 +31,16 @@ test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n \n+test_expect_success 'setup branches and remote tracking' '\n+\tgit tag -l >tags &&\n+\tfor tag in $(cat tags)\n+\tdo\n+\t\tgit branch branch-$tag $tag || return 1\n+\tdone &&\n+\tgit remote add origin \"file://$PWD\" &&\n+\tgit fetch origin\n+'\n+\n test_run_rebase () {\n \tresult=$1\n \tshift\n@@ -57,10 +67,28 @@ test_run_rebase () {\n \t\"\n }\n test_run_rebase success ''\n+test_run_rebase success --fork-point\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase failure -p\n \n+test_run_rebase () {\n+\tresult=$1\n+\tshift\n+\ttest_expect_$result \"rebase $* -f rewrites even if remote upstream is an ancestor\" \"\n+\t\treset_rebase &&\n+\t\tgit rebase $* -f branch-b branch-e &&\n+\t\t! test_cmp_rev branch-e origin/branch-e &&\n+\t\ttest_cmp_rev branch-b HEAD~2 &&\n+\t\ttest_linear_range 'd e' branch-b..\n+\t\"\n+}\n+test_run_rebase success ''\n+test_run_rebase success --fork-point\n+test_run_rebase success -m\n+test_run_rebase success -i\n+test_have_prereq !REBASE_P || test_run_rebase success -p\n+\n test_run_rebase () {\n \tresult=$1\n \tshift\n@@ -71,6 +99,7 @@ test_run_rebase () {\n \t\"\n }\n test_run_rebase success ''\n+test_run_rebase success --fork-point\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n-- \n2.21.0.1020.gf2820cf01a\n\n"},{"id":"375077","messageId":"20190508001252.15752-9-avarab@gmail.com","threadId":"50810","inReplyTo":"cover.1555832783.git.liu.denton@gmail.com","subject":"[RFC WIP PATCH v8 08/13] rebase: teach rebase --keep-base","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-05-08T00:12:47Z","receivedAt":"2019-05-08T00:13:23Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"From: Denton Liu <liu.denton@gmail.com>\n\nA common scenario is if a user is working on a topic branch and they\nwish to make some changes to intermediate commits or autosquash, they\nwould run something such as\n\n\tgit rebase -i --onto master... master\n\nin order to preserve the merge base. This is useful when contributing a\npatch series to the Git mailing list, one often starts on top of the\ncurrent 'master'. However, while developing the patches, 'master' is\nalso developed further and it is sometimes not the bst idea to keep\nrebasing on top of 'master', but to keep the base commit as-is.\n\nAlternatively, a user wishing to test individual commits in a topic\nbranch without changing anything may run\n\n\tgit rebase -x ./test.sh master... master\n\nSince rebasing onto the merge base of the branch and the upstream is\nsuch a common case, introduce the --keep-base option as a shortcut.\n\nThis allows us to rewrite the above as\n\n\tgit rebase -i --keep-base master\n\nand\n\n\tgit rebase -x ./test.sh --keep-base master\n\nrespectively.\n\nAdd tests to ensure --keep-base works correctly in the normal case and\nfails when there are multiple merge bases, both in regular and\ninteractive mode. Also, test to make sure conflicting options cause\nrebase to fail. While we're adding test cases, add a missing\nset_fake_editor call to 'rebase -i --onto master...side'.\n\nWhile we're documenting the --keep-base option, change an instance of\n\"merge-base\" to \"merge base\", which is the consistent spelling.\n\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nHelped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/git-rebase.txt           | 30 ++++++++++++--\n builtin/rebase.c                       | 32 ++++++++++++---\n contrib/completion/git-completion.bash |  2 +-\n t/t3416-rebase-onto-threedots.sh       | 57 ++++++++++++++++++++++++++\n t/t3431-rebase-fork-point.sh           |  4 ++\n t/t3432-rebase-fast-forward.sh         | 11 +++++\n 6 files changed, 126 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex f5e6ae3907..fafb7b4eb3 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip\n SYNOPSIS\n --------\n [verse]\n-'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n-\t[<upstream> [<branch>]]\n+'git rebase' [-i | --interactive] [<options>] [--exec <cmd>]\n+\t[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\n 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n \t--root [<branch>]\n 'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch\n@@ -217,6 +217,24 @@ As a special case, you may use \"A\\...B\" as a shortcut for the\n merge base of A and B if there is exactly one merge base. You can\n leave out at most one of A and B, in which case it defaults to HEAD.\n \n+--keep-base::\n+\tSet the starting point at which to create the new commits to the\n+\tmerge base of <upstream> <branch>. Running\n+\t'git rebase --keep-base <upstream> <branch>' is equivalent to\n+\trunning 'git rebase --onto <upstream>... <upstream>'.\n++\n+This option is useful in the case where one is developing a feature on\n+top of an upstream branch. While the feature is being worked on, the\n+upstream branch may advance and it may not be the best idea to keep\n+rebasing on top of the upstream but to keep the base commit as-is.\n++\n+Although both this option and --fork-point find the merge base between\n+<upstream> and <branch>, this option uses the merge base as the _starting\n+point_ on which new commits will be created, whereas --fork-point uses\n+the merge base to determine the _set of commits_ which will be rebased.\n++\n+See also INCOMPATIBLE OPTIONS below.\n+\n <upstream>::\n \tUpstream branch to compare against.  May be any valid commit,\n \tnot just an existing branch name. Defaults to the configured\n@@ -369,6 +387,10 @@ ends up being empty, the <upstream> will be used as a fallback.\n +\n If either <upstream> or --root is given on the command line, then the\n default is `--no-fork-point`, otherwise the default is `--fork-point`.\n++\n+If your branch was based on <upstream> but <upstream> was rewound and\n+your branch contains commits which were dropped, this option can be used\n+with `--keep-base` in order to drop those commits from your branch.\n \n --ignore-whitespace::\n --whitespace=<option>::\n@@ -545,6 +567,8 @@ In addition, the following pairs of options are incompatible:\n  * --preserve-merges and --rebase-merges\n  * --rebase-merges and --strategy\n  * --rebase-merges and --strategy-option\n+ * --keep-base and --onto\n+ * --keep-base and --root\n \n BEHAVIORAL DIFFERENCES\n -----------------------\n@@ -869,7 +893,7 @@ NOTE: While an \"easy case recovery\" sometimes appears to be successful\n       --interactive` will be **resurrected**!\n \n The idea is to manually tell 'git rebase' \"where the old 'subsystem'\n-ended and your 'topic' began\", that is, what the old merge-base\n+ended and your 'topic' began\", that is, what the old merge base\n between them was.  You will have to find a way to name the last commit\n of the old 'subsystem', for example:\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 5f9beda46b..ae6b9b42b8 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -27,8 +27,8 @@\n #include \"branch.h\"\n \n static char const * const builtin_rebase_usage[] = {\n-\tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n-\t\t\"[<upstream>] [<branch>]\"),\n+\tN_(\"git rebase [-i] [options] [--exec <cmd>] \"\n+\t\t\"[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\"),\n \tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n \t\t\"--root [<branch>]\"),\n \tN_(\"git rebase --continue | --abort | --skip | --edit-todo\"),\n@@ -1022,6 +1022,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t};\n \tconst char *branch_name;\n \tint ret, flags, total_argc, in_progress = 0;\n+\tint keep_base = 0;\n \tint ok_to_skip_pre_rebase = 0;\n \tstruct strbuf msg = STRBUF_INIT;\n \tstruct strbuf revisions = STRBUF_INIT;\n@@ -1055,6 +1056,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"rebase onto given branch instead of upstream\")),\n+\t\tOPT_BOOL(0, \"keep-base\", &keep_base,\n+\t\t\t N_(\"use the merge-base of upstream and branch as the current base\")),\n \t\tOPT_BOOL(0, \"no-verify\", &ok_to_skip_pre_rebase,\n \t\t\t N_(\"allow pre-rebase hook to run\")),\n \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n@@ -1214,6 +1217,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\twarning(_(\"git rebase --preserve-merges is deprecated. \"\n \t\t\t  \"Use --rebase-merges instead.\"));\n \n+\tif (keep_base) {\n+\t\tif (options.onto_name)\n+\t\t\tdie(_(\"cannot combine '--keep-base' with '--onto'\"));\n+\t\tif (options.root)\n+\t\t\tdie(_(\"cannot combine '--keep-base' with '--root'\"));\n+\t}\n+\n \tif (action != NO_ACTION && !in_progress)\n \t\tdie(_(\"No rebase in progress?\"));\n \tsetenv(GIT_REFLOG_ACTION_ENVIRONMENT, \"rebase\", 0);\n@@ -1538,12 +1548,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t}\n \n \t/* Make sure the branch to rebase onto is valid. */\n-\tif (!options.onto_name)\n+\tif (keep_base) {\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addstr(&buf, options.upstream_name);\n+\t\tstrbuf_addstr(&buf, \"...\");\n+\t\toptions.onto_name = xstrdup(buf.buf);\n+\t} else if (!options.onto_name)\n \t\toptions.onto_name = options.upstream_name;\n \tif (strstr(options.onto_name, \"...\")) {\n-\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0)\n-\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n-\t\t\t    options.onto_name);\n+\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0) {\n+\t\t\tif (keep_base)\n+\t\t\t\tdie(_(\"'%s': need exactly one merge base with branch\"),\n+\t\t\t\t    options.upstream_name);\n+\t\t\telse\n+\t\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n+\t\t\t\t    options.onto_name);\n+\t\t}\n \t\toptions.onto = lookup_commit_or_die(&merge_base,\n \t\t\t\t\t\t    options.onto_name);\n \t} else {\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex 3eefbabdb1..fe3768c8c6 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2028,7 +2028,7 @@ _git_rebase ()\n \t\t\t--autosquash --no-autosquash\n \t\t\t--fork-point --no-fork-point\n \t\t\t--autostash --no-autostash\n-\t\t\t--verify --no-verify\n+\t\t\t--verify --no-verify --keep-base\n \t\t\t--keep-empty --root --force-rebase --no-ff\n \t\t\t--rerere-autoupdate\n \t\t\t--exec\ndiff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh\nindex ddf2f64853..9c2548423b 100755\n--- a/t/t3416-rebase-onto-threedots.sh\n+++ b/t/t3416-rebase-onto-threedots.sh\n@@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' '\n \tgit checkout side &&\n \tgit reset --hard K &&\n \n+\tset_fake_editor &&\n \ttest_must_fail git rebase -i --onto master...side J\n '\n \n+test_expect_success 'rebase --keep-base --onto incompatible' '\n+\ttest_must_fail git rebase --keep-base --onto master...\n+'\n+\n+test_expect_success 'rebase --keep-base --root incompatible' '\n+\ttest_must_fail git rebase --keep-base --root\n+'\n+\n+test_expect_success 'rebase --keep-base master from topic' '\n+\tgit reset --hard &&\n+\tgit checkout topic &&\n+\tgit reset --hard G &&\n+\n+\tgit rebase --keep-base master &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base master HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --keep-base master from side' '\n+\tgit reset --hard &&\n+\tgit checkout side &&\n+\tgit reset --hard K &&\n+\n+\ttest_must_fail git rebase --keep-base master\n+'\n+\n+test_expect_success 'rebase -i --keep-base master from topic' '\n+\tgit reset --hard &&\n+\tgit checkout topic &&\n+\tgit reset --hard G &&\n+\n+\tset_fake_editor &&\n+\tEXPECT_COUNT=2 git rebase -i --keep-base master &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base master HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase -i --keep-base master from side' '\n+\tgit reset --hard &&\n+\tgit checkout side &&\n+\tgit reset --hard K &&\n+\n+\tset_fake_editor &&\n+\ttest_must_fail git rebase -i --keep-base master\n+'\n+\n test_done\ndiff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\nindex 2d5c6e641e..78851b9a2a 100755\n--- a/t/t3431-rebase-fork-point.sh\n+++ b/t/t3431-rebase-fork-point.sh\n@@ -43,11 +43,15 @@ test_rebase () {\n \n test_rebase 'G F E D B A'\n test_rebase 'G F D B A' --onto D\n+test_rebase 'G F B A' --keep-base\n test_rebase 'G F C E D B A' --no-fork-point\n test_rebase 'G F C D B A' --no-fork-point --onto D\n+test_rebase 'G F C B A' --no-fork-point --keep-base\n test_rebase 'G F E D B A' --fork-point refs/heads/master\n test_rebase 'G F D B A' --fork-point --onto D refs/heads/master\n+test_rebase 'G F B A' --fork-point --keep-base refs/heads/master\n test_rebase 'G F C E D B A' refs/heads/master\n test_rebase 'G F C D B A' --onto D refs/heads/master\n+test_rebase 'G F C B A' --keep-base refs/heads/master\n \n test_done\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 8f4996e476..e8a9bf42b6 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -75,11 +75,15 @@ test_rebase_same_head success noop same success noop-force same master\n test_rebase_same_head success noop same success noop-force diff --onto B B\n test_rebase_same_head success noop same success noop-force diff --onto B... B\n test_rebase_same_head success noop same success noop-force same --onto master... master\n+test_rebase_same_head success noop same success noop-force same --keep-base master\n+test_rebase_same_head success noop same success noop-force same --keep-base\n test_rebase_same_head success noop same success noop-force same --no-fork-point\n+test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point\n test_rebase_same_head success noop same success work same --fork-point master\n test_rebase_same_head success noop same success work diff --fork-point --onto B B\n test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n test_rebase_same_head success noop same success work same --fork-point --onto master... master\n+test_rebase_same_head success noop same success work same --keep-base --keep-base master\n \n test_expect_success 'add work same  to side' '\n \ttest_commit E\n@@ -91,11 +95,15 @@ test_rebase_same_head success noop same success noop-force same master\n test_rebase_same_head success noop same success noop-force diff --onto B B\n test_rebase_same_head success noop same success noop-force diff --onto B... B\n test_rebase_same_head success noop same success noop-force same --onto master... master\n+test_rebase_same_head success noop same success noop-force same --keep-base master\n+test_rebase_same_head success noop same success noop-force same --keep-base\n test_rebase_same_head success noop same success noop-force same --no-fork-point\n+test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point\n test_rebase_same_head success noop same success work same --fork-point master\n test_rebase_same_head success noop same success work diff --fork-point --onto B B\n test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n test_rebase_same_head success noop same success work same --fork-point --onto master... master\n+test_rebase_same_head success noop same success work same --fork-point --keep-base master\n \n test_expect_success 'add work same  to upstream' '\n \tgit checkout master &&\n@@ -107,8 +115,11 @@ changes='our and their changes'\n test_rebase_same_head success noop same success noop-force diff --onto B B\n test_rebase_same_head success noop same success noop-force diff --onto B... B\n test_rebase_same_head success noop same success work diff --onto master... master\n+test_rebase_same_head success noop same success work diff --keep-base master\n+test_rebase_same_head success noop same success work diff --keep-base\n test_rebase_same_head failure work same success work diff --fork-point --onto B B\n test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n test_rebase_same_head success noop same success work diff --fork-point --onto master... master\n+test_rebase_same_head success noop same success work diff --fork-point --keep-base master\n \n test_done\n-- \n2.21.0.1020.gf2820cf01a\n\n"},{"id":"375078","messageId":"20190508001252.15752-11-avarab@gmail.com","threadId":"50810","inReplyTo":"cover.1555832783.git.liu.denton@gmail.com","subject":"[RFC WIP PATCH v8 10/13] rebase: don't rebase linear topology with --fork-point","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-05-08T00:12:49Z","receivedAt":"2019-05-08T00:13:24Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"Fix a regression introduced in 4f21454b55 (\"merge-base: handle\n--fork-point without reflog\", 2016-10-12).\n\nBefore that change having a linear history on top of an upstream\nmaster would with --fork-point (aka argument-less rebase) tell us\nthere was nothing to be done:\n\n    $ git rebase\n    Current branch master is up to date.\n\nAfter that change \"rebase\" will always redundantly find that it has\nwork to do (it doesn't):\n\n    $ git rebase\n    First, rewinding head to replay your work on top of it...\n    Applying: [...]\n\nWhereas equivalently running:\n\n    $ git rebase @{upstream}\n    $ git rebase $(git merge-base --fork-point @{u})\n\nGives us the old behavior of doing nothing.\n\nNow, why did we have this regression? Fully digging into it yields an\ninteresting combination of causes:\n\nWay back in 1308c17b3e (\"Allow rebase to run if upstream is completely\nmerged\", 2007-07-04) \"rebase\" learned to not do this redundant work\nwhen asked to rebase on a commit that was already an ancestor of the\ncurrent commit.\n\nThen in 1e0dacdbdb (\"rebase: omit patch-identical commits with\n--fork-point\", 2014-07-16) a rebase bug was fixed for a case where the\nhistory to be rebased was divergent by entirely skipping the 2007-era\nlogic if --fork-point was provided.\n\nBut here's the critical thing, *only* if the --fork-point was\ndivergent. At that time \"git merge-base --fork-point A B\" would return\nnothing if the two commits weren't divergent.\n\nThen in 4f21454b55 (\"merge-base: handle --fork-point without reflog\",\n2016-10-12) which introduced the regression being fixed here, a bug\nfix for \"git merge-base --fork-point\" being run stand-alone by proxy\nbroke this use-case git-rebase.sh was relying on, since it was still\nassuming that if we didn't have divergent history we'd have no output.\n\nFinally, when \"rebase\" was rewritten in C a combination of\n9a48a615b4 (\"builtin rebase: try to fast forward when possible\",\n2018-09-04), 103148aad8 (\"merge-base --fork-point: extract libified\nfunction\", 2018-09-04) and 92d0d74e8d (\"builtin rebase: support\n`fork-point` option\", 2018-09-04) faithfully re-implemented the\nthen-buggy behavior.\n\nSo let's fix this. It's easy enough, we just stop explicitly excluding\n--fork-point from the can_fast_forward(...) test we're doing, which as\ndiscussed above is faithfully ported over from buggy shellscript-era\nlogic.\n\nI'm not bothering to fix this in the legacy rebase mode. As discussed\nin 9aea5e9286 (\"rebase: fix regression in rebase.useBuiltin=false test\nmode\", 2019-02-13) it'll be going away shortly after 2.21.0 lands.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n t/t3421-rebase-topology-linear.sh | 15 +++++++++++++++\n 1 file changed, 15 insertions(+)\n\ndiff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\nindex b847064f91..1754537789 100755\n--- a/t/t3421-rebase-topology-linear.sh\n+++ b/t/t3421-rebase-topology-linear.sh\n@@ -55,6 +55,21 @@ test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n \n+test_run_rebase () {\n+\tresult=$1\n+\tshift\n+\ttest_expect_$result \"rebase $* is no-op if remote upstream is an ancestor\" \"\n+\t\treset_rebase &&\n+\t\tGIT_TEST_REBASE_USE_BUILTIN=true git rebase $* branch-b branch-e &&\n+\t\ttest_cmp_rev e HEAD\n+\t\"\n+}\n+test_run_rebase success ''\n+test_run_rebase success --fork-point\n+test_run_rebase success -m\n+test_run_rebase success -i\n+test_have_prereq !REBASE_P || test_run_rebase success -p\n+\n test_run_rebase () {\n \tresult=$1\n \tshift\n-- \n2.21.0.1020.gf2820cf01a\n\n"},{"id":"375079","messageId":"20190508001252.15752-12-avarab@gmail.com","threadId":"50810","inReplyTo":"cover.1555832783.git.liu.denton@gmail.com","subject":"[RFC WIP PATCH v8 11/13] rebase: eliminate side-effects from can_fast_forward()","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-05-08T00:12:50Z","receivedAt":"2019-05-08T00:13:26Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"The can_fast_forward() function is potentially much more expensive\nthan is_interactive() since it e.g. might need to call\nis_linear_history().\n\nSo reversing the two looks like an obvious improvement, but doing so\nreveals a previously hidden caveat: We need the can_fast_forward()\nfunction to populate data used later, namely the \"merge_bases\"\nvariable. This has been the case since it was added in\n9a48a615b4 (\"builtin rebase: try to fast forward when possible\",\n2018-09-04).\n\nSo let's refactor it into two functions. Now we'll always call\npopulate_merge_bases(), and then only call can_fast_forward() if\nis_interactive() is false, making this both faster in pathological\ncases, and more importantly easier to follow.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/rebase.c | 41 +++++++++++++++++++++++++++--------------\n 1 file changed, 27 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex ae6b9b42b8..cb5d7fcb53 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -878,24 +878,30 @@ static int is_linear_history(struct commit *from, struct commit *to)\n \treturn 1;\n }\n \n-static int can_fast_forward(struct commit *onto, struct commit *upstream,\n+static void populate_merge_bases(struct commit *head, struct commit *onto,\n+\t\t\t\t struct commit_list *merge_bases,\n+\t\t\t\t struct object_id *merge_base)\n+{\n+\tmerge_bases = get_merge_bases(onto, head);\n+\tif (!merge_bases || merge_bases->next) {\n+\t\toidcpy(merge_base, &null_oid);\n+\t\treturn;\n+\t}\n+\toidcpy(merge_base, &merge_bases->item->object.oid);\n+}\n+\n+static int can_fast_forward(struct commit *head,\n+\t\t\t    struct commit *onto, struct commit *upstream,\n \t\t\t    struct commit *restrict_revision,\n-\t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n+\t\t\t    struct object_id *head_oid,\n+\t\t\t    struct commit_list *merge_bases,\n+\t\t\t    struct object_id *merge_base)\n {\n-\tstruct commit *head = lookup_commit(the_repository, head_oid);\n-\tstruct commit_list *merge_bases = NULL;\n \tint res = 0;\n \n \tif (!head)\n \t\tgoto done;\n \n-\tmerge_bases = get_merge_bases(onto, head);\n-\tif (!merge_bases || merge_bases->next) {\n-\t\toidcpy(merge_base, &null_oid);\n-\t\tgoto done;\n-\t}\n-\n-\toidcpy(merge_base, &merge_bases->item->object.oid);\n \tif (!oideq(merge_base, &onto->object.oid))\n \t\tgoto done;\n \n@@ -1154,6 +1160,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_END(),\n \t};\n \tint i;\n+\tstruct commit *head_commit;\n+\tstruct commit_list *merge_bases = NULL;\n \n \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n \t\tusage_with_options(builtin_rebase_usage,\n@@ -1703,9 +1711,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t * with new commits recreated by replaying their changes. This\n \t * optimization must not be done if this is an interactive rebase.\n \t */\n-\tif (can_fast_forward(options.onto, options.upstream, options.restrict_revision,\n-\t\t    &options.orig_head, &merge_base) &&\n-\t    !is_interactive(&options)) {\n+\thead_commit = lookup_commit(the_repository, &options.orig_head);\n+\tif (head_commit)\n+\t\tpopulate_merge_bases(head_commit, options.onto, merge_bases,\n+\t\t\t\t     &merge_base);\n+\tif (!is_interactive(&options) &&\n+\t    can_fast_forward(head_commit, options.onto, options.upstream,\n+\t\t\t     options.restrict_revision, &options.orig_head,\n+\t\t\t     merge_bases, &merge_base)) {\n \t\tint flag;\n \n \t\tif (!(options.flags & REBASE_FORCE)) {\n-- \n2.21.0.1020.gf2820cf01a\n\n"},{"id":"375080","messageId":"20190508001252.15752-13-avarab@gmail.com","threadId":"50810","inReplyTo":"cover.1555832783.git.liu.denton@gmail.com","subject":"[RFC WIP PATCH v8 12/13] rebase: add a should_fast_forward() utility function","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-05-08T00:12:51Z","receivedAt":"2019-05-08T00:13:28Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"The is_interactive() test gets tricky around --rebase-merges. Let's\nsplit our use of it for the purposes of whether we should try\nfast-forwarding a rebase into a utility function to prepare for adding\nmore logic to that specific codepath.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/rebase.c | 7 ++++++-\n 1 file changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex cb5d7fcb53..167d4fcf67 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -890,6 +890,11 @@ static void populate_merge_bases(struct commit *head, struct commit *onto,\n \toidcpy(merge_base, &merge_bases->item->object.oid);\n }\n \n+static int should_fast_forward(struct rebase_options *opts)\n+{\n+\treturn !is_interactive(opts);\n+}\n+\n static int can_fast_forward(struct commit *head,\n \t\t\t    struct commit *onto, struct commit *upstream,\n \t\t\t    struct commit *restrict_revision,\n@@ -1715,7 +1720,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tif (head_commit)\n \t\tpopulate_merge_bases(head_commit, options.onto, merge_bases,\n \t\t\t\t     &merge_base);\n-\tif (!is_interactive(&options) &&\n+\tif (should_fast_forward(&options) &&\n \t    can_fast_forward(head_commit, options.onto, options.upstream,\n \t\t\t     options.restrict_revision, &options.orig_head,\n \t\t\t     merge_bases, &merge_base)) {\n-- \n2.21.0.1020.gf2820cf01a\n\n"},{"id":"375081","messageId":"20190508001252.15752-14-avarab@gmail.com","threadId":"50810","inReplyTo":"cover.1555832783.git.liu.denton@gmail.com","subject":"[RFC WIP PATCH v8 13/13] WIP: can_fast_forward() support for --preserve-merges and --rebase-merges","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2019-05-08T00:12:52Z","receivedAt":"2019-05-08T00:13:30Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"This seems to work, needs more tests etc...\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n---\n builtin/rebase.c               | 6 ++++++\n t/t3432-rebase-fast-forward.sh | 7 +++++--\n 2 files changed, 11 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 167d4fcf67..de1c5cacb8 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -892,6 +892,12 @@ static void populate_merge_bases(struct commit *head, struct commit *onto,\n \n static int should_fast_forward(struct rebase_options *opts)\n {\n+\tif (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {\n+\t\tif (opts->rebase_merges)\n+\t\t\treturn 1;\n+\t\tif (opts->type == REBASE_PRESERVE_MERGES)\n+\t\t\treturn 1;\n+\t}\n \treturn !is_interactive(opts);\n }\n \ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex e8a9bf42b6..d3e1815057 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -44,12 +44,13 @@ test_rebase_same_head_ () {\n \ttest_expect_$status \"git rebase$flag $* with $changes is $what with $cmp HEAD\" \"\n \t\toldhead=\\$(git rev-parse HEAD) &&\n \t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n-\t\tgit rebase$flag $* >stdout &&\n+\t\tgit rebase$flag $* >stdout 2>stderr &&\n \t\tif test $what = work\n \t\tthen\n \t\t\t# Must check this case first, for 'is up to\n \t\t\t# date, rebase forced[...]rewinding head' cases\n-\t\t\ttest_i18ngrep 'rewinding head' stdout\n+\t\t\ttest_i18ngrep 'rewinding head' stdout ||\n+\t\t\ttest_i18ngrep 'is up to date, rebase forced' stdout\n \t\telif test $what = noop\n \t\tthen\n \t\t\ttest_i18ngrep 'is up to date' stdout &&\n@@ -79,6 +80,8 @@ test_rebase_same_head success noop same success noop-force same --keep-base mast\n test_rebase_same_head success noop same success noop-force same --keep-base\n test_rebase_same_head success noop same success noop-force same --no-fork-point\n test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point\n+test_rebase_same_head success noop same success noop-force same --preserve-merges\n+test_rebase_same_head success noop same success noop-force same --rebase-merges\n test_rebase_same_head success noop same success work same --fork-point master\n test_rebase_same_head success noop same success work diff --fork-point --onto B B\n test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n-- \n2.21.0.1020.gf2820cf01a\n\n"},{"id":"375091","messageId":"xmqqa7fxionx.fsf@gitster-ct.c.googlers.com","threadId":"50810","inReplyTo":"20190508001252.15752-1-avarab@gmail.com","subject":"Re: [RFC WIP PATCH v8 00/13] learn --keep-base & more","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-05-08T03:57:22Z","receivedAt":"2019-05-08T03:57:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:\n\n> This is a WIP series I have that I figured I'd send out as-is for\n> comment since Junio said he'd be merging dl/rebase-i-keep-base down.\n>\n> So I wanted to test it, and as seen early in this series in 3/13 and\n> 4/13 we had significant blindspots in our tests, i.e. there were no\n> tests for whether --no-ff bypassed the amended logic as it should.\n>\n> As seen from those tests we may have some bugs here, either existing\n> or new, needs more poking at it.\n\nThanks.  I am actually OK to keep dl/rebase-i-keep-base out of\n'next' to iron out the kinks.  It's not like we are in a hurry to\ndeliber an important fix to our users---the topic is adding a new\nfeature and attempting to fix a minor irritation (i.e. lost\nopportunity to fast-forward).\n\n> Then in 9/13 and 10/13 I re-added the incomplete patches I had in\n> https://public-inbox.org/git/20190221214059.9195-1-avarab@gmail.com/\n> to see if my tests passed with Denton's --fork-point code, they\n> do. Yay!\n>\n> Left them there because I was wondering if I needed to port some/all\n> of the tests over, and maybe amend a commit message to reword some of\n> my findings in\n> https://public-inbox.org/git/871s3z6a4q.fsf@evledraar.gmail.com/\n>\n> Then I have 11/13 and 12/13 which seem pretty sensible to me as-is,\n> and finally I wanted --preserve-merges and --rebase-merges to also\n> benefit from this logic, so 13/13 is a WIP patch for that. The code\n> should be done (although maybe there's a better way to do it...), but\n> it needs a better commit message & tests.\n"},{"id":"375830","messageId":"nycvar.QRO.7.76.6.1905172301210.46@tvgsbejvaqbjf.bet","threadId":"50810","inReplyTo":"20190508001252.15752-12-avarab@gmail.com","subject":"Re: [RFC WIP PATCH v8 11/13] rebase: eliminate side-effects from can_fast_forward()","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-05-17T21:16:10Z","receivedAt":"2019-05-17T21:16:11Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Ævar,\n\nOn Wed, 8 May 2019, Ævar Arnfjörð Bjarmason wrote:\n\n> The can_fast_forward() function is potentially much more expensive\n> than is_interactive() since it e.g. might need to call\n> is_linear_history().\n>\n> So reversing the two looks like an obvious improvement, but doing so\n> reveals a previously hidden caveat: We need the can_fast_forward()\n> function to populate data used later, namely the \"merge_bases\"\n> variable. This has been the case since it was added in\n> 9a48a615b4 (\"builtin rebase: try to fast forward when possible\",\n> 2018-09-04).\n>\n> So let's refactor it into two functions. Now we'll always call\n> populate_merge_bases(), and then only call can_fast_forward() if\n> is_interactive() is false, making this both faster in pathological\n> cases, and more importantly easier to follow.\n\nTrue.\n\nWe might want to mention, though, what exactly that this \"pathological\ncase\" is: rebasing commits onto an unrelated history.\n\n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index ae6b9b42b8..cb5d7fcb53 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -878,24 +878,30 @@ static int is_linear_history(struct commit *from, struct commit *to)\n>  \treturn 1;\n>  }\n>\n> -static int can_fast_forward(struct commit *onto, struct commit *upstream,\n> +static void populate_merge_bases(struct commit *head, struct commit *onto,\n> +\t\t\t\t struct commit_list *merge_bases,\n> +\t\t\t\t struct object_id *merge_base)\n> +{\n> +\tmerge_bases = get_merge_bases(onto, head);\n\nHmm. This overrides whatever was passed in via the parameter. Did you mean\nto make the parameter of type `struct commit_list **`, i.e. a *pointer to\na pointer*?\n\n> +\tif (!merge_bases || merge_bases->next) {\n> +\t\toidcpy(merge_base, &null_oid);\n> +\t\treturn;\n> +\t}\n> +\toidcpy(merge_base, &merge_bases->item->object.oid);\n> +}\n> +\n> +static int can_fast_forward(struct commit *head,\n> +\t\t\t    struct commit *onto, struct commit *upstream,\n>  \t\t\t    struct commit *restrict_revision,\n> -\t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n> +\t\t\t    struct object_id *head_oid,\n> +\t\t\t    struct commit_list *merge_bases,\n> +\t\t\t    struct object_id *merge_base)\n>  {\n> -\tstruct commit *head = lookup_commit(the_repository, head_oid);\n> -\tstruct commit_list *merge_bases = NULL;\n>  \tint res = 0;\n>\n>  \tif (!head)\n>  \t\tgoto done;\n>\n> -\tmerge_bases = get_merge_bases(onto, head);\n> -\tif (!merge_bases || merge_bases->next) {\n> -\t\toidcpy(merge_base, &null_oid);\n> -\t\tgoto done;\n> -\t}\n> -\n> -\toidcpy(merge_base, &merge_bases->item->object.oid);\n\nDo we even use `merge_bases` after this anymore? I guess not. So we should\nget rid of this function parameter at this point.\n\n>  \tif (!oideq(merge_base, &onto->object.oid))\n\nUh oh. With this patch, `merge_base` can be `NULL` at this point, namely\nwhen trying to rebase commits onto an unrelated history.\n\n>  \t\tgoto done;\n>\n> @@ -1154,6 +1160,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \t\tOPT_END(),\n>  \t};\n>  \tint i;\n> +\tstruct commit *head_commit;\n> +\tstruct commit_list *merge_bases = NULL;\n>\n>  \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n>  \t\tusage_with_options(builtin_rebase_usage,\n> @@ -1703,9 +1711,14 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n>  \t * with new commits recreated by replaying their changes. This\n>  \t * optimization must not be done if this is an interactive rebase.\n>  \t */\n> -\tif (can_fast_forward(options.onto, options.upstream, options.restrict_revision,\n> -\t\t    &options.orig_head, &merge_base) &&\n> -\t    !is_interactive(&options)) {\n> +\thead_commit = lookup_commit(the_repository, &options.orig_head);\n\nHmm. I do not see this line (or any equivalent code) removed in this\npatch. Is it possible that we do not need to (re-?)initialize\n`head_commit` at this point?\n\nI guess I should read the entire patch series because that `head_commit`\nisn't there in the worktree I am looking at (oh, if only I had an easy way\nto review code, but I have to review static patches instead, not even in a\nproper IDE... ;-)). Will try to find time to do that next week.\n\nCiao,\nDscho\n\n> +\tif (head_commit)\n> +\t\tpopulate_merge_bases(head_commit, options.onto, merge_bases,\n> +\t\t\t\t     &merge_base);\n> +\tif (!is_interactive(&options) &&\n> +\t    can_fast_forward(head_commit, options.onto, options.upstream,\n> +\t\t\t     options.restrict_revision, &options.orig_head,\n> +\t\t\t     merge_bases, &merge_base)) {\n>  \t\tint flag;\n>\n>  \t\tif (!(options.flags & REBASE_FORCE)) {\n> --\n> 2.21.0.1020.gf2820cf01a\n>\n>\n"},{"id":"375833","messageId":"nycvar.QRO.7.76.6.1905172316260.46@tvgsbejvaqbjf.bet","threadId":"50810","inReplyTo":"20190508001252.15752-14-avarab@gmail.com","subject":"Re: [RFC WIP PATCH v8 13/13] WIP: can_fast_forward() support for --preserve-merges and --rebase-merges","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2019-05-17T22:02:24Z","receivedAt":"2019-05-17T22:02:27Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi Ævar,\n\nOn Wed, 8 May 2019, Ævar Arnfjörð Bjarmason wrote:\n\n> This seems to work, needs more tests etc...\n\nI can see how it works, but it is a bit limited, and at the same time\noverzealous.\n\nThe reason why we do not enter the fast-forwarding block in the\ninteractive case would appear to me to be that the interactive rebase\n*might* want to avoid fast-forwarding e.g. with --force-rebase.\n\nHowever, that is not the only instance where we must not simply\nfast-forward: Think `--exec`. There might be others, too. (I just saw that\n`--signoff` sets `FORCE_REBASE`, but `--exec` does not, so you cannot even\nuse the `FORCE_REBASE` flag as indicator.)\n\nSince `git rebase -rx \"make -j8\"` is something I use myself, this here\npatch would therefore break *my* workflow.\n\nThis is the \"overzealous\" part. Now for the \"limited\" part:\n\nLet's take a step back and ask how the interactive rebase handles these\npotentially fast-forwarding cases? Via `skip_unnecessary_picks()`.\n\nAnd that function is ill-prepared for rebasing merges (I specifically do\n*not* think about `--preserve-merges` at this point, for all I care, it is\nalready deprecated *and* dropped).\n\nEven if this function *was* well-prepared for rebasing merges, I think\nthat would miss the mark. Take this todo list, for example:\n\n\tlabel onto\n\n\t# Branch dscho\n\treset onto\n\tpick a123 first\n\tlabel dscho\n\n\t# Branch avar\n\treset onto\n\tpick b789 second\n\tlabel avar\n\n\treset onto\n\tmerge -C c124 dscho\n\tmerge -C d314 avar\n\nTwo branches, both one patch deep, both merged, one after the other. Now,\nif you insert `pick abc zeroth` before the first `pick`, obviously the\nfirst branch would no longer be skippable, but the second one totally\nwould be!\n\nThis is the \"limited\" part.\n\nTo remedy this, I think what we would need is code in `pick_commits()`,\nright where `TODO_RESET` is handled (or more toward the beginning of that\nfunction), that would:\n\n- parse the argument (this is currently done in `do_reset()` and would\n  have to be refactored out) and pretend that it is `HEAD`,\n\n- look at the following command: if it is\n\n\t- a `pick`, and if its parent agrees with `HEAD`, pretend that\n\t  the `pick` was actually a `reset`, update the pretended `HEAD`\n\t  and keep looking at the next command,\n\n\t- a `merge`, and if its option was `-C <orig-merge>` (not\n\t  lower-case `-c`!), and if its parent agrees with `HEAD`, and if\n\t  its merge head(s) agree with the original merge commit's (if\n\t  any), pretend that it was actually a `reset <orig-merge>`,\n\t  update the pretended `HEAD` and keep looking at the next\n\t  command,\n\n\t- a `label`, perform it, but with the pretended `HEAD`, and keep\n\t  looking for the next command,\n\n\t- a `reset`, update the `done` and `git-rebase-todo` files and\n\t  start the entire spiel from the top,\n\n\t- otherwise perform the reset.\n\n- all while skipping, this code would need to take care of updating the\n  `done` and `git-rebase-todo` files,\n\n- if a `reset` is necessary, and if it fails, the `done` and\n  `git-rebase-todo` files should *not* be updated, but the original\n  `reset` should be re-scheduled, and\n\n- since this adds quite a bit of code, it should probably be done in a\n  separate function.\n\nInstead of marking this as a left-over bit (which I would either forget,\nor whose status would be hard to track), I decided to open a ticket:\n\n\thttps://github.com/gitgitgadget/git/issues/209\n\n(I opened GitGitGadget's issues for exactly this kind of use case, because\nI recently tried to find some useful left-over bits as easy project\nstarters, and even *I* found it super hard to find those, let alone figure\nout whether they are being/have been addressed already, a mailing list is\njust not a good bug tracker, even if it is still better than trying to\nreport a bug on Twitter, where I could not even have written this\nparagraph in a single Tweet.)\n\nCiao,\nDscho\n\n> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n> ---\n>  builtin/rebase.c               | 6 ++++++\n>  t/t3432-rebase-fast-forward.sh | 7 +++++--\n>  2 files changed, 11 insertions(+), 2 deletions(-)\n>\n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index 167d4fcf67..de1c5cacb8 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -892,6 +892,12 @@ static void populate_merge_bases(struct commit *head, struct commit *onto,\n>\n>  static int should_fast_forward(struct rebase_options *opts)\n>  {\n> +\tif (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) {\n> +\t\tif (opts->rebase_merges)\n> +\t\t\treturn 1;\n> +\t\tif (opts->type == REBASE_PRESERVE_MERGES)\n> +\t\t\treturn 1;\n> +\t}\n>  \treturn !is_interactive(opts);\n>  }\n>\n> diff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\n> index e8a9bf42b6..d3e1815057 100755\n> --- a/t/t3432-rebase-fast-forward.sh\n> +++ b/t/t3432-rebase-fast-forward.sh\n> @@ -44,12 +44,13 @@ test_rebase_same_head_ () {\n>  \ttest_expect_$status \"git rebase$flag $* with $changes is $what with $cmp HEAD\" \"\n>  \t\toldhead=\\$(git rev-parse HEAD) &&\n>  \t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n> -\t\tgit rebase$flag $* >stdout &&\n> +\t\tgit rebase$flag $* >stdout 2>stderr &&\n>  \t\tif test $what = work\n>  \t\tthen\n>  \t\t\t# Must check this case first, for 'is up to\n>  \t\t\t# date, rebase forced[...]rewinding head' cases\n> -\t\t\ttest_i18ngrep 'rewinding head' stdout\n> +\t\t\ttest_i18ngrep 'rewinding head' stdout ||\n> +\t\t\ttest_i18ngrep 'is up to date, rebase forced' stdout\n>  \t\telif test $what = noop\n>  \t\tthen\n>  \t\t\ttest_i18ngrep 'is up to date' stdout &&\n> @@ -79,6 +80,8 @@ test_rebase_same_head success noop same success noop-force same --keep-base mast\n>  test_rebase_same_head success noop same success noop-force same --keep-base\n>  test_rebase_same_head success noop same success noop-force same --no-fork-point\n>  test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point\n> +test_rebase_same_head success noop same success noop-force same --preserve-merges\n> +test_rebase_same_head success noop same success noop-force same --rebase-merges\n>  test_rebase_same_head success noop same success work same --fork-point master\n>  test_rebase_same_head success noop same success work diff --fork-point --onto B B\n>  test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n> --\n> 2.21.0.1020.gf2820cf01a\n>\n>\n"},{"id":"379111","messageId":"xmqq1rylx2dy.fsf@gitster-ct.c.googlers.com","threadId":"50810","inReplyTo":"xmqqa7fxionx.fsf@gitster-ct.c.googlers.com","subject":"Re: [RFC WIP PATCH v8 00/13] learn --keep-base & more","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-07-19T19:14:49Z","receivedAt":"2019-07-19T19:15:20Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:\n>\n>> This is a WIP series I have that I figured I'd send out as-is for\n>> comment since Junio said he'd be merging dl/rebase-i-keep-base down.\n>>\n>> So I wanted to test it, and as seen early in this series in 3/13 and\n>> 4/13 we had significant blindspots in our tests, i.e. there were no\n>> tests for whether --no-ff bypassed the amended logic as it should.\n>>\n>> As seen from those tests we may have some bugs here, either existing\n>> or new, needs more poking at it.\n>\n> Thanks.  I am actually OK to keep dl/rebase-i-keep-base out of\n> 'next' to iron out the kinks.  It's not like we are in a hurry to\n> deliber an important fix to our users---the topic is adding a new\n> feature and attempting to fix a minor irritation (i.e. lost\n> opportunity to fast-forward).\n\nThe 'next' branch has thinned down sufficiently that it may be a\ngood time to resurrect topics like this one that we postponed. If\nthere is (still) interest in them, that is, which I do not know ;-)\n\n"},{"id":"379117","messageId":"20190719210156.GA9688@archbookpro.localdomain","threadId":"50810","inReplyTo":"xmqq1rylx2dy.fsf@gitster-ct.c.googlers.com","subject":"Re: [RFC WIP PATCH v8 00/13] learn --keep-base & more","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-07-19T21:01:56Z","receivedAt":"2019-07-19T21:02:02Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"On Fri, Jul 19, 2019 at 12:14:49PM -0700, Junio C Hamano wrote:\n> Junio C Hamano <gitster@pobox.com> writes:\n> \n> > Ævar Arnfjörð Bjarmason  <avarab@gmail.com> writes:\n> >\n> >> This is a WIP series I have that I figured I'd send out as-is for\n> >> comment since Junio said he'd be merging dl/rebase-i-keep-base down.\n> >>\n> >> So I wanted to test it, and as seen early in this series in 3/13 and\n> >> 4/13 we had significant blindspots in our tests, i.e. there were no\n> >> tests for whether --no-ff bypassed the amended logic as it should.\n> >>\n> >> As seen from those tests we may have some bugs here, either existing\n> >> or new, needs more poking at it.\n> >\n> > Thanks.  I am actually OK to keep dl/rebase-i-keep-base out of\n> > 'next' to iron out the kinks.  It's not like we are in a hurry to\n> > deliber an important fix to our users---the topic is adding a new\n> > feature and attempting to fix a minor irritation (i.e. lost\n> > opportunity to fast-forward).\n> \n> The 'next' branch has thinned down sufficiently that it may be a\n> good time to resurrect topics like this one that we postponed. If\n> there is (still) interest in them, that is, which I do not know ;-)\n> \n\nI'm definitely still interested in this topic but I haven't had much time\nto contribute since school's been busy lately.\n\nIf Ævar doesn't get to it by around a month from now, I'll polish up the\nRFC and resubmit.\n"},{"id":"381096","messageId":"cover.1566724236.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"20190508001252.15752-1-avarab@gmail.com","subject":"[PATCH v9 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-08-25T09:11:54Z","receivedAt":"2019-08-25T09:12:02Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Hi all, it's been a while but I guess now's a good time as any to\nresurrect this topic. This is basically a resubmission of Ævar's WIP v8\nbut I fixed a couple of minor whitespace issues.\n\nIn addition, I opted to drop patches 9-13 from v8 since I don't think I\ncan do a good enough job polishing them up and I don't really understand\nthe intricacies of this part of the rebase code. Hopefully, Ævar will\npick them up at a later time.\n\nChanges since v1:\n\n* Squashed old set into one patch\n* Fixed indentation style and dangling else\n* Added more documentation after discussion with Ævar\n\nChanges since v2:\n\n* Add testing for rebase --fork-point behaviour\n* Add testing for rebase fast-forward behaviour\n* Make rebase --onto fast-forward in more cases\n* Update documentation to include use-case\n\nChanges since v3:\n\n* Fix tests failing on bash 4.2\n* Fix typo in t3431 comment\n\nChanges since v4:\n\n* Make rebase --fork-point fast-forward in more cases\n\nChanges since v5:\n\n* Fix graph illustrations so that the \"branch off\" is visually in the\n  correct place\n* Refactor if-else in can_fast_forward into one-level-deep ifs to\n  increase clarity\n\nChanges since v6:\n\n* Remove redundant braces around if\n* Update comment around can_fast_forward call\n* Add completion for rebase\n\nChanges since v7:\n\n* Ævar sent in his WIP patchset\n\nChanges since v8:\n\n* Drop patches 9-13\n* Fix some minor whitespace issues from v7\n\n[1]: https://public-inbox.org/git/a1b4b74b9167e279dae4cd8c58fb28d8a714a66a.1553537656.git.gitgitgadget@gmail.com/\n\n\nDenton Liu (6):\n  t3431: add rebase --fork-point tests\n  t3432: test rebase fast-forward behavior\n  rebase: refactor can_fast_forward into goto tower\n  rebase: fast-forward --onto in more cases\n  rebase: fast-forward --fork-point in more cases\n  rebase: teach rebase --keep-base\n\nÆvar Arnfjörð Bjarmason (3):\n  t3432: distinguish \"noop-same\" v.s. \"work-same\" in \"same head\" tests\n  t3432: test for --no-ff's interaction with fast-forward\n  rebase tests: test linear branch topology\n\n Documentation/git-rebase.txt           |  30 +++++-\n builtin/rebase.c                       |  85 ++++++++++++-----\n contrib/completion/git-completion.bash |   2 +-\n t/t3400-rebase.sh                      |   2 +-\n t/t3404-rebase-interactive.sh          |   2 +-\n t/t3416-rebase-onto-threedots.sh       |  57 +++++++++++\n t/t3421-rebase-topology-linear.sh      |  29 ++++++\n t/t3431-rebase-fork-point.sh           |  57 +++++++++++\n t/t3432-rebase-fast-forward.sh         | 125 +++++++++++++++++++++++++\n 9 files changed, 361 insertions(+), 28 deletions(-)\n create mode 100755 t/t3431-rebase-fork-point.sh\n create mode 100755 t/t3432-rebase-fast-forward.sh\n\nRange-diff against v8:\n 1:  eb64f6c91d !  1:  03f769d410 t3431: add rebase --fork-point tests\n    @@ t/t3431-rebase-fork-point.sh (new)\n     +\ttest_commit G\n     +'\n     +\n    -+test_rebase() {\n    ++test_rebase () {\n     +\texpected=\"$1\" &&\n     +\tshift &&\n     +\ttest_expect_success \"git rebase $*\" \"\n 2:  4c087ec041 !  2:  bc8998079d t3432: test rebase fast-forward behavior\n    @@ t/t3432-rebase-fast-forward.sh (new)\n     +\tgit checkout -t -b side\n     +'\n     +\n    -+test_rebase_same_head() {\n    ++test_rebase_same_head () {\n     +\tstatus=\"$1\" &&\n     +\tshift &&\n     +\ttest_expect_$status \"git rebase $* with $changes is no-op\" \"\n -:  ---------- >  3:  5c08e2b81f t3432: distinguish \"noop-same\" v.s. \"work-same\" in \"same head\" tests\n -:  ---------- >  4:  48b4e41a17 t3432: test for --no-ff's interaction with fast-forward\n 3:  3d348d2c37 =  5:  9bd34b4a40 rebase: refactor can_fast_forward into goto tower\n 4:  27cbcfaeae !  6:  becb924232 rebase: fast-forward --onto in more cases\n    @@ Commit message\n         Add '-f' to test cases that failed as a result of this change because\n         they were not expecting a fast-forward so that a rebase is forced.\n     \n    -    While we're at it, remove a trailing whitespace from rebase.c.\n    -\n         Helped-by: Phillip Wood <phillip.wood@dunelm.org.uk>\n         Signed-off-by: Denton Liu <liu.denton@gmail.com>\n     \n    @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix\n      \t\tint flag;\n      \n      \t\tif (!(options.flags & REBASE_FORCE)) {\n    -@@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)\n    - \tstrbuf_addf(&msg, \"%s: checkout %s\",\n    - \t\t    getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name);\n    - \tif (reset_head(&options.onto->object.oid, \"checkout\", NULL,\n    --\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD | \n    -+\t\t       RESET_HEAD_DETACH | RESET_ORIG_HEAD |\n    - \t\t       RESET_HEAD_RUN_POST_CHECKOUT_HOOK,\n    - \t\t       NULL, msg.buf))\n    - \t\tdie(_(\"Could not detach HEAD\"));\n     \n      ## t/t3400-rebase.sh ##\n    -@@ t/t3400-rebase.sh: test_expect_success 'rebase--am.sh and --show-current-patch' '\n    +@@ t/t3400-rebase.sh: test_expect_success 'rebase --am and --show-current-patch' '\n      \t\techo two >>init.t &&\n      \t\tgit commit -a -m two &&\n      \t\tgit tag two &&\n    @@ t/t3404-rebase-interactive.sh: test_expect_success C_LOCALE_OUTPUT 'rebase --edi\n      '\n     \n      ## t/t3432-rebase-fast-forward.sh ##\n    -@@ t/t3432-rebase-fast-forward.sh: test_expect_success 'add work to upstream' '\n    +@@ t/t3432-rebase-fast-forward.sh: test_expect_success 'add work same to upstream' '\n      changes='our and their changes'\n    - test_rebase_same_head success --onto B B\n    - test_rebase_same_head success --onto B... B\n    --test_rebase_same_head failure --onto master... master\n    -+test_rebase_same_head success --onto master... master\n    - test_rebase_same_head failure --fork-point --onto B B\n    - test_rebase_same_head failure --fork-point --onto B... B\n    --test_rebase_same_head failure --fork-point --onto master... master\n    -+test_rebase_same_head success --fork-point --onto master... master\n    + test_rebase_same_head success noop same success noop-force diff --onto B B\n    + test_rebase_same_head success noop same success noop-force diff --onto B... B\n    +-test_rebase_same_head failure work same success work diff --onto master... master\n    ++test_rebase_same_head success noop same success work diff --onto master... master\n    + test_rebase_same_head failure work same success work diff --fork-point --onto B B\n    + test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n    +-test_rebase_same_head failure work same success work diff --fork-point --onto master... master\n    ++test_rebase_same_head success noop same success work diff --fork-point --onto master... master\n      \n      test_done\n 5:  8254730810 !  7:  4dab5847cb rebase: fast-forward --fork-point in more cases\n    @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix\n      \t\tif (!(options.flags & REBASE_FORCE)) {\n     \n      ## t/t3432-rebase-fast-forward.sh ##\n    -@@ t/t3432-rebase-fast-forward.sh: test_rebase_same_head success --onto B... B\n    - test_rebase_same_head success --onto master... master\n    - test_rebase_same_head success --no-fork-point\n    - test_rebase_same_head success --fork-point master\n    --test_rebase_same_head failure --fork-point --onto B B\n    --test_rebase_same_head failure --fork-point --onto B... B\n    -+test_rebase_same_head success --fork-point --onto B B\n    -+test_rebase_same_head success --fork-point --onto B... B\n    - test_rebase_same_head success --fork-point --onto master... master\n    +@@ t/t3432-rebase-fast-forward.sh: test_rebase_same_head_ () {\n    + }\n    + \n    + changes='no changes'\n    +-test_rebase_same_head success work same success work same\n    ++test_rebase_same_head success noop same success work same\n    + test_rebase_same_head success noop same success noop-force same master\n    + test_rebase_same_head success noop same success noop-force diff --onto B B\n    + test_rebase_same_head success noop same success noop-force diff --onto B... B\n    + test_rebase_same_head success noop same success noop-force same --onto master... master\n    + test_rebase_same_head success noop same success noop-force same --no-fork-point\n    +-test_rebase_same_head success work same success work same --fork-point master\n    +-test_rebase_same_head failure noop same success work diff --fork-point --onto B B\n    +-test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n    +-test_rebase_same_head success work same success work same --fork-point --onto master... master\n    ++test_rebase_same_head success noop same success work same --fork-point master\n    ++test_rebase_same_head success noop same success work diff --fork-point --onto B B\n    ++test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n    ++test_rebase_same_head success noop same success work same --fork-point --onto master... master\n      \n    - test_expect_success 'add work to side' '\n    -@@ t/t3432-rebase-fast-forward.sh: test_rebase_same_head success --onto B... B\n    - test_rebase_same_head success --onto master... master\n    - test_rebase_same_head success --no-fork-point\n    - test_rebase_same_head success --fork-point master\n    --test_rebase_same_head failure --fork-point --onto B B\n    --test_rebase_same_head failure --fork-point --onto B... B\n    -+test_rebase_same_head success --fork-point --onto B B\n    -+test_rebase_same_head success --fork-point --onto B... B\n    - test_rebase_same_head success --fork-point --onto master... master\n    + test_expect_success 'add work same to side' '\n    + \ttest_commit E\n    + '\n      \n    - test_expect_success 'add work to upstream' '\n    -@@ t/t3432-rebase-fast-forward.sh: changes='our and their changes'\n    - test_rebase_same_head success --onto B B\n    - test_rebase_same_head success --onto B... B\n    - test_rebase_same_head success --onto master... master\n    --test_rebase_same_head failure --fork-point --onto B B\n    --test_rebase_same_head failure --fork-point --onto B... B\n    -+test_rebase_same_head success --fork-point --onto B B\n    -+test_rebase_same_head success --fork-point --onto B... B\n    - test_rebase_same_head success --fork-point --onto master... master\n    + changes='our changes'\n    +-test_rebase_same_head success work same success work same\n    ++test_rebase_same_head success noop same success work same\n    + test_rebase_same_head success noop same success noop-force same master\n    + test_rebase_same_head success noop same success noop-force diff --onto B B\n    + test_rebase_same_head success noop same success noop-force diff --onto B... B\n    + test_rebase_same_head success noop same success noop-force same --onto master... master\n    + test_rebase_same_head success noop same success noop-force same --no-fork-point\n    +-test_rebase_same_head success work same success work same --fork-point master\n    +-test_rebase_same_head failure work same success work diff --fork-point --onto B B\n    +-test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n    +-test_rebase_same_head success work same success work same --fork-point --onto master... master\n    ++test_rebase_same_head success noop same success work same --fork-point master\n    ++test_rebase_same_head success noop same success work diff --fork-point --onto B B\n    ++test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n    ++test_rebase_same_head success noop same success work same --fork-point --onto master... master\n      \n    - test_done\n    + test_expect_success 'add work same to upstream' '\n    + \tgit checkout master &&\n -:  ---------- >  8:  4699a90993 rebase tests: test linear branch topology\n 6:  f18d3ee3fa !  9:  6927aba617 rebase: teach rebase --keep-base\n    @@ Commit message\n         in order to preserve the merge base. This is useful when contributing a\n         patch series to the Git mailing list, one often starts on top of the\n         current 'master'. However, while developing the patches, 'master' is\n    -    also developed further and it is sometimes not the bst idea to keep\n    +    also developed further and it is sometimes not the best idea to keep\n         rebasing on top of 'master', but to keep the base commit as-is.\n     \n         Alternatively, a user wishing to test individual commits in a topic\n    @@ Documentation/git-rebase.txt: git-rebase - Reapply commits on top of another bas\n     +\t[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\n      'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n      \t--root [<branch>]\n    - 'git rebase' --continue | --skip | --abort | --quit | --edit-todo | --show-current-patch\n    + 'git rebase' (--continue | --skip | --abort | --quit | --edit-todo | --show-current-patch)\n     @@ Documentation/git-rebase.txt: As a special case, you may use \"A\\...B\" as a shortcut for the\n      merge base of A and B if there is exactly one merge base. You can\n      leave out at most one of A and B, in which case it defaults to HEAD.\n    @@ Documentation/git-rebase.txt: NOTE: While an \"easy case recovery\" sometimes appe\n     \n      ## builtin/rebase.c ##\n     @@\n    - #include \"branch.h\"\n    + #include \"rebase-interactive.h\"\n      \n      static char const * const builtin_rebase_usage[] = {\n     -\tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n    @@ builtin/rebase.c\n      \t\t\"--root [<branch>]\"),\n      \tN_(\"git rebase --continue | --abort | --skip | --edit-todo\"),\n     @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)\n    - \t};\n    + \tstruct rebase_options options = REBASE_OPTIONS_INIT;\n      \tconst char *branch_name;\n      \tint ret, flags, total_argc, in_progress = 0;\n     +\tint keep_base = 0;\n    @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix\n      \t\t\t N_(\"allow pre-rebase hook to run\")),\n      \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n     @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)\n    - \t\tusage_with_options(builtin_rebase_usage,\n    - \t\t\t\t   builtin_rebase_options);\n    + \t\twarning(_(\"git rebase --preserve-merges is deprecated. \"\n    + \t\t\t  \"Use --rebase-merges instead.\"));\n      \n     +\tif (keep_base) {\n     +\t\tif (options.onto_name)\n    @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix\n     +\t\t\tdie(_(\"cannot combine '--keep-base' with '--root'\"));\n     +\t}\n     +\n    - \tif (action != NO_ACTION && !in_progress)\n    + \tif (action != ACTION_NONE && !in_progress)\n      \t\tdie(_(\"No rebase in progress?\"));\n      \tsetenv(GIT_REFLOG_ACTION_ENVIRONMENT, \"rebase\", 0);\n     @@ builtin/rebase.c: int cmd_rebase(int argc, const char **argv, const char *prefix)\n    @@ t/t3416-rebase-onto-threedots.sh: test_expect_success 'rebase -i --onto master..\n      test_done\n     \n      ## t/t3431-rebase-fork-point.sh ##\n    -@@ t/t3431-rebase-fork-point.sh: test_rebase() {\n    +@@ t/t3431-rebase-fork-point.sh: test_rebase () {\n      \n      test_rebase 'G F E D B A'\n      test_rebase 'G F D B A' --onto D\n    @@ t/t3431-rebase-fork-point.sh: test_rebase() {\n      test_done\n     \n      ## t/t3432-rebase-fast-forward.sh ##\n    -@@ t/t3432-rebase-fast-forward.sh: test_rebase_same_head success master\n    - test_rebase_same_head success --onto B B\n    - test_rebase_same_head success --onto B... B\n    - test_rebase_same_head success --onto master... master\n    -+test_rebase_same_head success --keep-base master\n    -+test_rebase_same_head success --keep-base\n    - test_rebase_same_head success --no-fork-point\n    -+test_rebase_same_head success --keep-base --no-fork-point\n    - test_rebase_same_head success --fork-point master\n    - test_rebase_same_head success --fork-point --onto B B\n    - test_rebase_same_head success --fork-point --onto B... B\n    - test_rebase_same_head success --fork-point --onto master... master\n    -+test_rebase_same_head success --fork-point --keep-base master\n    +@@ t/t3432-rebase-fast-forward.sh: test_rebase_same_head success noop same success noop-force same master\n    + test_rebase_same_head success noop same success noop-force diff --onto B B\n    + test_rebase_same_head success noop same success noop-force diff --onto B... B\n    + test_rebase_same_head success noop same success noop-force same --onto master... master\n    ++test_rebase_same_head success noop same success noop-force same --keep-base master\n    ++test_rebase_same_head success noop same success noop-force same --keep-base\n    + test_rebase_same_head success noop same success noop-force same --no-fork-point\n    ++test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point\n    + test_rebase_same_head success noop same success work same --fork-point master\n    + test_rebase_same_head success noop same success work diff --fork-point --onto B B\n    + test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n    + test_rebase_same_head success noop same success work same --fork-point --onto master... master\n    ++test_rebase_same_head success noop same success work same --keep-base --keep-base master\n      \n    - test_expect_success 'add work to side' '\n    + test_expect_success 'add work same to side' '\n      \ttest_commit E\n    -@@ t/t3432-rebase-fast-forward.sh: test_rebase_same_head success master\n    - test_rebase_same_head success --onto B B\n    - test_rebase_same_head success --onto B... B\n    - test_rebase_same_head success --onto master... master\n    -+test_rebase_same_head success --keep-base master\n    -+test_rebase_same_head success --keep-base\n    - test_rebase_same_head success --no-fork-point\n    -+test_rebase_same_head success --keep-base --no-fork-point\n    - test_rebase_same_head success --fork-point master\n    - test_rebase_same_head success --fork-point --onto B B\n    - test_rebase_same_head success --fork-point --onto B... B\n    - test_rebase_same_head success --fork-point --onto master... master\n    -+test_rebase_same_head success --fork-point --keep-base master\n    +@@ t/t3432-rebase-fast-forward.sh: test_rebase_same_head success noop same success noop-force same master\n    + test_rebase_same_head success noop same success noop-force diff --onto B B\n    + test_rebase_same_head success noop same success noop-force diff --onto B... B\n    + test_rebase_same_head success noop same success noop-force same --onto master... master\n    ++test_rebase_same_head success noop same success noop-force same --keep-base master\n    ++test_rebase_same_head success noop same success noop-force same --keep-base\n    + test_rebase_same_head success noop same success noop-force same --no-fork-point\n    ++test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point\n    + test_rebase_same_head success noop same success work same --fork-point master\n    + test_rebase_same_head success noop same success work diff --fork-point --onto B B\n    + test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n    + test_rebase_same_head success noop same success work same --fork-point --onto master... master\n    ++test_rebase_same_head success noop same success work same --fork-point --keep-base master\n      \n    - test_expect_success 'add work to upstream' '\n    + test_expect_success 'add work same to upstream' '\n      \tgit checkout master &&\n     @@ t/t3432-rebase-fast-forward.sh: changes='our and their changes'\n    - test_rebase_same_head success --onto B B\n    - test_rebase_same_head success --onto B... B\n    - test_rebase_same_head success --onto master... master\n    -+test_rebase_same_head success --keep-base master\n    -+test_rebase_same_head success --keep-base\n    - test_rebase_same_head success --fork-point --onto B B\n    - test_rebase_same_head success --fork-point --onto B... B\n    - test_rebase_same_head success --fork-point --onto master... master\n    -+test_rebase_same_head success --fork-point --keep-base master\n    + test_rebase_same_head success noop same success noop-force diff --onto B B\n    + test_rebase_same_head success noop same success noop-force diff --onto B... B\n    + test_rebase_same_head success noop same success work diff --onto master... master\n    ++test_rebase_same_head success noop same success work diff --keep-base master\n    ++test_rebase_same_head success noop same success work diff --keep-base\n    + test_rebase_same_head failure work same success work diff --fork-point --onto B B\n    + test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n    + test_rebase_same_head success noop same success work diff --fork-point --onto master... master\n    ++test_rebase_same_head success noop same success work diff --fork-point --keep-base master\n      \n      test_done\n-- \n2.23.0.248.g3a9dd8fb08\n\n"},{"id":"381097","messageId":"03f769d410447fc2db3b6dd18cdcc5f6eb160f62.1566724236.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1566724236.git.liu.denton@gmail.com","subject":"[PATCH v9 1/9] t3431: add rebase --fork-point tests","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-08-25T09:11:57Z","receivedAt":"2019-08-25T09:12:02Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Signed-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3431-rebase-fork-point.sh | 53 ++++++++++++++++++++++++++++++++++++\n 1 file changed, 53 insertions(+)\n create mode 100755 t/t3431-rebase-fork-point.sh\n\ndiff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\nnew file mode 100755\nindex 0000000000..2d5c6e641e\n--- /dev/null\n+++ b/t/t3431-rebase-fork-point.sh\n@@ -0,0 +1,53 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2019 Denton Liu\n+#\n+\n+test_description='git rebase --fork-point test'\n+\n+. ./test-lib.sh\n+\n+# A---B---D---E    (master)\n+#      \\\n+#       C*---F---G (side)\n+#\n+# C was formerly part of master but master was rewound to remove C\n+#\n+test_expect_success setup '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\ttest_commit C &&\n+\tgit branch -t side &&\n+\tgit reset --hard HEAD^ &&\n+\ttest_commit D &&\n+\ttest_commit E &&\n+\tgit checkout side &&\n+\ttest_commit F &&\n+\ttest_commit G\n+'\n+\n+test_rebase () {\n+\texpected=\"$1\" &&\n+\tshift &&\n+\ttest_expect_success \"git rebase $*\" \"\n+\t\tgit checkout master &&\n+\t\tgit reset --hard E &&\n+\t\tgit checkout side &&\n+\t\tgit reset --hard G &&\n+\t\tgit rebase $* &&\n+\t\ttest_write_lines $expected >expect &&\n+\t\tgit log --pretty=%s >actual &&\n+\t\ttest_cmp expect actual\n+\t\"\n+}\n+\n+test_rebase 'G F E D B A'\n+test_rebase 'G F D B A' --onto D\n+test_rebase 'G F C E D B A' --no-fork-point\n+test_rebase 'G F C D B A' --no-fork-point --onto D\n+test_rebase 'G F E D B A' --fork-point refs/heads/master\n+test_rebase 'G F D B A' --fork-point --onto D refs/heads/master\n+test_rebase 'G F C E D B A' refs/heads/master\n+test_rebase 'G F C D B A' --onto D refs/heads/master\n+\n+test_done\n-- \n2.23.0.248.g3a9dd8fb08\n\n"},{"id":"381098","messageId":"5c08e2b81fd65c5d4bcef1fb908987364143d181.1566724236.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1566724236.git.liu.denton@gmail.com","subject":"[PATCH v9 3/9] t3432: distinguish \"noop-same\" v.s. \"work-same\" in \"same head\" tests","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-08-25T09:12:02Z","receivedAt":"2019-08-25T09:12:07Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nChange \"same head\" introduced in the preceding commit to check whether\nthe rebase.c code lands in the can_fast_forward() case in, and thus\nprints out an \"is up to date\" and aborts early.\n\nIn some of these cases we make it past that and to \"rewinding head\",\nthen do a rebase, only to find out there's nothing to change so HEAD\nstays at the same OID.\n\nThese tests presumed these two cases were the same thing. In terms of\nwhere HEAD ends up they are, but we're not only interested in rebase\nsemantics, but also whether or not we're needlessly doing work when we\ncould avoid it entirely.\n\nI'm adding \"same\" and \"diff\" here because I'll follow-up and add\n--no-ff tests, where some of those will be \"diff\"-erent, so add the\n\"diff\" code already.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3432-rebase-fast-forward.sh | 79 +++++++++++++++++++++-------------\n 1 file changed, 48 insertions(+), 31 deletions(-)\n\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex f49af274e0..d9f20fa07c 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -18,55 +18,72 @@ test_expect_success setup '\n test_rebase_same_head () {\n \tstatus=\"$1\" &&\n \tshift &&\n-\ttest_expect_$status \"git rebase $* with $changes is no-op\" \"\n+\twhat=\"$1\" &&\n+\tshift &&\n+\tcmp=\"$1\" &&\n+\tshift &&\n+\ttest_expect_$status \"git rebase $* with $changes is $what\" \"\n \t\toldhead=\\$(git rev-parse HEAD) &&\n \t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n-\t\tgit rebase $* &&\n+\t\tgit rebase $* >stdout &&\n+\t\tif test $what = work\n+\t\tthen\n+\t\t\ttest_i18ngrep 'rewinding head' stdout\n+\t\telif test $what = noop\n+\t\tthen\n+\t\t\ttest_i18ngrep 'is up to date' stdout\n+\t\tfi &&\n \t\tnewhead=\\$(git rev-parse HEAD) &&\n-\t\ttest_cmp_rev \\$oldhead \\$newhead\n+\t\tif test $cmp = same\n+\t\tthen\n+\t\t\ttest_cmp_rev \\$oldhead \\$newhead\n+\t\telif test $cmp = diff\n+\t\tthen\n+\t\t\t! test_cmp_rev \\$oldhead \\$newhead\n+\t\tfi\n \t\"\n }\n \n changes='no changes'\n-test_rebase_same_head success\n-test_rebase_same_head success master\n-test_rebase_same_head success --onto B B\n-test_rebase_same_head success --onto B... B\n-test_rebase_same_head success --onto master... master\n-test_rebase_same_head success --no-fork-point\n-test_rebase_same_head success --fork-point master\n-test_rebase_same_head failure --fork-point --onto B B\n-test_rebase_same_head failure --fork-point --onto B... B\n-test_rebase_same_head success --fork-point --onto master... master\n+test_rebase_same_head success work same\n+test_rebase_same_head success noop same master\n+test_rebase_same_head success noop same --onto B B\n+test_rebase_same_head success noop same --onto B... B\n+test_rebase_same_head success noop same --onto master... master\n+test_rebase_same_head success noop same --no-fork-point\n+test_rebase_same_head success work same --fork-point master\n+test_rebase_same_head failure noop same --fork-point --onto B B\n+test_rebase_same_head failure work same --fork-point --onto B... B\n+test_rebase_same_head success work same --fork-point --onto master... master\n \n-test_expect_success 'add work to side' '\n+test_expect_success 'add work same to side' '\n \ttest_commit E\n '\n \n changes='our changes'\n-test_rebase_same_head success\n-test_rebase_same_head success master\n-test_rebase_same_head success --onto B B\n-test_rebase_same_head success --onto B... B\n-test_rebase_same_head success --onto master... master\n-test_rebase_same_head success --no-fork-point\n-test_rebase_same_head success --fork-point master\n-test_rebase_same_head failure --fork-point --onto B B\n-test_rebase_same_head failure --fork-point --onto B... B\n-test_rebase_same_head success --fork-point --onto master... master\n+test_rebase_same_head success work same\n+test_rebase_same_head success noop same master\n+test_rebase_same_head success noop same --onto B B\n+test_rebase_same_head success noop same --onto B... B\n+test_rebase_same_head success noop same --onto master... master\n+test_rebase_same_head success noop same --no-fork-point\n+test_rebase_same_head success work same --fork-point master\n+test_rebase_same_head failure work same --fork-point --onto B B\n+test_rebase_same_head failure work same --fork-point --onto B... B\n+test_rebase_same_head success work same --fork-point --onto master... master\n \n-test_expect_success 'add work to upstream' '\n+test_expect_success 'add work same to upstream' '\n \tgit checkout master &&\n \ttest_commit F &&\n \tgit checkout side\n '\n \n changes='our and their changes'\n-test_rebase_same_head success --onto B B\n-test_rebase_same_head success --onto B... B\n-test_rebase_same_head failure --onto master... master\n-test_rebase_same_head failure --fork-point --onto B B\n-test_rebase_same_head failure --fork-point --onto B... B\n-test_rebase_same_head failure --fork-point --onto master... master\n+test_rebase_same_head success noop same --onto B B\n+test_rebase_same_head success noop same --onto B... B\n+test_rebase_same_head failure work same --onto master... master\n+test_rebase_same_head failure work same --fork-point --onto B B\n+test_rebase_same_head failure work same --fork-point --onto B... B\n+test_rebase_same_head failure work same --fork-point --onto master... master\n \n test_done\n-- \n2.23.0.248.g3a9dd8fb08\n\n"},{"id":"381099","messageId":"bc8998079dfce14e88e3c6145740fda04d1c82fe.1566724236.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1566724236.git.liu.denton@gmail.com","subject":"[PATCH v9 2/9] t3432: test rebase fast-forward behavior","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-08-25T09:12:00Z","receivedAt":"2019-08-25T09:12:07Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"When rebase is run on a branch that can be fast-forwarded, this should\nautomatically be done. Create test to ensure this behavior happens.\n\nThere are some cases that currently don't pass. The first case is where\na feature and master have diverged, running\n\"git rebase master... master\" causes a full rebase to happen even though\na fast-forward should happen.\n\nThe second case is when we are doing \"git rebase --fork-point\" and a\nfork-point commit is found. Once again, a full rebase happens even\nthough a fast-forward should happen.\n\nMark these cases as failure so we can fix it later.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3432-rebase-fast-forward.sh | 72 ++++++++++++++++++++++++++++++++++\n 1 file changed, 72 insertions(+)\n create mode 100755 t/t3432-rebase-fast-forward.sh\n\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nnew file mode 100755\nindex 0000000000..f49af274e0\n--- /dev/null\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -0,0 +1,72 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2019 Denton Liu\n+#\n+\n+test_description='ensure rebase fast-forwards commits when possible'\n+\n+. ./test-lib.sh\n+\n+test_expect_success setup '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\ttest_commit C &&\n+\ttest_commit D &&\n+\tgit checkout -t -b side\n+'\n+\n+test_rebase_same_head () {\n+\tstatus=\"$1\" &&\n+\tshift &&\n+\ttest_expect_$status \"git rebase $* with $changes is no-op\" \"\n+\t\toldhead=\\$(git rev-parse HEAD) &&\n+\t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n+\t\tgit rebase $* &&\n+\t\tnewhead=\\$(git rev-parse HEAD) &&\n+\t\ttest_cmp_rev \\$oldhead \\$newhead\n+\t\"\n+}\n+\n+changes='no changes'\n+test_rebase_same_head success\n+test_rebase_same_head success master\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --no-fork-point\n+test_rebase_same_head success --fork-point master\n+test_rebase_same_head failure --fork-point --onto B B\n+test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head success --fork-point --onto master... master\n+\n+test_expect_success 'add work to side' '\n+\ttest_commit E\n+'\n+\n+changes='our changes'\n+test_rebase_same_head success\n+test_rebase_same_head success master\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --no-fork-point\n+test_rebase_same_head success --fork-point master\n+test_rebase_same_head failure --fork-point --onto B B\n+test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head success --fork-point --onto master... master\n+\n+test_expect_success 'add work to upstream' '\n+\tgit checkout master &&\n+\ttest_commit F &&\n+\tgit checkout side\n+'\n+\n+changes='our and their changes'\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head failure --onto master... master\n+test_rebase_same_head failure --fork-point --onto B B\n+test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head failure --fork-point --onto master... master\n+\n+test_done\n-- \n2.23.0.248.g3a9dd8fb08\n\n"},{"id":"381100","messageId":"48b4e41a17d98b2f3a4254ed37c76361c3abc9ba.1566724236.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1566724236.git.liu.denton@gmail.com","subject":"[PATCH v9 4/9] t3432: test for --no-ff's interaction with fast-forward","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-08-25T09:12:05Z","receivedAt":"2019-08-25T09:12:15Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nAdd more stress tests for the can_fast_forward() case in\nrebase.c. These tests are getting rather verbose, but now we can see\nunder --ff and --no-ff whether we skip work, or whether we're forced\nto run the rebase.\n\nThese tests aren't supposed to endorse the status quo, just test for\nwhat we're currently doing.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3432-rebase-fast-forward.sh | 83 ++++++++++++++++++++++------------\n 1 file changed, 54 insertions(+), 29 deletions(-)\n\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex d9f20fa07c..02b2516595 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -16,22 +16,47 @@ test_expect_success setup '\n '\n \n test_rebase_same_head () {\n+\tstatus_n=\"$1\" &&\n+\tshift &&\n+\twhat_n=\"$1\" &&\n+\tshift &&\n+\tcmp_n=\"$1\" &&\n+\tshift &&\n+\tstatus_f=\"$1\" &&\n+\tshift &&\n+\twhat_f=\"$1\" &&\n+\tshift &&\n+\tcmp_f=\"$1\" &&\n+\tshift &&\n+\ttest_rebase_same_head_ $status_n $what_n $cmp_n \"\" \"$*\" &&\n+\ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --no-ff\" \"$*\"\n+}\n+\n+test_rebase_same_head_ () {\n \tstatus=\"$1\" &&\n \tshift &&\n \twhat=\"$1\" &&\n \tshift &&\n \tcmp=\"$1\" &&\n \tshift &&\n-\ttest_expect_$status \"git rebase $* with $changes is $what\" \"\n+\tflag=\"$1\"\n+\tshift &&\n+\ttest_expect_$status \"git rebase$flag $* with $changes is $what with $cmp HEAD\" \"\n \t\toldhead=\\$(git rev-parse HEAD) &&\n \t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n-\t\tgit rebase $* >stdout &&\n+\t\tgit rebase$flag $* >stdout &&\n \t\tif test $what = work\n \t\tthen\n+\t\t\t# Must check this case first, for 'is up to\n+\t\t\t# date, rebase forced[...]rewinding head' cases\n \t\t\ttest_i18ngrep 'rewinding head' stdout\n \t\telif test $what = noop\n \t\tthen\n-\t\t\ttest_i18ngrep 'is up to date' stdout\n+\t\t\ttest_i18ngrep 'is up to date' stdout &&\n+\t\t\t! test_i18ngrep 'rebase forced' stdout\n+\t\telif test $what = noop-force\n+\t\tthen\n+\t\t\ttest_i18ngrep 'is up to date, rebase forced' stdout\n \t\tfi &&\n \t\tnewhead=\\$(git rev-parse HEAD) &&\n \t\tif test $cmp = same\n@@ -45,32 +70,32 @@ test_rebase_same_head () {\n }\n \n changes='no changes'\n-test_rebase_same_head success work same\n-test_rebase_same_head success noop same master\n-test_rebase_same_head success noop same --onto B B\n-test_rebase_same_head success noop same --onto B... B\n-test_rebase_same_head success noop same --onto master... master\n-test_rebase_same_head success noop same --no-fork-point\n-test_rebase_same_head success work same --fork-point master\n-test_rebase_same_head failure noop same --fork-point --onto B B\n-test_rebase_same_head failure work same --fork-point --onto B... B\n-test_rebase_same_head success work same --fork-point --onto master... master\n+test_rebase_same_head success work same success work same\n+test_rebase_same_head success noop same success noop-force same master\n+test_rebase_same_head success noop same success noop-force diff --onto B B\n+test_rebase_same_head success noop same success noop-force diff --onto B... B\n+test_rebase_same_head success noop same success noop-force same --onto master... master\n+test_rebase_same_head success noop same success noop-force same --no-fork-point\n+test_rebase_same_head success work same success work same --fork-point master\n+test_rebase_same_head failure noop same success work diff --fork-point --onto B B\n+test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n+test_rebase_same_head success work same success work same --fork-point --onto master... master\n \n test_expect_success 'add work same to side' '\n \ttest_commit E\n '\n \n changes='our changes'\n-test_rebase_same_head success work same\n-test_rebase_same_head success noop same master\n-test_rebase_same_head success noop same --onto B B\n-test_rebase_same_head success noop same --onto B... B\n-test_rebase_same_head success noop same --onto master... master\n-test_rebase_same_head success noop same --no-fork-point\n-test_rebase_same_head success work same --fork-point master\n-test_rebase_same_head failure work same --fork-point --onto B B\n-test_rebase_same_head failure work same --fork-point --onto B... B\n-test_rebase_same_head success work same --fork-point --onto master... master\n+test_rebase_same_head success work same success work same\n+test_rebase_same_head success noop same success noop-force same master\n+test_rebase_same_head success noop same success noop-force diff --onto B B\n+test_rebase_same_head success noop same success noop-force diff --onto B... B\n+test_rebase_same_head success noop same success noop-force same --onto master... master\n+test_rebase_same_head success noop same success noop-force same --no-fork-point\n+test_rebase_same_head success work same success work same --fork-point master\n+test_rebase_same_head failure work same success work diff --fork-point --onto B B\n+test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n+test_rebase_same_head success work same success work same --fork-point --onto master... master\n \n test_expect_success 'add work same to upstream' '\n \tgit checkout master &&\n@@ -79,11 +104,11 @@ test_expect_success 'add work same to upstream' '\n '\n \n changes='our and their changes'\n-test_rebase_same_head success noop same --onto B B\n-test_rebase_same_head success noop same --onto B... B\n-test_rebase_same_head failure work same --onto master... master\n-test_rebase_same_head failure work same --fork-point --onto B B\n-test_rebase_same_head failure work same --fork-point --onto B... B\n-test_rebase_same_head failure work same --fork-point --onto master... master\n+test_rebase_same_head success noop same success noop-force diff --onto B B\n+test_rebase_same_head success noop same success noop-force diff --onto B... B\n+test_rebase_same_head failure work same success work diff --onto master... master\n+test_rebase_same_head failure work same success work diff --fork-point --onto B B\n+test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n+test_rebase_same_head failure work same success work diff --fork-point --onto master... master\n \n test_done\n-- \n2.23.0.248.g3a9dd8fb08\n\n"},{"id":"381101","messageId":"9bd34b4a404136c974b71975c7a3d809a874cc3d.1566724236.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1566724236.git.liu.denton@gmail.com","subject":"[PATCH v9 5/9] rebase: refactor can_fast_forward into goto tower","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-08-25T09:12:07Z","receivedAt":"2019-08-25T09:12:15Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Before, can_fast_forward was written with an if-else statement. However,\nin the future, we may be adding more termination cases which would lead\nto deeply nested if statements.\n\nRefactor to use a goto tower so that future cases can be easily\ninserted.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n builtin/rebase.c | 24 +++++++++++++++---------\n 1 file changed, 15 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 670096c065..22c4f1ff93 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1264,21 +1264,27 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n \t\t\t    struct object_id *merge_base)\n {\n \tstruct commit *head = lookup_commit(the_repository, head_oid);\n-\tstruct commit_list *merge_bases;\n-\tint res;\n+\tstruct commit_list *merge_bases = NULL;\n+\tint res = 0;\n \n \tif (!head)\n-\t\treturn 0;\n+\t\tgoto done;\n \n \tmerge_bases = get_merge_bases(onto, head);\n-\tif (merge_bases && !merge_bases->next) {\n-\t\toidcpy(merge_base, &merge_bases->item->object.oid);\n-\t\tres = oideq(merge_base, &onto->object.oid);\n-\t} else {\n+\tif (!merge_bases || merge_bases->next) {\n \t\toidcpy(merge_base, &null_oid);\n-\t\tres = 0;\n+\t\tgoto done;\n \t}\n-\tfree_commit_list(merge_bases);\n+\n+\toidcpy(merge_base, &merge_bases->item->object.oid);\n+\tif (!oideq(merge_base, &onto->object.oid))\n+\t\tgoto done;\n+\n+\tres = 1;\n+\n+done:\n+\tif (merge_bases)\n+\t\tfree_commit_list(merge_bases);\n \treturn res && is_linear_history(onto, head);\n }\n \n-- \n2.23.0.248.g3a9dd8fb08\n\n"},{"id":"381102","messageId":"becb924232d70520ba19c67efcd6b18a0a3c6b9d.1566724236.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1566724236.git.liu.denton@gmail.com","subject":"[PATCH v9 6/9] rebase: fast-forward --onto in more cases","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-08-25T09:12:10Z","receivedAt":"2019-08-25T09:12:15Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Before, when we had the following graph,\n\n\tA---B---C (master)\n\t     \\\n\t      D (side)\n\nrunning 'git rebase --onto master... master side' would result in D\nbeing always rebased, no matter what. However, the desired behavior is\nthat rebase should notice that this is fast-forwardable and do that\ninstead.\n\nAdd detection to `can_fast_forward` so that this case can be detected\nand a fast-forward will be performed. First of all, rewrite the function\nto use gotos which simplifies the logic. Next, since the\n\n\toptions.upstream &&\n\t!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)\n\nconditions were removed in `cmd_rebase`, we reintroduce a substitute in\n`can_fast_forward`. In particular, checking the merge bases of\n`upstream` and `head` fixes a failing case in t3416.\n\nThe abbreviated graph for t3416 is as follows:\n\n\t\t    F---G topic\n\t\t   /\n\t  A---B---C---D---E master\n\nand the failing command was\n\n\tgit rebase --onto master...topic F topic\n\nBefore, Git would see that there was one merge base (C), and the merge\nand onto were the same so it would incorrectly return 1, indicating that\nwe could fast-forward. This would cause the rebased graph to be 'ABCFG'\nwhen we were expecting 'ABCG'.\n\nWith the additional logic, we detect that upstream and head's merge base\nis F. Since onto isn't F, it means we're not rebasing the full set of\ncommits from master..topic. Since we're excluding some commits, a\nfast-forward cannot be performed and so we correctly return 0.\n\nAdd '-f' to test cases that failed as a result of this change because\nthey were not expecting a fast-forward so that a rebase is forced.\n\nHelped-by: Phillip Wood <phillip.wood@dunelm.org.uk>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n builtin/rebase.c               | 27 +++++++++++++++++++--------\n t/t3400-rebase.sh              |  2 +-\n t/t3404-rebase-interactive.sh  |  2 +-\n t/t3432-rebase-fast-forward.sh |  4 ++--\n 4 files changed, 23 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 22c4f1ff93..bdb1d922a4 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1260,8 +1260,8 @@ static int is_linear_history(struct commit *from, struct commit *to)\n \treturn 1;\n }\n \n-static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n-\t\t\t    struct object_id *merge_base)\n+static int can_fast_forward(struct commit *onto, struct commit *upstream,\n+\t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n {\n \tstruct commit *head = lookup_commit(the_repository, head_oid);\n \tstruct commit_list *merge_bases = NULL;\n@@ -1280,6 +1280,17 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n \tif (!oideq(merge_base, &onto->object.oid))\n \t\tgoto done;\n \n+\tif (!upstream)\n+\t\tgoto done;\n+\n+\tfree_commit_list(merge_bases);\n+\tmerge_bases = get_merge_bases(upstream, head);\n+\tif (!merge_bases || merge_bases->next)\n+\t\tgoto done;\n+\n+\tif (!oideq(&onto->object.oid, &merge_bases->item->object.oid))\n+\t\tgoto done;\n+\n \tres = 1;\n \n done:\n@@ -2028,13 +2039,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \t/*\n \t * Check if we are already based on onto with linear history,\n-\t * but this should be done only when upstream and onto are the same\n-\t * and if this is not an interactive rebase.\n+\t * in which case we could fast-forward without replacing the commits\n+\t * with new commits recreated by replaying their changes. This\n+\t * optimization must not be done if this is an interactive rebase.\n \t */\n-\tif (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&\n-\t    !is_interactive(&options) && !options.restrict_revision &&\n-\t    options.upstream &&\n-\t    !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {\n+\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head,\n+\t\t    &merge_base) &&\n+\t    !is_interactive(&options) && !options.restrict_revision) {\n \t\tint flag;\n \n \t\tif (!(options.flags & REBASE_FORCE)) {\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 80b23fd326..d7c724bea3 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -295,7 +295,7 @@ test_expect_success 'rebase --am and --show-current-patch' '\n \t\techo two >>init.t &&\n \t\tgit commit -a -m two &&\n \t\tgit tag two &&\n-\t\ttest_must_fail git rebase --onto init HEAD^ &&\n+\t\ttest_must_fail git rebase -f --onto init HEAD^ &&\n \t\tGIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&\n \t\tgrep \"show.*$(git rev-parse two)\" stderr\n \t)\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 461dd539ff..3cc9052f10 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1058,7 +1058,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int\n \tgit reset --hard &&\n \tgit checkout conflict-branch &&\n \tset_fake_editor &&\n-\ttest_must_fail git rebase --onto HEAD~2 HEAD~ &&\n+\ttest_must_fail git rebase -f --onto HEAD~2 HEAD~ &&\n \ttest_must_fail git rebase --edit-todo &&\n \tgit rebase --abort\n '\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 02b2516595..d9957e5f1e 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -106,9 +106,9 @@ test_expect_success 'add work same to upstream' '\n changes='our and their changes'\n test_rebase_same_head success noop same success noop-force diff --onto B B\n test_rebase_same_head success noop same success noop-force diff --onto B... B\n-test_rebase_same_head failure work same success work diff --onto master... master\n+test_rebase_same_head success noop same success work diff --onto master... master\n test_rebase_same_head failure work same success work diff --fork-point --onto B B\n test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n-test_rebase_same_head failure work same success work diff --fork-point --onto master... master\n+test_rebase_same_head success noop same success work diff --fork-point --onto master... master\n \n test_done\n-- \n2.23.0.248.g3a9dd8fb08\n\n"},{"id":"381103","messageId":"4dab5847cbb7d40efc02cbd89203df6ff192a5b1.1566724236.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1566724236.git.liu.denton@gmail.com","subject":"[PATCH v9 7/9] rebase: fast-forward --fork-point in more cases","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-08-25T09:12:12Z","receivedAt":"2019-08-25T09:12:17Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Before, when we rebased with a --fork-point invocation where the\nfork-point wasn't empty, we would be setting options.restrict_revision.\nThe fast-forward logic would automatically declare that the rebase was\nnot fast-forwardable if it was set. However, this was painting with a\nvery broad brush.\n\nRefine the logic so that we can fast-forward in the case where the\nrestricted revision is equal to the merge base, since we stop rebasing\nat the merge base anyway.\n\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n builtin/rebase.c               | 10 +++++++---\n t/t3432-rebase-fast-forward.sh | 20 ++++++++++----------\n 2 files changed, 17 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex bdb1d922a4..b78bd92002 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1261,6 +1261,7 @@ static int is_linear_history(struct commit *from, struct commit *to)\n }\n \n static int can_fast_forward(struct commit *onto, struct commit *upstream,\n+\t\t\t    struct commit *restrict_revision,\n \t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n {\n \tstruct commit *head = lookup_commit(the_repository, head_oid);\n@@ -1280,6 +1281,9 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,\n \tif (!oideq(merge_base, &onto->object.oid))\n \t\tgoto done;\n \n+\tif (restrict_revision && !oideq(&restrict_revision->object.oid, merge_base))\n+\t\tgoto done;\n+\n \tif (!upstream)\n \t\tgoto done;\n \n@@ -2043,9 +2047,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t * with new commits recreated by replaying their changes. This\n \t * optimization must not be done if this is an interactive rebase.\n \t */\n-\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head,\n-\t\t    &merge_base) &&\n-\t    !is_interactive(&options) && !options.restrict_revision) {\n+\tif (can_fast_forward(options.onto, options.upstream, options.restrict_revision,\n+\t\t    &options.orig_head, &merge_base) &&\n+\t    !is_interactive(&options)) {\n \t\tint flag;\n \n \t\tif (!(options.flags & REBASE_FORCE)) {\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex d9957e5f1e..fbedfe7b4a 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -70,32 +70,32 @@ test_rebase_same_head_ () {\n }\n \n changes='no changes'\n-test_rebase_same_head success work same success work same\n+test_rebase_same_head success noop same success work same\n test_rebase_same_head success noop same success noop-force same master\n test_rebase_same_head success noop same success noop-force diff --onto B B\n test_rebase_same_head success noop same success noop-force diff --onto B... B\n test_rebase_same_head success noop same success noop-force same --onto master... master\n test_rebase_same_head success noop same success noop-force same --no-fork-point\n-test_rebase_same_head success work same success work same --fork-point master\n-test_rebase_same_head failure noop same success work diff --fork-point --onto B B\n-test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n-test_rebase_same_head success work same success work same --fork-point --onto master... master\n+test_rebase_same_head success noop same success work same --fork-point master\n+test_rebase_same_head success noop same success work diff --fork-point --onto B B\n+test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n+test_rebase_same_head success noop same success work same --fork-point --onto master... master\n \n test_expect_success 'add work same to side' '\n \ttest_commit E\n '\n \n changes='our changes'\n-test_rebase_same_head success work same success work same\n+test_rebase_same_head success noop same success work same\n test_rebase_same_head success noop same success noop-force same master\n test_rebase_same_head success noop same success noop-force diff --onto B B\n test_rebase_same_head success noop same success noop-force diff --onto B... B\n test_rebase_same_head success noop same success noop-force same --onto master... master\n test_rebase_same_head success noop same success noop-force same --no-fork-point\n-test_rebase_same_head success work same success work same --fork-point master\n-test_rebase_same_head failure work same success work diff --fork-point --onto B B\n-test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n-test_rebase_same_head success work same success work same --fork-point --onto master... master\n+test_rebase_same_head success noop same success work same --fork-point master\n+test_rebase_same_head success noop same success work diff --fork-point --onto B B\n+test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n+test_rebase_same_head success noop same success work same --fork-point --onto master... master\n \n test_expect_success 'add work same to upstream' '\n \tgit checkout master &&\n-- \n2.23.0.248.g3a9dd8fb08\n\n"},{"id":"381104","messageId":"4699a909937d3a01ad89f1fb37bf7cb4916c761e.1566724236.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1566724236.git.liu.denton@gmail.com","subject":"[PATCH v9 8/9] rebase tests: test linear branch topology","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-08-25T09:12:15Z","receivedAt":"2019-08-25T09:12:19Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nAdd tests rebasing a linear branch topology to linear rebase tests\nadded in 2aad7cace2 (\"add simple tests of consistency across rebase\ntypes\", 2013-06-06).\n\nThese tests are duplicates of two surrounding tests that do the same\nwith tags pointing to the same objects. Right now there's no change in\nbehavior being introduced, but as we'll see in a subsequent change\nrebase can have different behaviors when working implicitly with\nremote tracking branches.\n\nWhile I'm at it add a --fork-point test, strictly speaking this is\nredundant to the existing '' test, as no argument to rebase implies\n--fork-point. But now it's easier to grep for tests that explicitly\nstress --fork-point.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3421-rebase-topology-linear.sh | 29 +++++++++++++++++++++++++++++\n 1 file changed, 29 insertions(+)\n\ndiff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\nindex 7274dca40b..b847064f91 100755\n--- a/t/t3421-rebase-topology-linear.sh\n+++ b/t/t3421-rebase-topology-linear.sh\n@@ -31,6 +31,16 @@ test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n \n+test_expect_success 'setup branches and remote tracking' '\n+\tgit tag -l >tags &&\n+\tfor tag in $(cat tags)\n+\tdo\n+\t\tgit branch branch-$tag $tag || return 1\n+\tdone &&\n+\tgit remote add origin \"file://$PWD\" &&\n+\tgit fetch origin\n+'\n+\n test_run_rebase () {\n \tresult=$1\n \tshift\n@@ -57,10 +67,28 @@ test_run_rebase () {\n \t\"\n }\n test_run_rebase success ''\n+test_run_rebase success --fork-point\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase failure -p\n \n+test_run_rebase () {\n+\tresult=$1\n+\tshift\n+\ttest_expect_$result \"rebase $* -f rewrites even if remote upstream is an ancestor\" \"\n+\t\treset_rebase &&\n+\t\tgit rebase $* -f branch-b branch-e &&\n+\t\t! test_cmp_rev branch-e origin/branch-e &&\n+\t\ttest_cmp_rev branch-b HEAD~2 &&\n+\t\ttest_linear_range 'd e' branch-b..\n+\t\"\n+}\n+test_run_rebase success ''\n+test_run_rebase success --fork-point\n+test_run_rebase success -m\n+test_run_rebase success -i\n+test_have_prereq !REBASE_P || test_run_rebase success -p\n+\n test_run_rebase () {\n \tresult=$1\n \tshift\n@@ -71,6 +99,7 @@ test_run_rebase () {\n \t\"\n }\n test_run_rebase success ''\n+test_run_rebase success --fork-point\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n-- \n2.23.0.248.g3a9dd8fb08\n\n"},{"id":"381105","messageId":"6927aba6176fb1b6f30c63e7d8c096861b2f0141.1566724236.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1566724236.git.liu.denton@gmail.com","subject":"[PATCH v9 9/9] rebase: teach rebase --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-08-25T09:12:17Z","receivedAt":"2019-08-25T09:12:22Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"A common scenario is if a user is working on a topic branch and they\nwish to make some changes to intermediate commits or autosquash, they\nwould run something such as\n\n\tgit rebase -i --onto master... master\n\nin order to preserve the merge base. This is useful when contributing a\npatch series to the Git mailing list, one often starts on top of the\ncurrent 'master'. However, while developing the patches, 'master' is\nalso developed further and it is sometimes not the best idea to keep\nrebasing on top of 'master', but to keep the base commit as-is.\n\nAlternatively, a user wishing to test individual commits in a topic\nbranch without changing anything may run\n\n\tgit rebase -x ./test.sh master... master\n\nSince rebasing onto the merge base of the branch and the upstream is\nsuch a common case, introduce the --keep-base option as a shortcut.\n\nThis allows us to rewrite the above as\n\n\tgit rebase -i --keep-base master\n\nand\n\n\tgit rebase -x ./test.sh --keep-base master\n\nrespectively.\n\nAdd tests to ensure --keep-base works correctly in the normal case and\nfails when there are multiple merge bases, both in regular and\ninteractive mode. Also, test to make sure conflicting options cause\nrebase to fail. While we're adding test cases, add a missing\nset_fake_editor call to 'rebase -i --onto master...side'.\n\nWhile we're documenting the --keep-base option, change an instance of\n\"merge-base\" to \"merge base\", which is the consistent spelling.\n\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nHelped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n Documentation/git-rebase.txt           | 30 ++++++++++++--\n builtin/rebase.c                       | 32 ++++++++++++---\n contrib/completion/git-completion.bash |  2 +-\n t/t3416-rebase-onto-threedots.sh       | 57 ++++++++++++++++++++++++++\n t/t3431-rebase-fork-point.sh           |  4 ++\n t/t3432-rebase-fast-forward.sh         | 11 +++++\n 6 files changed, 126 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 6156609cf7..3146c1592d 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip\n SYNOPSIS\n --------\n [verse]\n-'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n-\t[<upstream> [<branch>]]\n+'git rebase' [-i | --interactive] [<options>] [--exec <cmd>]\n+\t[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\n 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n \t--root [<branch>]\n 'git rebase' (--continue | --skip | --abort | --quit | --edit-todo | --show-current-patch)\n@@ -217,6 +217,24 @@ As a special case, you may use \"A\\...B\" as a shortcut for the\n merge base of A and B if there is exactly one merge base. You can\n leave out at most one of A and B, in which case it defaults to HEAD.\n \n+--keep-base::\n+\tSet the starting point at which to create the new commits to the\n+\tmerge base of <upstream> <branch>. Running\n+\t'git rebase --keep-base <upstream> <branch>' is equivalent to\n+\trunning 'git rebase --onto <upstream>... <upstream>'.\n++\n+This option is useful in the case where one is developing a feature on\n+top of an upstream branch. While the feature is being worked on, the\n+upstream branch may advance and it may not be the best idea to keep\n+rebasing on top of the upstream but to keep the base commit as-is.\n++\n+Although both this option and --fork-point find the merge base between\n+<upstream> and <branch>, this option uses the merge base as the _starting\n+point_ on which new commits will be created, whereas --fork-point uses\n+the merge base to determine the _set of commits_ which will be rebased.\n++\n+See also INCOMPATIBLE OPTIONS below.\n+\n <upstream>::\n \tUpstream branch to compare against.  May be any valid commit,\n \tnot just an existing branch name. Defaults to the configured\n@@ -369,6 +387,10 @@ ends up being empty, the <upstream> will be used as a fallback.\n +\n If either <upstream> or --root is given on the command line, then the\n default is `--no-fork-point`, otherwise the default is `--fork-point`.\n++\n+If your branch was based on <upstream> but <upstream> was rewound and\n+your branch contains commits which were dropped, this option can be used\n+with `--keep-base` in order to drop those commits from your branch.\n \n --ignore-whitespace::\n --whitespace=<option>::\n@@ -545,6 +567,8 @@ In addition, the following pairs of options are incompatible:\n  * --preserve-merges and --rebase-merges\n  * --rebase-merges and --strategy\n  * --rebase-merges and --strategy-option\n+ * --keep-base and --onto\n+ * --keep-base and --root\n \n BEHAVIORAL DIFFERENCES\n -----------------------\n@@ -870,7 +894,7 @@ NOTE: While an \"easy case recovery\" sometimes appears to be successful\n       --interactive` will be **resurrected**!\n \n The idea is to manually tell 'git rebase' \"where the old 'subsystem'\n-ended and your 'topic' began\", that is, what the old merge-base\n+ended and your 'topic' began\", that is, what the old merge base\n between them was.  You will have to find a way to name the last commit\n of the old 'subsystem', for example:\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex b78bd92002..1a49060973 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -29,8 +29,8 @@\n #include \"rebase-interactive.h\"\n \n static char const * const builtin_rebase_usage[] = {\n-\tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n-\t\t\"[<upstream>] [<branch>]\"),\n+\tN_(\"git rebase [-i] [options] [--exec <cmd>] \"\n+\t\t\"[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\"),\n \tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n \t\t\"--root [<branch>]\"),\n \tN_(\"git rebase --continue | --abort | --skip | --edit-todo\"),\n@@ -1397,6 +1397,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tstruct rebase_options options = REBASE_OPTIONS_INIT;\n \tconst char *branch_name;\n \tint ret, flags, total_argc, in_progress = 0;\n+\tint keep_base = 0;\n \tint ok_to_skip_pre_rebase = 0;\n \tstruct strbuf msg = STRBUF_INIT;\n \tstruct strbuf revisions = STRBUF_INIT;\n@@ -1415,6 +1416,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"rebase onto given branch instead of upstream\")),\n+\t\tOPT_BOOL(0, \"keep-base\", &keep_base,\n+\t\t\t N_(\"use the merge-base of upstream and branch as the current base\")),\n \t\tOPT_BOOL(0, \"no-verify\", &ok_to_skip_pre_rebase,\n \t\t\t N_(\"allow pre-rebase hook to run\")),\n \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n@@ -1568,6 +1571,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\twarning(_(\"git rebase --preserve-merges is deprecated. \"\n \t\t\t  \"Use --rebase-merges instead.\"));\n \n+\tif (keep_base) {\n+\t\tif (options.onto_name)\n+\t\t\tdie(_(\"cannot combine '--keep-base' with '--onto'\"));\n+\t\tif (options.root)\n+\t\t\tdie(_(\"cannot combine '--keep-base' with '--root'\"));\n+\t}\n+\n \tif (action != ACTION_NONE && !in_progress)\n \t\tdie(_(\"No rebase in progress?\"));\n \tsetenv(GIT_REFLOG_ACTION_ENVIRONMENT, \"rebase\", 0);\n@@ -1903,12 +1913,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t}\n \n \t/* Make sure the branch to rebase onto is valid. */\n-\tif (!options.onto_name)\n+\tif (keep_base) {\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addstr(&buf, options.upstream_name);\n+\t\tstrbuf_addstr(&buf, \"...\");\n+\t\toptions.onto_name = xstrdup(buf.buf);\n+\t} else if (!options.onto_name)\n \t\toptions.onto_name = options.upstream_name;\n \tif (strstr(options.onto_name, \"...\")) {\n-\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0)\n-\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n-\t\t\t    options.onto_name);\n+\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0) {\n+\t\t\tif (keep_base)\n+\t\t\t\tdie(_(\"'%s': need exactly one merge base with branch\"),\n+\t\t\t\t    options.upstream_name);\n+\t\t\telse\n+\t\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n+\t\t\t\t    options.onto_name);\n+\t\t}\n \t\toptions.onto = lookup_commit_or_die(&merge_base,\n \t\t\t\t\t\t    options.onto_name);\n \t} else {\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex e087c4bf00..71e7159694 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2030,7 +2030,7 @@ _git_rebase ()\n \t\t\t--autosquash --no-autosquash\n \t\t\t--fork-point --no-fork-point\n \t\t\t--autostash --no-autostash\n-\t\t\t--verify --no-verify\n+\t\t\t--verify --no-verify --keep-base\n \t\t\t--keep-empty --root --force-rebase --no-ff\n \t\t\t--rerere-autoupdate\n \t\t\t--exec\ndiff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh\nindex ddf2f64853..9c2548423b 100755\n--- a/t/t3416-rebase-onto-threedots.sh\n+++ b/t/t3416-rebase-onto-threedots.sh\n@@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' '\n \tgit checkout side &&\n \tgit reset --hard K &&\n \n+\tset_fake_editor &&\n \ttest_must_fail git rebase -i --onto master...side J\n '\n \n+test_expect_success 'rebase --keep-base --onto incompatible' '\n+\ttest_must_fail git rebase --keep-base --onto master...\n+'\n+\n+test_expect_success 'rebase --keep-base --root incompatible' '\n+\ttest_must_fail git rebase --keep-base --root\n+'\n+\n+test_expect_success 'rebase --keep-base master from topic' '\n+\tgit reset --hard &&\n+\tgit checkout topic &&\n+\tgit reset --hard G &&\n+\n+\tgit rebase --keep-base master &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base master HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --keep-base master from side' '\n+\tgit reset --hard &&\n+\tgit checkout side &&\n+\tgit reset --hard K &&\n+\n+\ttest_must_fail git rebase --keep-base master\n+'\n+\n+test_expect_success 'rebase -i --keep-base master from topic' '\n+\tgit reset --hard &&\n+\tgit checkout topic &&\n+\tgit reset --hard G &&\n+\n+\tset_fake_editor &&\n+\tEXPECT_COUNT=2 git rebase -i --keep-base master &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base master HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase -i --keep-base master from side' '\n+\tgit reset --hard &&\n+\tgit checkout side &&\n+\tgit reset --hard K &&\n+\n+\tset_fake_editor &&\n+\ttest_must_fail git rebase -i --keep-base master\n+'\n+\n test_done\ndiff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\nindex 2d5c6e641e..78851b9a2a 100755\n--- a/t/t3431-rebase-fork-point.sh\n+++ b/t/t3431-rebase-fork-point.sh\n@@ -43,11 +43,15 @@ test_rebase () {\n \n test_rebase 'G F E D B A'\n test_rebase 'G F D B A' --onto D\n+test_rebase 'G F B A' --keep-base\n test_rebase 'G F C E D B A' --no-fork-point\n test_rebase 'G F C D B A' --no-fork-point --onto D\n+test_rebase 'G F C B A' --no-fork-point --keep-base\n test_rebase 'G F E D B A' --fork-point refs/heads/master\n test_rebase 'G F D B A' --fork-point --onto D refs/heads/master\n+test_rebase 'G F B A' --fork-point --keep-base refs/heads/master\n test_rebase 'G F C E D B A' refs/heads/master\n test_rebase 'G F C D B A' --onto D refs/heads/master\n+test_rebase 'G F C B A' --keep-base refs/heads/master\n \n test_done\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex fbedfe7b4a..2f9f82be10 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -75,11 +75,15 @@ test_rebase_same_head success noop same success noop-force same master\n test_rebase_same_head success noop same success noop-force diff --onto B B\n test_rebase_same_head success noop same success noop-force diff --onto B... B\n test_rebase_same_head success noop same success noop-force same --onto master... master\n+test_rebase_same_head success noop same success noop-force same --keep-base master\n+test_rebase_same_head success noop same success noop-force same --keep-base\n test_rebase_same_head success noop same success noop-force same --no-fork-point\n+test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point\n test_rebase_same_head success noop same success work same --fork-point master\n test_rebase_same_head success noop same success work diff --fork-point --onto B B\n test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n test_rebase_same_head success noop same success work same --fork-point --onto master... master\n+test_rebase_same_head success noop same success work same --keep-base --keep-base master\n \n test_expect_success 'add work same to side' '\n \ttest_commit E\n@@ -91,11 +95,15 @@ test_rebase_same_head success noop same success noop-force same master\n test_rebase_same_head success noop same success noop-force diff --onto B B\n test_rebase_same_head success noop same success noop-force diff --onto B... B\n test_rebase_same_head success noop same success noop-force same --onto master... master\n+test_rebase_same_head success noop same success noop-force same --keep-base master\n+test_rebase_same_head success noop same success noop-force same --keep-base\n test_rebase_same_head success noop same success noop-force same --no-fork-point\n+test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point\n test_rebase_same_head success noop same success work same --fork-point master\n test_rebase_same_head success noop same success work diff --fork-point --onto B B\n test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n test_rebase_same_head success noop same success work same --fork-point --onto master... master\n+test_rebase_same_head success noop same success work same --fork-point --keep-base master\n \n test_expect_success 'add work same to upstream' '\n \tgit checkout master &&\n@@ -107,8 +115,11 @@ changes='our and their changes'\n test_rebase_same_head success noop same success noop-force diff --onto B B\n test_rebase_same_head success noop same success noop-force diff --onto B... B\n test_rebase_same_head success noop same success work diff --onto master... master\n+test_rebase_same_head success noop same success work diff --keep-base master\n+test_rebase_same_head success noop same success work diff --keep-base\n test_rebase_same_head failure work same success work diff --fork-point --onto B B\n test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n test_rebase_same_head success noop same success work diff --fork-point --onto master... master\n+test_rebase_same_head success noop same success work diff --fork-point --keep-base master\n \n test_done\n-- \n2.23.0.248.g3a9dd8fb08\n\n"},{"id":"381112","messageId":"20190825130235.bsbknf7sky44kwt7@localhost.localdomain","threadId":"50810","inReplyTo":"9bd34b4a404136c974b71975c7a3d809a874cc3d.1566724236.git.liu.denton@gmail.com","subject":"Re: [PATCH v9 5/9] rebase: refactor can_fast_forward into goto tower","fromName":"Pratyush Yadav","fromEmail":"me@yadavpratyush.com","sentAt":"2019-08-25T13:17:02Z","receivedAt":"2019-08-25T13:17:10Z","isPatch":true,"sender":{"key":"me@yadavpratyush.com","avatar":"https://avatars.githubusercontent.com/u/8817931?v=4"},"body":"On 25/08/19 05:12AM, Denton Liu wrote:\n> Before, can_fast_forward was written with an if-else statement. However,\n> in the future, we may be adding more termination cases which would lead\n> to deeply nested if statements.\n> \n> Refactor to use a goto tower so that future cases can be easily\n> inserted.\n> \n> Signed-off-by: Denton Liu <liu.denton@gmail.com>\n> ---\n>  builtin/rebase.c | 24 +++++++++++++++---------\n>  1 file changed, 15 insertions(+), 9 deletions(-)\n> \n> diff --git a/builtin/rebase.c b/builtin/rebase.c\n> index 670096c065..22c4f1ff93 100644\n> --- a/builtin/rebase.c\n> +++ b/builtin/rebase.c\n> @@ -1264,21 +1264,27 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n>  \t\t\t    struct object_id *merge_base)\n>  {\n>  \tstruct commit *head = lookup_commit(the_repository, head_oid);\n> -\tstruct commit_list *merge_bases;\n> -\tint res;\n> +\tstruct commit_list *merge_bases = NULL;\n> +\tint res = 0;\n>  \n>  \tif (!head)\n> -\t\treturn 0;\n> +\t\tgoto done;\n>  \n>  \tmerge_bases = get_merge_bases(onto, head);\n> -\tif (merge_bases && !merge_bases->next) {\n> -\t\toidcpy(merge_base, &merge_bases->item->object.oid);\n> -\t\tres = oideq(merge_base, &onto->object.oid);\n> -\t} else {\n> +\tif (!merge_bases || merge_bases->next) {\n>  \t\toidcpy(merge_base, &null_oid);\n> -\t\tres = 0;\n> +\t\tgoto done;\n>  \t}\n> -\tfree_commit_list(merge_bases);\n> +\n> +\toidcpy(merge_base, &merge_bases->item->object.oid);\n> +\tif (!oideq(merge_base, &onto->object.oid))\n> +\t\tgoto done;\n> +\n> +\tres = 1;\n> +\n> +done:\n> +\tif (merge_bases)\n> +\t\tfree_commit_list(merge_bases);\n\nfree_commit_list() returns immediately when a NULL pointer is passed in, \nso I'm not sure if this check is really necessary. I think it is a \nreasonable assumption to make that free* functions work well with NULL \ninputs.\n\n>  \treturn res && is_linear_history(onto, head);\n>  }\n>  \n\nOut of curiosity, since you are going with a goto tower, why not do \nsomething like:\n\ndone_merge_bases:\nfree_commit_list(merge_bases);\ndone:\nreturn res && is_linear_history(onto, head);\n\nYou jump to done_merge_bases after you have initialized merge_bases, and \ndirectly to done before initializing it.\n\nI'm not advocating for either way, just curious if there is a specific \nreason to do it your way. Anyway, if you drop the if (merge_bases), then \nit doesn't really matter I suppose.\n\n-- \nRegards,\nPratyush Yadav\n"},{"id":"381131","messageId":"3ffbaead-27e5-1b1a-5ad1-1136697cea1d@iee.email","threadId":"50810","inReplyTo":"6927aba6176fb1b6f30c63e7d8c096861b2f0141.1566724236.git.liu.denton@gmail.com","subject":"Re: [PATCH v9 9/9] rebase: teach rebase --keep-base","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.email","sentAt":"2019-08-25T22:59:34Z","receivedAt":"2019-08-25T22:59:39Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"On 25/08/2019 10:12, Denton Liu wrote:\n> A common scenario is if a user is working on a topic branch and they\n> wish to make some changes to intermediate commits or autosquash, they\n> would run something such as\n>\n> \tgit rebase -i --onto master... master\n>\n> in order to preserve the merge base. This is useful when contributing a\n> patch series to the Git mailing list, one often starts on top of the\n> current 'master'.\nThis 'However' part doesn't fit the flow. Do you mean 'while initially \ndeveloping...', or is the choice of 'However' wrong?\n> However, while developing the patches, 'master' is\n> also developed further and it is sometimes not the best idea to keep\n> rebasing on top of 'master', but to keep the base commit as-is.\n>\nThis 'Alternatively' appears to be part of the first set of common \nscenarios. Maybe it's just 3 common scenarios, all justifying the same \nimprovement.\n> Alternatively, a user wishing to test individual commits in a topic\n> branch without changing anything may run\n>\n> \tgit rebase -x ./test.sh master... master\n>\n> Since rebasing onto the merge base of the branch and the upstream is\n> such a common case, introduce the --keep-base option as a shortcut.\n>\n> This allows us to rewrite the above as\n>\n> \tgit rebase -i --keep-base master\n>\n> and\n>\n> \tgit rebase -x ./test.sh --keep-base master\n>\n> respectively.\n[remainder snipped]\n[also Eric Sunshine dropped due to temporary sending difficulties]\n\nPhilip\n"},{"id":"381243","messageId":"xmqqtva3g1lb.fsf@gitster-ct.c.googlers.com","threadId":"50810","inReplyTo":"cover.1566724236.git.liu.denton@gmail.com","subject":"Re: [PATCH v9 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2019-08-26T19:37:20Z","receivedAt":"2019-08-26T19:37:27Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Denton Liu <liu.denton@gmail.com> writes:\n\n> Hi all, it's been a while but I guess now's a good time as any to\n> resurrect this topic. This is basically a resubmission of Ævar's WIP v8\n> but I fixed a couple of minor whitespace issues.\n>\n> In addition, I opted to drop patches 9-13 from v8 since I don't think I\n> can do a good enough job polishing them up and I don't really understand\n> the intricacies of this part of the rebase code. Hopefully, Ævar will\n> pick them up at a later time.\n\nThanks; let's queue this with an updated base (the tip of master) as\nwe are now in a new cycle.\n"},{"id":"381263","messageId":"20190826231714.GA13827@archbookpro.localdomain","threadId":"50810","inReplyTo":"20190825130235.bsbknf7sky44kwt7@localhost.localdomain","subject":"Re: [PATCH v9 5/9] rebase: refactor can_fast_forward into goto tower","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-08-26T23:17:14Z","receivedAt":"2019-08-26T23:17:19Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"On Sun, Aug 25, 2019 at 06:47:02PM +0530, Pratyush Yadav wrote:\n> On 25/08/19 05:12AM, Denton Liu wrote:\n> > Before, can_fast_forward was written with an if-else statement. However,\n> > in the future, we may be adding more termination cases which would lead\n> > to deeply nested if statements.\n> > \n> > Refactor to use a goto tower so that future cases can be easily\n> > inserted.\n> > \n> > Signed-off-by: Denton Liu <liu.denton@gmail.com>\n> > ---\n> >  builtin/rebase.c | 24 +++++++++++++++---------\n> >  1 file changed, 15 insertions(+), 9 deletions(-)\n> > \n> > diff --git a/builtin/rebase.c b/builtin/rebase.c\n> > index 670096c065..22c4f1ff93 100644\n> > --- a/builtin/rebase.c\n> > +++ b/builtin/rebase.c\n> > @@ -1264,21 +1264,27 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n> >  \t\t\t    struct object_id *merge_base)\n> >  {\n> >  \tstruct commit *head = lookup_commit(the_repository, head_oid);\n> > -\tstruct commit_list *merge_bases;\n> > -\tint res;\n> > +\tstruct commit_list *merge_bases = NULL;\n> > +\tint res = 0;\n> >  \n> >  \tif (!head)\n> > -\t\treturn 0;\n> > +\t\tgoto done;\n> >  \n> >  \tmerge_bases = get_merge_bases(onto, head);\n> > -\tif (merge_bases && !merge_bases->next) {\n> > -\t\toidcpy(merge_base, &merge_bases->item->object.oid);\n> > -\t\tres = oideq(merge_base, &onto->object.oid);\n> > -\t} else {\n> > +\tif (!merge_bases || merge_bases->next) {\n> >  \t\toidcpy(merge_base, &null_oid);\n> > -\t\tres = 0;\n> > +\t\tgoto done;\n> >  \t}\n> > -\tfree_commit_list(merge_bases);\n> > +\n> > +\toidcpy(merge_base, &merge_bases->item->object.oid);\n> > +\tif (!oideq(merge_base, &onto->object.oid))\n> > +\t\tgoto done;\n> > +\n> > +\tres = 1;\n> > +\n> > +done:\n> > +\tif (merge_bases)\n> > +\t\tfree_commit_list(merge_bases);\n> \n> free_commit_list() returns immediately when a NULL pointer is passed in, \n> so I'm not sure if this check is really necessary. I think it is a \n> reasonable assumption to make that free* functions work well with NULL \n> inputs.\n\nI didn't realise that free_commit_list() would freely accept a NULL\npointer without segfaulting. I'll remove the surrounding if.\n\n> \n> >  \treturn res && is_linear_history(onto, head);\n> >  }\n> >  \n> \n> Out of curiosity, since you are going with a goto tower, why not do \n> something like:\n> \n> done_merge_bases:\n> free_commit_list(merge_bases);\n> done:\n> return res && is_linear_history(onto, head);\n> \n> You jump to done_merge_bases after you have initialized merge_bases, and \n> directly to done before initializing it.\n\nI opted to do it this way since I figured it was less complexity to have\na common jump target at the end. Also, in case in the future we decided\nto add more logic before merge_bases was initialised, we wouldn't have\nto worry about figuring out which target to jump to.\n\n> \n> I'm not advocating for either way, just curious if there is a specific \n> reason to do it your way. Anyway, if you drop the if (merge_bases), then \n> it doesn't really matter I suppose.\n\nI'm pretty impartial as well. I'll drop the if and leave the rest as is\nunless someone else feels strongly about this.\n\n> \n> -- \n> Regards,\n> Pratyush Yadav\n"},{"id":"381308","messageId":"cover.1566884063.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1566724236.git.liu.denton@gmail.com","subject":"[PATCH v10 0/9] rebase: learn --keep-base and improvements on fast-forward behaviour","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-08-27T05:37:43Z","receivedAt":"2019-08-27T05:37:48Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Thanks for your suggestions, Pratyush and Philip. I've incorporated both\nof them into this reroll.\n\n\nChanges since v1:\n\n* Squashed old set into one patch\n* Fixed indentation style and dangling else\n* Added more documentation after discussion with Ævar\n\nChanges since v2:\n\n* Add testing for rebase --fork-point behaviour\n* Add testing for rebase fast-forward behaviour\n* Make rebase --onto fast-forward in more cases\n* Update documentation to include use-case\n\nChanges since v3:\n\n* Fix tests failing on bash 4.2\n* Fix typo in t3431 comment\n\nChanges since v4:\n\n* Make rebase --fork-point fast-forward in more cases\n\nChanges since v5:\n\n* Fix graph illustrations so that the \"branch off\" is visually in the\n  correct place\n* Refactor if-else in can_fast_forward into one-level-deep ifs to\n  increase clarity\n\nChanges since v6:\n\n* Remove redundant braces around if\n* Update comment around can_fast_forward call\n* Add completion for rebase\n\nChanges since v7:\n\n* Ævar sent in his WIP patchset\n\nChanges since v8:\n\n* Drop patches 9-13\n* Fix some minor whitespace issues from v7\n\nChanges since v9:\n\n* Remove unnecessary if-statement in patch 6/9\n* Update commit message for patch 9/9 for clarity according to Philip's\n  suggestions\n\n[1]: https://public-inbox.org/git/a1b4b74b9167e279dae4cd8c58fb28d8a714a66a.1553537656.git.gitgitgadget@gmail.com/\n\n\nDenton Liu (6):\n  t3431: add rebase --fork-point tests\n  t3432: test rebase fast-forward behavior\n  rebase: refactor can_fast_forward into goto tower\n  rebase: fast-forward --onto in more cases\n  rebase: fast-forward --fork-point in more cases\n  rebase: teach rebase --keep-base\n\nÆvar Arnfjörð Bjarmason (3):\n  t3432: distinguish \"noop-same\" v.s. \"work-same\" in \"same head\" tests\n  t3432: test for --no-ff's interaction with fast-forward\n  rebase tests: test linear branch topology\n\n Documentation/git-rebase.txt           |  30 +++++-\n builtin/rebase.c                       |  84 ++++++++++++-----\n contrib/completion/git-completion.bash |   2 +-\n t/t3400-rebase.sh                      |   2 +-\n t/t3404-rebase-interactive.sh          |   2 +-\n t/t3416-rebase-onto-threedots.sh       |  57 +++++++++++\n t/t3421-rebase-topology-linear.sh      |  29 ++++++\n t/t3431-rebase-fork-point.sh           |  57 +++++++++++\n t/t3432-rebase-fast-forward.sh         | 125 +++++++++++++++++++++++++\n 9 files changed, 360 insertions(+), 28 deletions(-)\n create mode 100755 t/t3431-rebase-fork-point.sh\n create mode 100755 t/t3432-rebase-fast-forward.sh\n\nRange-diff against v9:\n 1:  03f769d410 =  1:  03f769d410 t3431: add rebase --fork-point tests\n 2:  bc8998079d =  2:  bc8998079d t3432: test rebase fast-forward behavior\n 3:  5c08e2b81f =  3:  5c08e2b81f t3432: distinguish \"noop-same\" v.s. \"work-same\" in \"same head\" tests\n 4:  48b4e41a17 =  4:  48b4e41a17 t3432: test for --no-ff's interaction with fast-forward\n 5:  9bd34b4a40 !  5:  9acce7c911 rebase: refactor can_fast_forward into goto tower\n    @@ builtin/rebase.c: static int can_fast_forward(struct commit *onto, struct object\n     -\t\tres = 0;\n     +\t\tgoto done;\n      \t}\n    --\tfree_commit_list(merge_bases);\n     +\n     +\toidcpy(merge_base, &merge_bases->item->object.oid);\n     +\tif (!oideq(merge_base, &onto->object.oid))\n    @@ builtin/rebase.c: static int can_fast_forward(struct commit *onto, struct object\n     +\tres = 1;\n     +\n     +done:\n    -+\tif (merge_bases)\n    -+\t\tfree_commit_list(merge_bases);\n    + \tfree_commit_list(merge_bases);\n      \treturn res && is_linear_history(onto, head);\n      }\n    - \n 6:  becb924232 =  6:  3f208421d6 rebase: fast-forward --onto in more cases\n 7:  4dab5847cb =  7:  126c20a95d rebase: fast-forward --fork-point in more cases\n 8:  4699a90993 =  8:  bf6cc6a610 rebase tests: test linear branch topology\n 9:  6927aba617 !  9:  6bc7423ac1 rebase: teach rebase --keep-base\n    @@ Commit message\n     \n         in order to preserve the merge base. This is useful when contributing a\n         patch series to the Git mailing list, one often starts on top of the\n    -    current 'master'. However, while developing the patches, 'master' is\n    -    also developed further and it is sometimes not the best idea to keep\n    -    rebasing on top of 'master', but to keep the base commit as-is.\n    +    current 'master'. While developing the patches, 'master' is also\n    +    developed further and it is sometimes not the best idea to keep rebasing\n    +    on top of 'master', but to keep the base commit as-is.\n     \n    -    Alternatively, a user wishing to test individual commits in a topic\n    -    branch without changing anything may run\n    +    In addition to this, a user wishing to test individual commits in a\n    +    topic branch without changing anything may run\n     \n                 git rebase -x ./test.sh master... master\n     \n-- \n2.23.0.248.g3a9dd8fb08\n\n"},{"id":"381309","messageId":"03f769d410447fc2db3b6dd18cdcc5f6eb160f62.1566884063.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1566884063.git.liu.denton@gmail.com","subject":"[PATCH v10 1/9] t3431: add rebase --fork-point tests","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-08-27T05:37:46Z","receivedAt":"2019-08-27T05:37:51Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Signed-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3431-rebase-fork-point.sh | 53 ++++++++++++++++++++++++++++++++++++\n 1 file changed, 53 insertions(+)\n create mode 100755 t/t3431-rebase-fork-point.sh\n\ndiff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\nnew file mode 100755\nindex 0000000000..2d5c6e641e\n--- /dev/null\n+++ b/t/t3431-rebase-fork-point.sh\n@@ -0,0 +1,53 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2019 Denton Liu\n+#\n+\n+test_description='git rebase --fork-point test'\n+\n+. ./test-lib.sh\n+\n+# A---B---D---E    (master)\n+#      \\\n+#       C*---F---G (side)\n+#\n+# C was formerly part of master but master was rewound to remove C\n+#\n+test_expect_success setup '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\ttest_commit C &&\n+\tgit branch -t side &&\n+\tgit reset --hard HEAD^ &&\n+\ttest_commit D &&\n+\ttest_commit E &&\n+\tgit checkout side &&\n+\ttest_commit F &&\n+\ttest_commit G\n+'\n+\n+test_rebase () {\n+\texpected=\"$1\" &&\n+\tshift &&\n+\ttest_expect_success \"git rebase $*\" \"\n+\t\tgit checkout master &&\n+\t\tgit reset --hard E &&\n+\t\tgit checkout side &&\n+\t\tgit reset --hard G &&\n+\t\tgit rebase $* &&\n+\t\ttest_write_lines $expected >expect &&\n+\t\tgit log --pretty=%s >actual &&\n+\t\ttest_cmp expect actual\n+\t\"\n+}\n+\n+test_rebase 'G F E D B A'\n+test_rebase 'G F D B A' --onto D\n+test_rebase 'G F C E D B A' --no-fork-point\n+test_rebase 'G F C D B A' --no-fork-point --onto D\n+test_rebase 'G F E D B A' --fork-point refs/heads/master\n+test_rebase 'G F D B A' --fork-point --onto D refs/heads/master\n+test_rebase 'G F C E D B A' refs/heads/master\n+test_rebase 'G F C D B A' --onto D refs/heads/master\n+\n+test_done\n-- \n2.23.0.248.g3a9dd8fb08\n\n"},{"id":"381310","messageId":"bc8998079dfce14e88e3c6145740fda04d1c82fe.1566884063.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1566884063.git.liu.denton@gmail.com","subject":"[PATCH v10 2/9] t3432: test rebase fast-forward behavior","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-08-27T05:37:49Z","receivedAt":"2019-08-27T05:37:53Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"When rebase is run on a branch that can be fast-forwarded, this should\nautomatically be done. Create test to ensure this behavior happens.\n\nThere are some cases that currently don't pass. The first case is where\na feature and master have diverged, running\n\"git rebase master... master\" causes a full rebase to happen even though\na fast-forward should happen.\n\nThe second case is when we are doing \"git rebase --fork-point\" and a\nfork-point commit is found. Once again, a full rebase happens even\nthough a fast-forward should happen.\n\nMark these cases as failure so we can fix it later.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3432-rebase-fast-forward.sh | 72 ++++++++++++++++++++++++++++++++++\n 1 file changed, 72 insertions(+)\n create mode 100755 t/t3432-rebase-fast-forward.sh\n\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nnew file mode 100755\nindex 0000000000..f49af274e0\n--- /dev/null\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -0,0 +1,72 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2019 Denton Liu\n+#\n+\n+test_description='ensure rebase fast-forwards commits when possible'\n+\n+. ./test-lib.sh\n+\n+test_expect_success setup '\n+\ttest_commit A &&\n+\ttest_commit B &&\n+\ttest_commit C &&\n+\ttest_commit D &&\n+\tgit checkout -t -b side\n+'\n+\n+test_rebase_same_head () {\n+\tstatus=\"$1\" &&\n+\tshift &&\n+\ttest_expect_$status \"git rebase $* with $changes is no-op\" \"\n+\t\toldhead=\\$(git rev-parse HEAD) &&\n+\t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n+\t\tgit rebase $* &&\n+\t\tnewhead=\\$(git rev-parse HEAD) &&\n+\t\ttest_cmp_rev \\$oldhead \\$newhead\n+\t\"\n+}\n+\n+changes='no changes'\n+test_rebase_same_head success\n+test_rebase_same_head success master\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --no-fork-point\n+test_rebase_same_head success --fork-point master\n+test_rebase_same_head failure --fork-point --onto B B\n+test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head success --fork-point --onto master... master\n+\n+test_expect_success 'add work to side' '\n+\ttest_commit E\n+'\n+\n+changes='our changes'\n+test_rebase_same_head success\n+test_rebase_same_head success master\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head success --onto master... master\n+test_rebase_same_head success --no-fork-point\n+test_rebase_same_head success --fork-point master\n+test_rebase_same_head failure --fork-point --onto B B\n+test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head success --fork-point --onto master... master\n+\n+test_expect_success 'add work to upstream' '\n+\tgit checkout master &&\n+\ttest_commit F &&\n+\tgit checkout side\n+'\n+\n+changes='our and their changes'\n+test_rebase_same_head success --onto B B\n+test_rebase_same_head success --onto B... B\n+test_rebase_same_head failure --onto master... master\n+test_rebase_same_head failure --fork-point --onto B B\n+test_rebase_same_head failure --fork-point --onto B... B\n+test_rebase_same_head failure --fork-point --onto master... master\n+\n+test_done\n-- \n2.23.0.248.g3a9dd8fb08\n\n"},{"id":"381311","messageId":"5c08e2b81fd65c5d4bcef1fb908987364143d181.1566884063.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1566884063.git.liu.denton@gmail.com","subject":"[PATCH v10 3/9] t3432: distinguish \"noop-same\" v.s. \"work-same\" in \"same head\" tests","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-08-27T05:37:51Z","receivedAt":"2019-08-27T05:37:56Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nChange \"same head\" introduced in the preceding commit to check whether\nthe rebase.c code lands in the can_fast_forward() case in, and thus\nprints out an \"is up to date\" and aborts early.\n\nIn some of these cases we make it past that and to \"rewinding head\",\nthen do a rebase, only to find out there's nothing to change so HEAD\nstays at the same OID.\n\nThese tests presumed these two cases were the same thing. In terms of\nwhere HEAD ends up they are, but we're not only interested in rebase\nsemantics, but also whether or not we're needlessly doing work when we\ncould avoid it entirely.\n\nI'm adding \"same\" and \"diff\" here because I'll follow-up and add\n--no-ff tests, where some of those will be \"diff\"-erent, so add the\n\"diff\" code already.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3432-rebase-fast-forward.sh | 79 +++++++++++++++++++++-------------\n 1 file changed, 48 insertions(+), 31 deletions(-)\n\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex f49af274e0..d9f20fa07c 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -18,55 +18,72 @@ test_expect_success setup '\n test_rebase_same_head () {\n \tstatus=\"$1\" &&\n \tshift &&\n-\ttest_expect_$status \"git rebase $* with $changes is no-op\" \"\n+\twhat=\"$1\" &&\n+\tshift &&\n+\tcmp=\"$1\" &&\n+\tshift &&\n+\ttest_expect_$status \"git rebase $* with $changes is $what\" \"\n \t\toldhead=\\$(git rev-parse HEAD) &&\n \t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n-\t\tgit rebase $* &&\n+\t\tgit rebase $* >stdout &&\n+\t\tif test $what = work\n+\t\tthen\n+\t\t\ttest_i18ngrep 'rewinding head' stdout\n+\t\telif test $what = noop\n+\t\tthen\n+\t\t\ttest_i18ngrep 'is up to date' stdout\n+\t\tfi &&\n \t\tnewhead=\\$(git rev-parse HEAD) &&\n-\t\ttest_cmp_rev \\$oldhead \\$newhead\n+\t\tif test $cmp = same\n+\t\tthen\n+\t\t\ttest_cmp_rev \\$oldhead \\$newhead\n+\t\telif test $cmp = diff\n+\t\tthen\n+\t\t\t! test_cmp_rev \\$oldhead \\$newhead\n+\t\tfi\n \t\"\n }\n \n changes='no changes'\n-test_rebase_same_head success\n-test_rebase_same_head success master\n-test_rebase_same_head success --onto B B\n-test_rebase_same_head success --onto B... B\n-test_rebase_same_head success --onto master... master\n-test_rebase_same_head success --no-fork-point\n-test_rebase_same_head success --fork-point master\n-test_rebase_same_head failure --fork-point --onto B B\n-test_rebase_same_head failure --fork-point --onto B... B\n-test_rebase_same_head success --fork-point --onto master... master\n+test_rebase_same_head success work same\n+test_rebase_same_head success noop same master\n+test_rebase_same_head success noop same --onto B B\n+test_rebase_same_head success noop same --onto B... B\n+test_rebase_same_head success noop same --onto master... master\n+test_rebase_same_head success noop same --no-fork-point\n+test_rebase_same_head success work same --fork-point master\n+test_rebase_same_head failure noop same --fork-point --onto B B\n+test_rebase_same_head failure work same --fork-point --onto B... B\n+test_rebase_same_head success work same --fork-point --onto master... master\n \n-test_expect_success 'add work to side' '\n+test_expect_success 'add work same to side' '\n \ttest_commit E\n '\n \n changes='our changes'\n-test_rebase_same_head success\n-test_rebase_same_head success master\n-test_rebase_same_head success --onto B B\n-test_rebase_same_head success --onto B... B\n-test_rebase_same_head success --onto master... master\n-test_rebase_same_head success --no-fork-point\n-test_rebase_same_head success --fork-point master\n-test_rebase_same_head failure --fork-point --onto B B\n-test_rebase_same_head failure --fork-point --onto B... B\n-test_rebase_same_head success --fork-point --onto master... master\n+test_rebase_same_head success work same\n+test_rebase_same_head success noop same master\n+test_rebase_same_head success noop same --onto B B\n+test_rebase_same_head success noop same --onto B... B\n+test_rebase_same_head success noop same --onto master... master\n+test_rebase_same_head success noop same --no-fork-point\n+test_rebase_same_head success work same --fork-point master\n+test_rebase_same_head failure work same --fork-point --onto B B\n+test_rebase_same_head failure work same --fork-point --onto B... B\n+test_rebase_same_head success work same --fork-point --onto master... master\n \n-test_expect_success 'add work to upstream' '\n+test_expect_success 'add work same to upstream' '\n \tgit checkout master &&\n \ttest_commit F &&\n \tgit checkout side\n '\n \n changes='our and their changes'\n-test_rebase_same_head success --onto B B\n-test_rebase_same_head success --onto B... B\n-test_rebase_same_head failure --onto master... master\n-test_rebase_same_head failure --fork-point --onto B B\n-test_rebase_same_head failure --fork-point --onto B... B\n-test_rebase_same_head failure --fork-point --onto master... master\n+test_rebase_same_head success noop same --onto B B\n+test_rebase_same_head success noop same --onto B... B\n+test_rebase_same_head failure work same --onto master... master\n+test_rebase_same_head failure work same --fork-point --onto B B\n+test_rebase_same_head failure work same --fork-point --onto B... B\n+test_rebase_same_head failure work same --fork-point --onto master... master\n \n test_done\n-- \n2.23.0.248.g3a9dd8fb08\n\n"},{"id":"381312","messageId":"48b4e41a17d98b2f3a4254ed37c76361c3abc9ba.1566884063.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1566884063.git.liu.denton@gmail.com","subject":"[PATCH v10 4/9] t3432: test for --no-ff's interaction with fast-forward","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-08-27T05:37:53Z","receivedAt":"2019-08-27T05:37:58Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nAdd more stress tests for the can_fast_forward() case in\nrebase.c. These tests are getting rather verbose, but now we can see\nunder --ff and --no-ff whether we skip work, or whether we're forced\nto run the rebase.\n\nThese tests aren't supposed to endorse the status quo, just test for\nwhat we're currently doing.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3432-rebase-fast-forward.sh | 83 ++++++++++++++++++++++------------\n 1 file changed, 54 insertions(+), 29 deletions(-)\n\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex d9f20fa07c..02b2516595 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -16,22 +16,47 @@ test_expect_success setup '\n '\n \n test_rebase_same_head () {\n+\tstatus_n=\"$1\" &&\n+\tshift &&\n+\twhat_n=\"$1\" &&\n+\tshift &&\n+\tcmp_n=\"$1\" &&\n+\tshift &&\n+\tstatus_f=\"$1\" &&\n+\tshift &&\n+\twhat_f=\"$1\" &&\n+\tshift &&\n+\tcmp_f=\"$1\" &&\n+\tshift &&\n+\ttest_rebase_same_head_ $status_n $what_n $cmp_n \"\" \"$*\" &&\n+\ttest_rebase_same_head_ $status_f $what_f $cmp_f \" --no-ff\" \"$*\"\n+}\n+\n+test_rebase_same_head_ () {\n \tstatus=\"$1\" &&\n \tshift &&\n \twhat=\"$1\" &&\n \tshift &&\n \tcmp=\"$1\" &&\n \tshift &&\n-\ttest_expect_$status \"git rebase $* with $changes is $what\" \"\n+\tflag=\"$1\"\n+\tshift &&\n+\ttest_expect_$status \"git rebase$flag $* with $changes is $what with $cmp HEAD\" \"\n \t\toldhead=\\$(git rev-parse HEAD) &&\n \t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n-\t\tgit rebase $* >stdout &&\n+\t\tgit rebase$flag $* >stdout &&\n \t\tif test $what = work\n \t\tthen\n+\t\t\t# Must check this case first, for 'is up to\n+\t\t\t# date, rebase forced[...]rewinding head' cases\n \t\t\ttest_i18ngrep 'rewinding head' stdout\n \t\telif test $what = noop\n \t\tthen\n-\t\t\ttest_i18ngrep 'is up to date' stdout\n+\t\t\ttest_i18ngrep 'is up to date' stdout &&\n+\t\t\t! test_i18ngrep 'rebase forced' stdout\n+\t\telif test $what = noop-force\n+\t\tthen\n+\t\t\ttest_i18ngrep 'is up to date, rebase forced' stdout\n \t\tfi &&\n \t\tnewhead=\\$(git rev-parse HEAD) &&\n \t\tif test $cmp = same\n@@ -45,32 +70,32 @@ test_rebase_same_head () {\n }\n \n changes='no changes'\n-test_rebase_same_head success work same\n-test_rebase_same_head success noop same master\n-test_rebase_same_head success noop same --onto B B\n-test_rebase_same_head success noop same --onto B... B\n-test_rebase_same_head success noop same --onto master... master\n-test_rebase_same_head success noop same --no-fork-point\n-test_rebase_same_head success work same --fork-point master\n-test_rebase_same_head failure noop same --fork-point --onto B B\n-test_rebase_same_head failure work same --fork-point --onto B... B\n-test_rebase_same_head success work same --fork-point --onto master... master\n+test_rebase_same_head success work same success work same\n+test_rebase_same_head success noop same success noop-force same master\n+test_rebase_same_head success noop same success noop-force diff --onto B B\n+test_rebase_same_head success noop same success noop-force diff --onto B... B\n+test_rebase_same_head success noop same success noop-force same --onto master... master\n+test_rebase_same_head success noop same success noop-force same --no-fork-point\n+test_rebase_same_head success work same success work same --fork-point master\n+test_rebase_same_head failure noop same success work diff --fork-point --onto B B\n+test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n+test_rebase_same_head success work same success work same --fork-point --onto master... master\n \n test_expect_success 'add work same to side' '\n \ttest_commit E\n '\n \n changes='our changes'\n-test_rebase_same_head success work same\n-test_rebase_same_head success noop same master\n-test_rebase_same_head success noop same --onto B B\n-test_rebase_same_head success noop same --onto B... B\n-test_rebase_same_head success noop same --onto master... master\n-test_rebase_same_head success noop same --no-fork-point\n-test_rebase_same_head success work same --fork-point master\n-test_rebase_same_head failure work same --fork-point --onto B B\n-test_rebase_same_head failure work same --fork-point --onto B... B\n-test_rebase_same_head success work same --fork-point --onto master... master\n+test_rebase_same_head success work same success work same\n+test_rebase_same_head success noop same success noop-force same master\n+test_rebase_same_head success noop same success noop-force diff --onto B B\n+test_rebase_same_head success noop same success noop-force diff --onto B... B\n+test_rebase_same_head success noop same success noop-force same --onto master... master\n+test_rebase_same_head success noop same success noop-force same --no-fork-point\n+test_rebase_same_head success work same success work same --fork-point master\n+test_rebase_same_head failure work same success work diff --fork-point --onto B B\n+test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n+test_rebase_same_head success work same success work same --fork-point --onto master... master\n \n test_expect_success 'add work same to upstream' '\n \tgit checkout master &&\n@@ -79,11 +104,11 @@ test_expect_success 'add work same to upstream' '\n '\n \n changes='our and their changes'\n-test_rebase_same_head success noop same --onto B B\n-test_rebase_same_head success noop same --onto B... B\n-test_rebase_same_head failure work same --onto master... master\n-test_rebase_same_head failure work same --fork-point --onto B B\n-test_rebase_same_head failure work same --fork-point --onto B... B\n-test_rebase_same_head failure work same --fork-point --onto master... master\n+test_rebase_same_head success noop same success noop-force diff --onto B B\n+test_rebase_same_head success noop same success noop-force diff --onto B... B\n+test_rebase_same_head failure work same success work diff --onto master... master\n+test_rebase_same_head failure work same success work diff --fork-point --onto B B\n+test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n+test_rebase_same_head failure work same success work diff --fork-point --onto master... master\n \n test_done\n-- \n2.23.0.248.g3a9dd8fb08\n\n"},{"id":"381313","messageId":"9acce7c911f11dbaa385f924ec8728160f990504.1566884063.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1566884063.git.liu.denton@gmail.com","subject":"[PATCH v10 5/9] rebase: refactor can_fast_forward into goto tower","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-08-27T05:37:56Z","receivedAt":"2019-08-27T05:38:01Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Before, can_fast_forward was written with an if-else statement. However,\nin the future, we may be adding more termination cases which would lead\nto deeply nested if statements.\n\nRefactor to use a goto tower so that future cases can be easily\ninserted.\n\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n builtin/rebase.c | 21 +++++++++++++--------\n 1 file changed, 13 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 670096c065..1ddad46126 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1264,20 +1264,25 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n \t\t\t    struct object_id *merge_base)\n {\n \tstruct commit *head = lookup_commit(the_repository, head_oid);\n-\tstruct commit_list *merge_bases;\n-\tint res;\n+\tstruct commit_list *merge_bases = NULL;\n+\tint res = 0;\n \n \tif (!head)\n-\t\treturn 0;\n+\t\tgoto done;\n \n \tmerge_bases = get_merge_bases(onto, head);\n-\tif (merge_bases && !merge_bases->next) {\n-\t\toidcpy(merge_base, &merge_bases->item->object.oid);\n-\t\tres = oideq(merge_base, &onto->object.oid);\n-\t} else {\n+\tif (!merge_bases || merge_bases->next) {\n \t\toidcpy(merge_base, &null_oid);\n-\t\tres = 0;\n+\t\tgoto done;\n \t}\n+\n+\toidcpy(merge_base, &merge_bases->item->object.oid);\n+\tif (!oideq(merge_base, &onto->object.oid))\n+\t\tgoto done;\n+\n+\tres = 1;\n+\n+done:\n \tfree_commit_list(merge_bases);\n \treturn res && is_linear_history(onto, head);\n }\n-- \n2.23.0.248.g3a9dd8fb08\n\n"},{"id":"381314","messageId":"3f208421d672215e1f65097f87e823edfca78886.1566884063.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1566884063.git.liu.denton@gmail.com","subject":"[PATCH v10 6/9] rebase: fast-forward --onto in more cases","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-08-27T05:37:59Z","receivedAt":"2019-08-27T05:38:04Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Before, when we had the following graph,\n\n\tA---B---C (master)\n\t     \\\n\t      D (side)\n\nrunning 'git rebase --onto master... master side' would result in D\nbeing always rebased, no matter what. However, the desired behavior is\nthat rebase should notice that this is fast-forwardable and do that\ninstead.\n\nAdd detection to `can_fast_forward` so that this case can be detected\nand a fast-forward will be performed. First of all, rewrite the function\nto use gotos which simplifies the logic. Next, since the\n\n\toptions.upstream &&\n\t!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)\n\nconditions were removed in `cmd_rebase`, we reintroduce a substitute in\n`can_fast_forward`. In particular, checking the merge bases of\n`upstream` and `head` fixes a failing case in t3416.\n\nThe abbreviated graph for t3416 is as follows:\n\n\t\t    F---G topic\n\t\t   /\n\t  A---B---C---D---E master\n\nand the failing command was\n\n\tgit rebase --onto master...topic F topic\n\nBefore, Git would see that there was one merge base (C), and the merge\nand onto were the same so it would incorrectly return 1, indicating that\nwe could fast-forward. This would cause the rebased graph to be 'ABCFG'\nwhen we were expecting 'ABCG'.\n\nWith the additional logic, we detect that upstream and head's merge base\nis F. Since onto isn't F, it means we're not rebasing the full set of\ncommits from master..topic. Since we're excluding some commits, a\nfast-forward cannot be performed and so we correctly return 0.\n\nAdd '-f' to test cases that failed as a result of this change because\nthey were not expecting a fast-forward so that a rebase is forced.\n\nHelped-by: Phillip Wood <phillip.wood@dunelm.org.uk>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n builtin/rebase.c               | 27 +++++++++++++++++++--------\n t/t3400-rebase.sh              |  2 +-\n t/t3404-rebase-interactive.sh  |  2 +-\n t/t3432-rebase-fast-forward.sh |  4 ++--\n 4 files changed, 23 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 1ddad46126..1e1406c8ba 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1260,8 +1260,8 @@ static int is_linear_history(struct commit *from, struct commit *to)\n \treturn 1;\n }\n \n-static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n-\t\t\t    struct object_id *merge_base)\n+static int can_fast_forward(struct commit *onto, struct commit *upstream,\n+\t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n {\n \tstruct commit *head = lookup_commit(the_repository, head_oid);\n \tstruct commit_list *merge_bases = NULL;\n@@ -1280,6 +1280,17 @@ static int can_fast_forward(struct commit *onto, struct object_id *head_oid,\n \tif (!oideq(merge_base, &onto->object.oid))\n \t\tgoto done;\n \n+\tif (!upstream)\n+\t\tgoto done;\n+\n+\tfree_commit_list(merge_bases);\n+\tmerge_bases = get_merge_bases(upstream, head);\n+\tif (!merge_bases || merge_bases->next)\n+\t\tgoto done;\n+\n+\tif (!oideq(&onto->object.oid, &merge_bases->item->object.oid))\n+\t\tgoto done;\n+\n \tres = 1;\n \n done:\n@@ -2027,13 +2038,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \n \t/*\n \t * Check if we are already based on onto with linear history,\n-\t * but this should be done only when upstream and onto are the same\n-\t * and if this is not an interactive rebase.\n+\t * in which case we could fast-forward without replacing the commits\n+\t * with new commits recreated by replaying their changes. This\n+\t * optimization must not be done if this is an interactive rebase.\n \t */\n-\tif (can_fast_forward(options.onto, &options.orig_head, &merge_base) &&\n-\t    !is_interactive(&options) && !options.restrict_revision &&\n-\t    options.upstream &&\n-\t    !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) {\n+\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head,\n+\t\t    &merge_base) &&\n+\t    !is_interactive(&options) && !options.restrict_revision) {\n \t\tint flag;\n \n \t\tif (!(options.flags & REBASE_FORCE)) {\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 80b23fd326..d7c724bea3 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -295,7 +295,7 @@ test_expect_success 'rebase --am and --show-current-patch' '\n \t\techo two >>init.t &&\n \t\tgit commit -a -m two &&\n \t\tgit tag two &&\n-\t\ttest_must_fail git rebase --onto init HEAD^ &&\n+\t\ttest_must_fail git rebase -f --onto init HEAD^ &&\n \t\tGIT_TRACE=1 git rebase --show-current-patch >/dev/null 2>stderr &&\n \t\tgrep \"show.*$(git rev-parse two)\" stderr\n \t)\ndiff --git a/t/t3404-rebase-interactive.sh b/t/t3404-rebase-interactive.sh\nindex 461dd539ff..3cc9052f10 100755\n--- a/t/t3404-rebase-interactive.sh\n+++ b/t/t3404-rebase-interactive.sh\n@@ -1058,7 +1058,7 @@ test_expect_success C_LOCALE_OUTPUT 'rebase --edit-todo does not work on non-int\n \tgit reset --hard &&\n \tgit checkout conflict-branch &&\n \tset_fake_editor &&\n-\ttest_must_fail git rebase --onto HEAD~2 HEAD~ &&\n+\ttest_must_fail git rebase -f --onto HEAD~2 HEAD~ &&\n \ttest_must_fail git rebase --edit-todo &&\n \tgit rebase --abort\n '\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex 02b2516595..d9957e5f1e 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -106,9 +106,9 @@ test_expect_success 'add work same to upstream' '\n changes='our and their changes'\n test_rebase_same_head success noop same success noop-force diff --onto B B\n test_rebase_same_head success noop same success noop-force diff --onto B... B\n-test_rebase_same_head failure work same success work diff --onto master... master\n+test_rebase_same_head success noop same success work diff --onto master... master\n test_rebase_same_head failure work same success work diff --fork-point --onto B B\n test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n-test_rebase_same_head failure work same success work diff --fork-point --onto master... master\n+test_rebase_same_head success noop same success work diff --fork-point --onto master... master\n \n test_done\n-- \n2.23.0.248.g3a9dd8fb08\n\n"},{"id":"381315","messageId":"126c20a95d1d7afa4e0cba384fbd08a4bea43614.1566884063.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1566884063.git.liu.denton@gmail.com","subject":"[PATCH v10 7/9] rebase: fast-forward --fork-point in more cases","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-08-27T05:38:01Z","receivedAt":"2019-08-27T05:38:06Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"Before, when we rebased with a --fork-point invocation where the\nfork-point wasn't empty, we would be setting options.restrict_revision.\nThe fast-forward logic would automatically declare that the rebase was\nnot fast-forwardable if it was set. However, this was painting with a\nvery broad brush.\n\nRefine the logic so that we can fast-forward in the case where the\nrestricted revision is equal to the merge base, since we stop rebasing\nat the merge base anyway.\n\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n builtin/rebase.c               | 10 +++++++---\n t/t3432-rebase-fast-forward.sh | 20 ++++++++++----------\n 2 files changed, 17 insertions(+), 13 deletions(-)\n\ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 1e1406c8ba..7ef9095e7c 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -1261,6 +1261,7 @@ static int is_linear_history(struct commit *from, struct commit *to)\n }\n \n static int can_fast_forward(struct commit *onto, struct commit *upstream,\n+\t\t\t    struct commit *restrict_revision,\n \t\t\t    struct object_id *head_oid, struct object_id *merge_base)\n {\n \tstruct commit *head = lookup_commit(the_repository, head_oid);\n@@ -1280,6 +1281,9 @@ static int can_fast_forward(struct commit *onto, struct commit *upstream,\n \tif (!oideq(merge_base, &onto->object.oid))\n \t\tgoto done;\n \n+\tif (restrict_revision && !oideq(&restrict_revision->object.oid, merge_base))\n+\t\tgoto done;\n+\n \tif (!upstream)\n \t\tgoto done;\n \n@@ -2042,9 +2046,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t * with new commits recreated by replaying their changes. This\n \t * optimization must not be done if this is an interactive rebase.\n \t */\n-\tif (can_fast_forward(options.onto, options.upstream, &options.orig_head,\n-\t\t    &merge_base) &&\n-\t    !is_interactive(&options) && !options.restrict_revision) {\n+\tif (can_fast_forward(options.onto, options.upstream, options.restrict_revision,\n+\t\t    &options.orig_head, &merge_base) &&\n+\t    !is_interactive(&options)) {\n \t\tint flag;\n \n \t\tif (!(options.flags & REBASE_FORCE)) {\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex d9957e5f1e..fbedfe7b4a 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -70,32 +70,32 @@ test_rebase_same_head_ () {\n }\n \n changes='no changes'\n-test_rebase_same_head success work same success work same\n+test_rebase_same_head success noop same success work same\n test_rebase_same_head success noop same success noop-force same master\n test_rebase_same_head success noop same success noop-force diff --onto B B\n test_rebase_same_head success noop same success noop-force diff --onto B... B\n test_rebase_same_head success noop same success noop-force same --onto master... master\n test_rebase_same_head success noop same success noop-force same --no-fork-point\n-test_rebase_same_head success work same success work same --fork-point master\n-test_rebase_same_head failure noop same success work diff --fork-point --onto B B\n-test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n-test_rebase_same_head success work same success work same --fork-point --onto master... master\n+test_rebase_same_head success noop same success work same --fork-point master\n+test_rebase_same_head success noop same success work diff --fork-point --onto B B\n+test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n+test_rebase_same_head success noop same success work same --fork-point --onto master... master\n \n test_expect_success 'add work same to side' '\n \ttest_commit E\n '\n \n changes='our changes'\n-test_rebase_same_head success work same success work same\n+test_rebase_same_head success noop same success work same\n test_rebase_same_head success noop same success noop-force same master\n test_rebase_same_head success noop same success noop-force diff --onto B B\n test_rebase_same_head success noop same success noop-force diff --onto B... B\n test_rebase_same_head success noop same success noop-force same --onto master... master\n test_rebase_same_head success noop same success noop-force same --no-fork-point\n-test_rebase_same_head success work same success work same --fork-point master\n-test_rebase_same_head failure work same success work diff --fork-point --onto B B\n-test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n-test_rebase_same_head success work same success work same --fork-point --onto master... master\n+test_rebase_same_head success noop same success work same --fork-point master\n+test_rebase_same_head success noop same success work diff --fork-point --onto B B\n+test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n+test_rebase_same_head success noop same success work same --fork-point --onto master... master\n \n test_expect_success 'add work same to upstream' '\n \tgit checkout master &&\n-- \n2.23.0.248.g3a9dd8fb08\n\n"},{"id":"381316","messageId":"bf6cc6a6102d7b872de590db99b1c5957d7a2dad.1566884063.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1566884063.git.liu.denton@gmail.com","subject":"[PATCH v10 8/9] rebase tests: test linear branch topology","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-08-27T05:38:04Z","receivedAt":"2019-08-27T05:38:08Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\nAdd tests rebasing a linear branch topology to linear rebase tests\nadded in 2aad7cace2 (\"add simple tests of consistency across rebase\ntypes\", 2013-06-06).\n\nThese tests are duplicates of two surrounding tests that do the same\nwith tags pointing to the same objects. Right now there's no change in\nbehavior being introduced, but as we'll see in a subsequent change\nrebase can have different behaviors when working implicitly with\nremote tracking branches.\n\nWhile I'm at it add a --fork-point test, strictly speaking this is\nredundant to the existing '' test, as no argument to rebase implies\n--fork-point. But now it's easier to grep for tests that explicitly\nstress --fork-point.\n\nSigned-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n t/t3421-rebase-topology-linear.sh | 29 +++++++++++++++++++++++++++++\n 1 file changed, 29 insertions(+)\n\ndiff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\nindex 7274dca40b..b847064f91 100755\n--- a/t/t3421-rebase-topology-linear.sh\n+++ b/t/t3421-rebase-topology-linear.sh\n@@ -31,6 +31,16 @@ test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n \n+test_expect_success 'setup branches and remote tracking' '\n+\tgit tag -l >tags &&\n+\tfor tag in $(cat tags)\n+\tdo\n+\t\tgit branch branch-$tag $tag || return 1\n+\tdone &&\n+\tgit remote add origin \"file://$PWD\" &&\n+\tgit fetch origin\n+'\n+\n test_run_rebase () {\n \tresult=$1\n \tshift\n@@ -57,10 +67,28 @@ test_run_rebase () {\n \t\"\n }\n test_run_rebase success ''\n+test_run_rebase success --fork-point\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase failure -p\n \n+test_run_rebase () {\n+\tresult=$1\n+\tshift\n+\ttest_expect_$result \"rebase $* -f rewrites even if remote upstream is an ancestor\" \"\n+\t\treset_rebase &&\n+\t\tgit rebase $* -f branch-b branch-e &&\n+\t\t! test_cmp_rev branch-e origin/branch-e &&\n+\t\ttest_cmp_rev branch-b HEAD~2 &&\n+\t\ttest_linear_range 'd e' branch-b..\n+\t\"\n+}\n+test_run_rebase success ''\n+test_run_rebase success --fork-point\n+test_run_rebase success -m\n+test_run_rebase success -i\n+test_have_prereq !REBASE_P || test_run_rebase success -p\n+\n test_run_rebase () {\n \tresult=$1\n \tshift\n@@ -71,6 +99,7 @@ test_run_rebase () {\n \t\"\n }\n test_run_rebase success ''\n+test_run_rebase success --fork-point\n test_run_rebase success -m\n test_run_rebase success -i\n test_have_prereq !REBASE_P || test_run_rebase success -p\n-- \n2.23.0.248.g3a9dd8fb08\n\n"},{"id":"381317","messageId":"6bc7423ac1e6f7d81de96cd4911ff652dc0e5a88.1566884063.git.liu.denton@gmail.com","threadId":"50810","inReplyTo":"cover.1566884063.git.liu.denton@gmail.com","subject":"[PATCH v10 9/9] rebase: teach rebase --keep-base","fromName":"Denton Liu","fromEmail":"liu.denton@gmail.com","sentAt":"2019-08-27T05:38:06Z","receivedAt":"2019-08-27T05:38:11Z","isPatch":true,"sender":{"key":"liu.denton@gmail.com","avatar":"https://avatars.githubusercontent.com/u/9620836?v=4"},"body":"A common scenario is if a user is working on a topic branch and they\nwish to make some changes to intermediate commits or autosquash, they\nwould run something such as\n\n\tgit rebase -i --onto master... master\n\nin order to preserve the merge base. This is useful when contributing a\npatch series to the Git mailing list, one often starts on top of the\ncurrent 'master'. While developing the patches, 'master' is also\ndeveloped further and it is sometimes not the best idea to keep rebasing\non top of 'master', but to keep the base commit as-is.\n\nIn addition to this, a user wishing to test individual commits in a\ntopic branch without changing anything may run\n\n\tgit rebase -x ./test.sh master... master\n\nSince rebasing onto the merge base of the branch and the upstream is\nsuch a common case, introduce the --keep-base option as a shortcut.\n\nThis allows us to rewrite the above as\n\n\tgit rebase -i --keep-base master\n\nand\n\n\tgit rebase -x ./test.sh --keep-base master\n\nrespectively.\n\nAdd tests to ensure --keep-base works correctly in the normal case and\nfails when there are multiple merge bases, both in regular and\ninteractive mode. Also, test to make sure conflicting options cause\nrebase to fail. While we're adding test cases, add a missing\nset_fake_editor call to 'rebase -i --onto master...side'.\n\nWhile we're documenting the --keep-base option, change an instance of\n\"merge-base\" to \"merge base\", which is the consistent spelling.\n\nHelped-by: Eric Sunshine <sunshine@sunshineco.com>\nHelped-by: Junio C Hamano <gitster@pobox.com>\nHelped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\nHelped-by: Johannes Schindelin <Johannes.Schindelin@gmx.de>\nSigned-off-by: Denton Liu <liu.denton@gmail.com>\n---\n Documentation/git-rebase.txt           | 30 ++++++++++++--\n builtin/rebase.c                       | 32 ++++++++++++---\n contrib/completion/git-completion.bash |  2 +-\n t/t3416-rebase-onto-threedots.sh       | 57 ++++++++++++++++++++++++++\n t/t3431-rebase-fork-point.sh           |  4 ++\n t/t3432-rebase-fast-forward.sh         | 11 +++++\n 6 files changed, 126 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-rebase.txt b/Documentation/git-rebase.txt\nindex 6156609cf7..3146c1592d 100644\n--- a/Documentation/git-rebase.txt\n+++ b/Documentation/git-rebase.txt\n@@ -8,8 +8,8 @@ git-rebase - Reapply commits on top of another base tip\n SYNOPSIS\n --------\n [verse]\n-'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n-\t[<upstream> [<branch>]]\n+'git rebase' [-i | --interactive] [<options>] [--exec <cmd>]\n+\t[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\n 'git rebase' [-i | --interactive] [<options>] [--exec <cmd>] [--onto <newbase>]\n \t--root [<branch>]\n 'git rebase' (--continue | --skip | --abort | --quit | --edit-todo | --show-current-patch)\n@@ -217,6 +217,24 @@ As a special case, you may use \"A\\...B\" as a shortcut for the\n merge base of A and B if there is exactly one merge base. You can\n leave out at most one of A and B, in which case it defaults to HEAD.\n \n+--keep-base::\n+\tSet the starting point at which to create the new commits to the\n+\tmerge base of <upstream> <branch>. Running\n+\t'git rebase --keep-base <upstream> <branch>' is equivalent to\n+\trunning 'git rebase --onto <upstream>... <upstream>'.\n++\n+This option is useful in the case where one is developing a feature on\n+top of an upstream branch. While the feature is being worked on, the\n+upstream branch may advance and it may not be the best idea to keep\n+rebasing on top of the upstream but to keep the base commit as-is.\n++\n+Although both this option and --fork-point find the merge base between\n+<upstream> and <branch>, this option uses the merge base as the _starting\n+point_ on which new commits will be created, whereas --fork-point uses\n+the merge base to determine the _set of commits_ which will be rebased.\n++\n+See also INCOMPATIBLE OPTIONS below.\n+\n <upstream>::\n \tUpstream branch to compare against.  May be any valid commit,\n \tnot just an existing branch name. Defaults to the configured\n@@ -369,6 +387,10 @@ ends up being empty, the <upstream> will be used as a fallback.\n +\n If either <upstream> or --root is given on the command line, then the\n default is `--no-fork-point`, otherwise the default is `--fork-point`.\n++\n+If your branch was based on <upstream> but <upstream> was rewound and\n+your branch contains commits which were dropped, this option can be used\n+with `--keep-base` in order to drop those commits from your branch.\n \n --ignore-whitespace::\n --whitespace=<option>::\n@@ -545,6 +567,8 @@ In addition, the following pairs of options are incompatible:\n  * --preserve-merges and --rebase-merges\n  * --rebase-merges and --strategy\n  * --rebase-merges and --strategy-option\n+ * --keep-base and --onto\n+ * --keep-base and --root\n \n BEHAVIORAL DIFFERENCES\n -----------------------\n@@ -870,7 +894,7 @@ NOTE: While an \"easy case recovery\" sometimes appears to be successful\n       --interactive` will be **resurrected**!\n \n The idea is to manually tell 'git rebase' \"where the old 'subsystem'\n-ended and your 'topic' began\", that is, what the old merge-base\n+ended and your 'topic' began\", that is, what the old merge base\n between them was.  You will have to find a way to name the last commit\n of the old 'subsystem', for example:\n \ndiff --git a/builtin/rebase.c b/builtin/rebase.c\nindex 7ef9095e7c..27d5c4e19d 100644\n--- a/builtin/rebase.c\n+++ b/builtin/rebase.c\n@@ -29,8 +29,8 @@\n #include \"rebase-interactive.h\"\n \n static char const * const builtin_rebase_usage[] = {\n-\tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n-\t\t\"[<upstream>] [<branch>]\"),\n+\tN_(\"git rebase [-i] [options] [--exec <cmd>] \"\n+\t\t\"[--onto <newbase> | --keep-base] [<upstream> [<branch>]]\"),\n \tN_(\"git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] \"\n \t\t\"--root [<branch>]\"),\n \tN_(\"git rebase --continue | --abort | --skip | --edit-todo\"),\n@@ -1396,6 +1396,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \tstruct rebase_options options = REBASE_OPTIONS_INIT;\n \tconst char *branch_name;\n \tint ret, flags, total_argc, in_progress = 0;\n+\tint keep_base = 0;\n \tint ok_to_skip_pre_rebase = 0;\n \tstruct strbuf msg = STRBUF_INIT;\n \tstruct strbuf revisions = STRBUF_INIT;\n@@ -1414,6 +1415,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\tOPT_STRING(0, \"onto\", &options.onto_name,\n \t\t\t   N_(\"revision\"),\n \t\t\t   N_(\"rebase onto given branch instead of upstream\")),\n+\t\tOPT_BOOL(0, \"keep-base\", &keep_base,\n+\t\t\t N_(\"use the merge-base of upstream and branch as the current base\")),\n \t\tOPT_BOOL(0, \"no-verify\", &ok_to_skip_pre_rebase,\n \t\t\t N_(\"allow pre-rebase hook to run\")),\n \t\tOPT_NEGBIT('q', \"quiet\", &options.flags,\n@@ -1567,6 +1570,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t\twarning(_(\"git rebase --preserve-merges is deprecated. \"\n \t\t\t  \"Use --rebase-merges instead.\"));\n \n+\tif (keep_base) {\n+\t\tif (options.onto_name)\n+\t\t\tdie(_(\"cannot combine '--keep-base' with '--onto'\"));\n+\t\tif (options.root)\n+\t\t\tdie(_(\"cannot combine '--keep-base' with '--root'\"));\n+\t}\n+\n \tif (action != ACTION_NONE && !in_progress)\n \t\tdie(_(\"No rebase in progress?\"));\n \tsetenv(GIT_REFLOG_ACTION_ENVIRONMENT, \"rebase\", 0);\n@@ -1902,12 +1912,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix)\n \t}\n \n \t/* Make sure the branch to rebase onto is valid. */\n-\tif (!options.onto_name)\n+\tif (keep_base) {\n+\t\tstrbuf_reset(&buf);\n+\t\tstrbuf_addstr(&buf, options.upstream_name);\n+\t\tstrbuf_addstr(&buf, \"...\");\n+\t\toptions.onto_name = xstrdup(buf.buf);\n+\t} else if (!options.onto_name)\n \t\toptions.onto_name = options.upstream_name;\n \tif (strstr(options.onto_name, \"...\")) {\n-\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0)\n-\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n-\t\t\t    options.onto_name);\n+\t\tif (get_oid_mb(options.onto_name, &merge_base) < 0) {\n+\t\t\tif (keep_base)\n+\t\t\t\tdie(_(\"'%s': need exactly one merge base with branch\"),\n+\t\t\t\t    options.upstream_name);\n+\t\t\telse\n+\t\t\t\tdie(_(\"'%s': need exactly one merge base\"),\n+\t\t\t\t    options.onto_name);\n+\t\t}\n \t\toptions.onto = lookup_commit_or_die(&merge_base,\n \t\t\t\t\t\t    options.onto_name);\n \t} else {\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex e087c4bf00..71e7159694 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -2030,7 +2030,7 @@ _git_rebase ()\n \t\t\t--autosquash --no-autosquash\n \t\t\t--fork-point --no-fork-point\n \t\t\t--autostash --no-autostash\n-\t\t\t--verify --no-verify\n+\t\t\t--verify --no-verify --keep-base\n \t\t\t--keep-empty --root --force-rebase --no-ff\n \t\t\t--rerere-autoupdate\n \t\t\t--exec\ndiff --git a/t/t3416-rebase-onto-threedots.sh b/t/t3416-rebase-onto-threedots.sh\nindex ddf2f64853..9c2548423b 100755\n--- a/t/t3416-rebase-onto-threedots.sh\n+++ b/t/t3416-rebase-onto-threedots.sh\n@@ -99,7 +99,64 @@ test_expect_success 'rebase -i --onto master...side' '\n \tgit checkout side &&\n \tgit reset --hard K &&\n \n+\tset_fake_editor &&\n \ttest_must_fail git rebase -i --onto master...side J\n '\n \n+test_expect_success 'rebase --keep-base --onto incompatible' '\n+\ttest_must_fail git rebase --keep-base --onto master...\n+'\n+\n+test_expect_success 'rebase --keep-base --root incompatible' '\n+\ttest_must_fail git rebase --keep-base --root\n+'\n+\n+test_expect_success 'rebase --keep-base master from topic' '\n+\tgit reset --hard &&\n+\tgit checkout topic &&\n+\tgit reset --hard G &&\n+\n+\tgit rebase --keep-base master &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base master HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase --keep-base master from side' '\n+\tgit reset --hard &&\n+\tgit checkout side &&\n+\tgit reset --hard K &&\n+\n+\ttest_must_fail git rebase --keep-base master\n+'\n+\n+test_expect_success 'rebase -i --keep-base master from topic' '\n+\tgit reset --hard &&\n+\tgit checkout topic &&\n+\tgit reset --hard G &&\n+\n+\tset_fake_editor &&\n+\tEXPECT_COUNT=2 git rebase -i --keep-base master &&\n+\tgit rev-parse C >base.expect &&\n+\tgit merge-base master HEAD >base.actual &&\n+\ttest_cmp base.expect base.actual &&\n+\n+\tgit rev-parse HEAD~2 >actual &&\n+\tgit rev-parse C^0 >expect &&\n+\ttest_cmp expect actual\n+'\n+\n+test_expect_success 'rebase -i --keep-base master from side' '\n+\tgit reset --hard &&\n+\tgit checkout side &&\n+\tgit reset --hard K &&\n+\n+\tset_fake_editor &&\n+\ttest_must_fail git rebase -i --keep-base master\n+'\n+\n test_done\ndiff --git a/t/t3431-rebase-fork-point.sh b/t/t3431-rebase-fork-point.sh\nindex 2d5c6e641e..78851b9a2a 100755\n--- a/t/t3431-rebase-fork-point.sh\n+++ b/t/t3431-rebase-fork-point.sh\n@@ -43,11 +43,15 @@ test_rebase () {\n \n test_rebase 'G F E D B A'\n test_rebase 'G F D B A' --onto D\n+test_rebase 'G F B A' --keep-base\n test_rebase 'G F C E D B A' --no-fork-point\n test_rebase 'G F C D B A' --no-fork-point --onto D\n+test_rebase 'G F C B A' --no-fork-point --keep-base\n test_rebase 'G F E D B A' --fork-point refs/heads/master\n test_rebase 'G F D B A' --fork-point --onto D refs/heads/master\n+test_rebase 'G F B A' --fork-point --keep-base refs/heads/master\n test_rebase 'G F C E D B A' refs/heads/master\n test_rebase 'G F C D B A' --onto D refs/heads/master\n+test_rebase 'G F C B A' --keep-base refs/heads/master\n \n test_done\ndiff --git a/t/t3432-rebase-fast-forward.sh b/t/t3432-rebase-fast-forward.sh\nindex fbedfe7b4a..2f9f82be10 100755\n--- a/t/t3432-rebase-fast-forward.sh\n+++ b/t/t3432-rebase-fast-forward.sh\n@@ -75,11 +75,15 @@ test_rebase_same_head success noop same success noop-force same master\n test_rebase_same_head success noop same success noop-force diff --onto B B\n test_rebase_same_head success noop same success noop-force diff --onto B... B\n test_rebase_same_head success noop same success noop-force same --onto master... master\n+test_rebase_same_head success noop same success noop-force same --keep-base master\n+test_rebase_same_head success noop same success noop-force same --keep-base\n test_rebase_same_head success noop same success noop-force same --no-fork-point\n+test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point\n test_rebase_same_head success noop same success work same --fork-point master\n test_rebase_same_head success noop same success work diff --fork-point --onto B B\n test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n test_rebase_same_head success noop same success work same --fork-point --onto master... master\n+test_rebase_same_head success noop same success work same --keep-base --keep-base master\n \n test_expect_success 'add work same to side' '\n \ttest_commit E\n@@ -91,11 +95,15 @@ test_rebase_same_head success noop same success noop-force same master\n test_rebase_same_head success noop same success noop-force diff --onto B B\n test_rebase_same_head success noop same success noop-force diff --onto B... B\n test_rebase_same_head success noop same success noop-force same --onto master... master\n+test_rebase_same_head success noop same success noop-force same --keep-base master\n+test_rebase_same_head success noop same success noop-force same --keep-base\n test_rebase_same_head success noop same success noop-force same --no-fork-point\n+test_rebase_same_head success noop same success noop-force same --keep-base --no-fork-point\n test_rebase_same_head success noop same success work same --fork-point master\n test_rebase_same_head success noop same success work diff --fork-point --onto B B\n test_rebase_same_head success noop same success work diff --fork-point --onto B... B\n test_rebase_same_head success noop same success work same --fork-point --onto master... master\n+test_rebase_same_head success noop same success work same --fork-point --keep-base master\n \n test_expect_success 'add work same to upstream' '\n \tgit checkout master &&\n@@ -107,8 +115,11 @@ changes='our and their changes'\n test_rebase_same_head success noop same success noop-force diff --onto B B\n test_rebase_same_head success noop same success noop-force diff --onto B... B\n test_rebase_same_head success noop same success work diff --onto master... master\n+test_rebase_same_head success noop same success work diff --keep-base master\n+test_rebase_same_head success noop same success work diff --keep-base\n test_rebase_same_head failure work same success work diff --fork-point --onto B B\n test_rebase_same_head failure work same success work diff --fork-point --onto B... B\n test_rebase_same_head success noop same success work diff --fork-point --onto master... master\n+test_rebase_same_head success noop same success work diff --fork-point --keep-base master\n \n test_done\n-- \n2.23.0.248.g3a9dd8fb08\n\n"},{"id":"381321","messageId":"20190827081104.GT20404@szeder.dev","threadId":"50810","inReplyTo":"48b4e41a17d98b2f3a4254ed37c76361c3abc9ba.1566884063.git.liu.denton@gmail.com","subject":"Re: [PATCH v10 4/9] t3432: test for --no-ff's interaction with fast-forward","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2019-08-27T08:11:04Z","receivedAt":"2019-08-27T08:11:12Z","isPatch":true,"sender":{"key":"szeder.dev@gmail.com","avatar":"https://avatars.githubusercontent.com/u/116324?v=4"},"body":"On Tue, Aug 27, 2019 at 01:37:53AM -0400, Denton Liu wrote:\n> From: Ævar Arnfjörð Bjarmason <avarab@gmail.com>\n\n> +\ttest_expect_$status \"git rebase$flag $* with $changes is $what with $cmp HEAD\" \"\n>  \t\toldhead=\\$(git rev-parse HEAD) &&\n>  \t\ttest_when_finished 'git reset --hard \\$oldhead' &&\n> -\t\tgit rebase $* >stdout &&\n> +\t\tgit rebase$flag $* >stdout &&\n>  \t\tif test $what = work\n>  \t\tthen\n> +\t\t\t# Must check this case first, for 'is up to\n> +\t\t\t# date, rebase forced[...]rewinding head' cases\n>  \t\t\ttest_i18ngrep 'rewinding head' stdout\n>  \t\telif test $what = noop\n>  \t\tthen\n> -\t\t\ttest_i18ngrep 'is up to date' stdout\n> +\t\t\ttest_i18ngrep 'is up to date' stdout &&\n> +\t\t\t! test_i18ngrep 'rebase forced' stdout\n\nThis must be written as 'test_i18ngrep ! ....'.  When running the test\nwith GIT_TEST_GETTEXT_POISON=true, then 'test_i18ngrep' is basically a\nnoop and always returns with success, the leading ! turns that into a\nfailure, which then fails the test.\n\n"}]}