{"thread":{"id":"56121","subject":"[PATCH 0/9] Handle pull option precedence","startedAt":"2021-07-17T15:41:52Z","lastAt":"2021-07-22T07:09:32Z","messageCount":59,"participants":["Elijah Newren via GitGitGadget","Alex Henrie via GitGitGadget","Felipe Contreras","Junio C Hamano","Elijah Newren","Matthias Baumgarten"],"isPatch":true,"patchVersion":1,"patchTotal":9},"messages":[{"id":"430421","messageId":"pull.1049.git.git.1626536507.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":null,"subject":"[PATCH 0/9] Handle pull option precedence","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-17T15:41:38Z","receivedAt":"2021-07-17T15:41:52Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Based on a recent list of rules for flag/option precedence for git-pull[1]\nfrom Junio (particularly focusing on rebase vs. merge vs. fast-forward),\nhere's an attempt to implement and document it. Given multiple recent\nsurprises from users about some of these behaviors[2][3] and a coworker just\nyesterday expressing some puzzlement with git-pull and rebase vs. merge, it\nseems like a good time to address some of these issues.\n\nSince the handling of conflicting options was holding up two of Alex's\npatches[4][5], and his patches fix some of the tests, I also include those\ntwo patches in my series, with a few small changes to the first (so I've\nkept him as author) and more substantial changes to the second (so I've\ngiven him an Initial-patch-by attribution).\n\nQuick overview:\n\n * Patches 1-2: new testcases (see the commit messages for the rules)\n * Patch 3: Alex's recent patch (abort if --ff-only but can't do so)\n * Patches 4-6: fix the precedence parts Alex didn't cover\n * Patch 7: Alex's other patch, abort if rebase vs. merge not specified\n * Patch 8: Compatibility of git-pull with merge-options.txt (think\n   rebasing)\n * Patch 9: Fix multiple heads handling too\n\n[1] https://lore.kernel.org/git/xmqqwnpqot4m.fsf@gitster.g/ [2]\nhttps://lore.kernel.org/git/CAL3xRKdOyVWvcLXK7zoXtFPiHBjgL24zi5hhg+3yjowwSUPgmg@mail.gmail.com/\n[3]\nhttps://lore.kernel.org/git/c62933fb-96b2-99f5-7169-372f486f6e39@aixigo.com/\n[4]\nhttps://lore.kernel.org/git/20210711012604.947321-1-alexhenrie24@gmail.com/\n[5]\nhttps://lore.kernel.org/git/20210627000855.530985-1-alexhenrie24@gmail.com/\n\nAlex Henrie (1):\n  pull: abort if --ff-only is given and fast-forwarding is impossible\n\nElijah Newren (8):\n  t7601: add relative precedence tests for merge and rebase\n    flags/options\n  t7601: add tests of interactions with multiple merge heads and config\n  pull: since --ff-only overrides, handle it first\n  pull: ensure --rebase overrides ability to ff\n  pull: make --rebase and --no-rebase override pull.ff=only\n  pull: abort by default when fast-forwarding is not possible\n  pull: update docs & code for option compatibility with rebasing\n  pull: fix handling of multiple heads\n\n Documentation/git-pull.txt      |  22 ++--\n Documentation/merge-options.txt |  25 ++++\n advice.c                        |   5 +\n advice.h                        |   1 +\n builtin/merge.c                 |   2 +-\n builtin/pull.c                  |  55 ++++++---\n t/t4013-diff-various.sh         |   2 +-\n t/t5520-pull.sh                 |  26 ++--\n t/t5521-pull-options.sh         |   4 +-\n t/t5524-pull-msg.sh             |   4 +-\n t/t5553-set-upstream.sh         |  14 +--\n t/t5604-clone-reference.sh      |   4 +-\n t/t6402-merge-rename.sh         |  18 +--\n t/t6409-merge-subtree.sh        |   6 +-\n t/t6417-merge-ours-theirs.sh    |  10 +-\n t/t7601-merge-pull-config.sh    | 212 +++++++++++++++++++++++++++++---\n t/t7603-merge-reduce-heads.sh   |   2 +-\n 17 files changed, 321 insertions(+), 91 deletions(-)\n\n\nbase-commit: 75ae10bc75336db031ee58d13c5037b929235912\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1049%2Fnewren%2Fhandle-pull-option-precedence-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1049/newren/handle-pull-option-precedence-v1\nPull-Request: https://github.com/git/git/pull/1049\n-- \ngitgitgadget\n"},{"id":"430422","messageId":"329802382bfa24241c2333bd38284aa77e3eb9f0.1626536508.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.git.git.1626536507.gitgitgadget@gmail.com","subject":"[PATCH 2/9] t7601: add tests of interactions with multiple merge heads and config","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-17T15:41:40Z","receivedAt":"2021-07-17T15:41:54Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThere were already code checking that --rebase was incompatible with\na merge of multiple heads.  However, we were sometimes throwing warnings\nabout lack of specification of rebase vs. merge when given multiple\nheads.  Since rebasing is disallowed with multiple merge heads, that\nseems like a poor warning to print; we should instead just assume\nmerging is wanted.\n\nAdd a few tests checking multiple merge head behavior.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t7601-merge-pull-config.sh | 20 ++++++++++++++++++++\n 1 file changed, 20 insertions(+)\n\ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex b24c98cc1b6..40d8fb95113 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -297,6 +297,26 @@ test_expect_success 'pull.rebase=true takes precedence over --ff' '\n \n # End of precedence rules\n \n+test_expect_failure 'Multiple heads does not warn about fast forwarding' '\n+\tgit reset --hard c1 &&\n+\tgit pull . c2 c3 2>err &&\n+\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+'\n+\n+test_expect_success 'Cannot fast-forward with multiple heads' '\n+\tgit reset --hard c0 &&\n+\ttest_must_fail git -c pull.ff=only pull . c1 c2 c3 2>err &&\n+\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err &&\n+\ttest_i18ngrep \"Not possible to fast-forward, aborting\" err\n+'\n+\n+test_expect_success 'Cannot rebase with multiple heads' '\n+\tgit reset --hard c0 &&\n+\ttest_must_fail git -c pull.rebase=true pull . c1 c2 c3 2>err &&\n+\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err &&\n+\ttest_i18ngrep \"Cannot rebase onto multiple branches.\" err\n+'\n+\n test_expect_success 'merge c1 with c2' '\n \tgit reset --hard c1 &&\n \ttest -f c0.c &&\n-- \ngitgitgadget\n\n"},{"id":"430423","messageId":"6cb771297f5f7d5bb0c6734bcb3fe6d3b8bb4c88.1626536508.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.git.git.1626536507.gitgitgadget@gmail.com","subject":"[PATCH 1/9] t7601: add relative precedence tests for merge and rebase flags/options","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-17T15:41:39Z","receivedAt":"2021-07-17T15:41:57Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe interaction of rebase and merge flags and options was not well\ntested.  Add several tests to check for correct behavior from the\nfollowing rules:\n    * --ff-only takes precedence over --[no-]rebase\n      * Corollary: pull.ff=only overrides pull.rebase\n    * --rebase[=!false] takes precedence over --no-ff and --ff\n      * Corollary: pull.rebase=!false overrides pull.ff=!only\n    * command line flags take precedence over config, except:\n      * --no-rebase heeds pull.ff=!only\n      * pull.rebase=!false takes precedence over --no-ff and --ff\n\nFor more details behind these rules and a larger table of individual\ncases, refer to https://lore.kernel.org/git/xmqqwnpqot4m.fsf@gitster.g/\nand the links found therein.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t7601-merge-pull-config.sh | 154 +++++++++++++++++++++++++++++++++++\n 1 file changed, 154 insertions(+)\n\ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex 52e8ccc933a..b24c98cc1b6 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -143,6 +143,160 @@ test_expect_success 'pull.rebase not set and --ff-only given (not-fast-forward)'\n \ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n '\n \n+test_does_rebase() {\n+\tgit reset --hard c2 &&\n+\tgit \"$@\" . c1 &&\n+\t# Check that we actually did a rebase\n+\tgit rev-list --count HEAD >actual &&\n+\tgit rev-list --merges --count HEAD >>actual &&\n+\ttest_write_lines 3 0 >expect &&\n+\ttest_cmp expect actual &&\n+\trm actual expect\n+}\n+\n+test_does_merge_noff() {\n+\tgit reset --hard c0 &&\n+\tgit \"$@\" . c1 &&\n+\t# Check that we actually did a merge\n+\tgit rev-list --count HEAD >actual &&\n+\tgit rev-list --merges --count HEAD >>actual &&\n+\ttest_write_lines 3 1 >expect &&\n+\ttest_cmp expect actual &&\n+\trm actual expect\n+}\n+\n+test_does_merge_ff() {\n+\tgit reset --hard c0 &&\n+\tgit \"$@\" . c1 &&\n+\t# Check that we actually did a merge\n+\tgit rev-list --count HEAD >actual &&\n+\tgit rev-list --merges --count HEAD >>actual &&\n+\ttest_write_lines 2 0 >expect &&\n+\ttest_cmp expect actual &&\n+\trm actual expect\n+}\n+\n+test_does_need_full_merge() {\n+\tgit reset --hard c2 &&\n+\tgit \"$@\" . c1 &&\n+\t# Check that we actually did a merge\n+\tgit rev-list --count HEAD >actual &&\n+\tgit rev-list --merges --count HEAD >>actual &&\n+\ttest_write_lines 4 1 >expect &&\n+\ttest_cmp expect actual &&\n+\trm actual expect\n+}\n+\n+test_attempts_fast_forward() {\n+\tgit reset --hard c2 &&\n+\ttest_must_fail git \"$@\" . c1 2>err &&\n+\ttest_i18ngrep \"Not possible to fast-forward, aborting\" err\n+}\n+\n+#\n+# Rule 1: --ff-only takes precedence over --[no-]rebase\n+# (Corollary: pull.ff=only overrides pull.rebase)\n+#\n+test_expect_failure '--ff-only takes precedence over --rebase' '\n+\ttest_attempts_fast_forward pull --rebase --ff-only\n+'\n+\n+test_expect_failure '--ff-only takes precedence over --rebase even if first' '\n+\ttest_attempts_fast_forward pull --ff-only --rebase\n+'\n+\n+test_expect_success '--ff-only takes precedence over --no-rebase' '\n+\ttest_attempts_fast_forward pull --ff-only --no-rebase\n+'\n+\n+test_expect_failure 'pull.ff=only overrides pull.rebase=true' '\n+\ttest_attempts_fast_forward -c pull.ff=only -c pull.rebase=true pull\n+'\n+\n+test_expect_success 'pull.ff=only overrides pull.rebase=false' '\n+\ttest_attempts_fast_forward -c pull.ff=only -c pull.rebase=false pull\n+'\n+\n+# Rule 2: --rebase=[!false] takes precedence over --no-ff and --ff\n+# (Corollary: pull.rebase=!false overrides pull.ff=!only)\n+test_expect_success '--rebase takes precedence over --no-ff' '\n+\ttest_does_rebase pull --rebase --no-ff\n+'\n+\n+test_expect_success '--rebase takes precedence over --ff' '\n+\ttest_does_rebase pull --rebase --ff\n+'\n+\n+test_expect_success 'pull.rebase=true takes precedence over pull.ff=false' '\n+\ttest_does_rebase -c pull.rebase=true -c pull.ff=false pull\n+'\n+\n+test_expect_success 'pull.rebase=true takes precedence over pull.ff=true' '\n+\ttest_does_rebase -c pull.rebase=true -c pull.ff=true pull\n+'\n+\n+# Rule 3: command line flags take precedence over config\n+test_expect_failure '--ff-only takes precedence over pull.rebase=true' '\n+\ttest_attempts_fast_forward -c pull.rebase=true pull --ff-only\n+'\n+\n+test_expect_success '--ff-only takes precedence over pull.rebase=false' '\n+\ttest_attempts_fast_forward -c pull.rebase=false pull --ff-only\n+'\n+\n+test_expect_failure '--no-rebase overrides pull.ff=only' '\n+\ttest_does_need_full_merge -c pull.ff=only pull --no-rebase\n+'\n+\n+test_expect_success '--rebase takes precedence over pull.ff=only' '\n+\ttest_does_rebase -c pull.ff=only pull --rebase\n+'\n+\n+test_expect_success '--rebase takes precedence over pull.ff=true' '\n+\ttest_does_rebase -c pull.ff=true pull --rebase\n+'\n+\n+test_expect_success '--rebase takes precedence over pull.ff=false' '\n+\ttest_does_rebase -c pull.ff=false pull --rebase\n+'\n+\n+test_expect_success '--rebase takes precedence over pull.ff unset' '\n+\ttest_does_rebase pull --rebase\n+'\n+\n+# Rule 4: --no-rebase heeds pull.ff=!only or explict --ff or --no-ff\n+\n+test_expect_success '--no-rebase works with --no-ff' '\n+\ttest_does_merge_noff pull --no-rebase --no-ff\n+'\n+\n+test_expect_success '--no-rebase works with --ff' '\n+\ttest_does_merge_ff pull --no-rebase --ff\n+'\n+\n+test_expect_success '--no-rebase does ff if pull.ff unset' '\n+\ttest_does_merge_ff pull --no-rebase\n+'\n+\n+test_expect_success '--no-rebase heeds pull.ff=true' '\n+\ttest_does_merge_ff -c pull.ff=true pull --no-rebase\n+'\n+\n+test_expect_success '--no-rebase heeds pull.ff=false' '\n+\ttest_does_merge_noff -c pull.ff=false pull --no-rebase\n+'\n+\n+# Rule 5: pull.rebase=!false takes precedence over --no-ff and --ff\n+test_expect_success 'pull.rebase=true takes precedence over --no-ff' '\n+\ttest_does_rebase -c pull.rebase=true pull --no-ff\n+'\n+\n+test_expect_success 'pull.rebase=true takes precedence over --ff' '\n+\ttest_does_rebase -c pull.rebase=true pull --ff\n+'\n+\n+# End of precedence rules\n+\n test_expect_success 'merge c1 with c2' '\n \tgit reset --hard c1 &&\n \ttest -f c0.c &&\n-- \ngitgitgadget\n\n"},{"id":"430424","messageId":"ae54afd8b0118aa1e2b90503aee75a8fddecae1a.1626536508.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.git.git.1626536507.gitgitgadget@gmail.com","subject":"[PATCH 3/9] pull: abort if --ff-only is given and fast-forwarding is impossible","fromName":"Alex Henrie via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-17T15:41:41Z","receivedAt":"2021-07-17T15:41:58Z","isPatch":true,"sender":{"key":"alexhenrie24@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5951993?v=4"},"body":"From: Alex Henrie <alexhenrie24@gmail.com>\n\nThe warning about pulling without specifying how to reconcile divergent\nbranches says that after setting pull.rebase to true, --ff-only can\nstill be passed on the command line to require a fast-forward. Make that\nactually work.\n\nSigned-off-by: Alex Henrie <alexhenrie24@gmail.com>\n[en: updated tests; note 3 fixes and 1 new failure]\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n advice.c                     |  5 +++++\n advice.h                     |  1 +\n builtin/merge.c              |  2 +-\n builtin/pull.c               | 11 ++++++++---\n t/t7601-merge-pull-config.sh | 10 +++++-----\n 5 files changed, 20 insertions(+), 9 deletions(-)\n\ndiff --git a/advice.c b/advice.c\nindex 0b9c89c48ab..337e8f342bc 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -286,6 +286,11 @@ void NORETURN die_conclude_merge(void)\n \tdie(_(\"Exiting because of unfinished merge.\"));\n }\n \n+void NORETURN die_ff_impossible(void)\n+{\n+\tdie(_(\"Not possible to fast-forward, aborting.\"));\n+}\n+\n void advise_on_updating_sparse_paths(struct string_list *pathspec_list)\n {\n \tstruct string_list_item *item;\ndiff --git a/advice.h b/advice.h\nindex bd26c385d00..16240438387 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -95,6 +95,7 @@ void advise_if_enabled(enum advice_type type, const char *advice, ...);\n int error_resolve_conflict(const char *me);\n void NORETURN die_resolve_conflict(const char *me);\n void NORETURN die_conclude_merge(void);\n+void NORETURN die_ff_impossible(void);\n void advise_on_updating_sparse_paths(struct string_list *pathspec_list);\n void detach_advice(const char *new_name);\n \ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex a8a843b1f54..aa920ac524f 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1620,7 +1620,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t}\n \n \tif (fast_forward == FF_ONLY)\n-\t\tdie(_(\"Not possible to fast-forward, aborting.\"));\n+\t\tdie_ff_impossible();\n \n \tif (autostash)\n \t\tcreate_autostash(the_repository,\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 3e13f810843..d9796604825 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -1046,9 +1046,14 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \n \tcan_ff = get_can_ff(&orig_head, &merge_heads.oid[0]);\n \n-\tif (rebase_unspecified && !opt_ff && !can_ff) {\n-\t\tif (opt_verbosity >= 0)\n-\t\t\tshow_advice_pull_non_ff();\n+\tif (!can_ff) {\n+\t\tif (opt_ff) {\n+\t\t\tif (!strcmp(opt_ff, \"--ff-only\"))\n+\t\t\t\tdie_ff_impossible();\n+\t\t} else {\n+\t\t\tif (rebase_unspecified && opt_verbosity >= 0)\n+\t\t\t\tshow_advice_pull_non_ff();\n+\t\t}\n \t}\n \n \tif (opt_rebase) {\ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex 40d8fb95113..23a1fbc17b3 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -197,11 +197,11 @@ test_attempts_fast_forward() {\n # Rule 1: --ff-only takes precedence over --[no-]rebase\n # (Corollary: pull.ff=only overrides pull.rebase)\n #\n-test_expect_failure '--ff-only takes precedence over --rebase' '\n+test_expect_success '--ff-only takes precedence over --rebase' '\n \ttest_attempts_fast_forward pull --rebase --ff-only\n '\n \n-test_expect_failure '--ff-only takes precedence over --rebase even if first' '\n+test_expect_success '--ff-only takes precedence over --rebase even if first' '\n \ttest_attempts_fast_forward pull --ff-only --rebase\n '\n \n@@ -209,7 +209,7 @@ test_expect_success '--ff-only takes precedence over --no-rebase' '\n \ttest_attempts_fast_forward pull --ff-only --no-rebase\n '\n \n-test_expect_failure 'pull.ff=only overrides pull.rebase=true' '\n+test_expect_success 'pull.ff=only overrides pull.rebase=true' '\n \ttest_attempts_fast_forward -c pull.ff=only -c pull.rebase=true pull\n '\n \n@@ -236,7 +236,7 @@ test_expect_success 'pull.rebase=true takes precedence over pull.ff=true' '\n '\n \n # Rule 3: command line flags take precedence over config\n-test_expect_failure '--ff-only takes precedence over pull.rebase=true' '\n+test_expect_success '--ff-only takes precedence over pull.rebase=true' '\n \ttest_attempts_fast_forward -c pull.rebase=true pull --ff-only\n '\n \n@@ -248,7 +248,7 @@ test_expect_failure '--no-rebase overrides pull.ff=only' '\n \ttest_does_need_full_merge -c pull.ff=only pull --no-rebase\n '\n \n-test_expect_success '--rebase takes precedence over pull.ff=only' '\n+test_expect_failure '--rebase takes precedence over pull.ff=only' '\n \ttest_does_rebase -c pull.ff=only pull --rebase\n '\n \n-- \ngitgitgadget\n\n"},{"id":"430425","messageId":"de4b460b09d3a3b6848f9f9eaa5520b31a3b453a.1626536508.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.git.git.1626536507.gitgitgadget@gmail.com","subject":"[PATCH 4/9] pull: since --ff-only overrides, handle it first","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-17T15:41:42Z","receivedAt":"2021-07-17T15:41:59Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThere are both merge and rebase branches in the logic, and previously\nboth had to handle fast-forwarding.  Merge handled that implicitly\n(because git merge handles it directly), while in rebase it was\nexplicit.  Given that the --ff-only flag is meant to override any\n--rebase or --no-rebase, make the code reflect that by handling\n--ff-only before the merge-vs-rebase logic.\n\nNo functional changes, just making it easier to verify that the codeflow\nmatches our precedence rules.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/pull.c | 17 +++++++++--------\n 1 file changed, 9 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex d9796604825..5c9cbea37c9 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -1046,15 +1046,16 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \n \tcan_ff = get_can_ff(&orig_head, &merge_heads.oid[0]);\n \n-\tif (!can_ff) {\n-\t\tif (opt_ff) {\n-\t\t\tif (!strcmp(opt_ff, \"--ff-only\"))\n-\t\t\t\tdie_ff_impossible();\n-\t\t} else {\n-\t\t\tif (rebase_unspecified && opt_verbosity >= 0)\n-\t\t\t\tshow_advice_pull_non_ff();\n-\t\t}\n+\t/* ff-only takes precedence over rebase */\n+\tif (opt_ff && !strcmp(opt_ff, \"--ff-only\")) {\n+\t\tif (!can_ff)\n+\t\t\tdie_ff_impossible();\n+\t\telse\n+\t\t\treturn run_merge();\n \t}\n+\t/* If no action specified and we can't fast forward, then warn. */\n+\tif (!opt_ff && rebase_unspecified && !can_ff)\n+\t\tshow_advice_pull_non_ff();\n \n \tif (opt_rebase) {\n \t\tint ret = 0;\n-- \ngitgitgadget\n\n"},{"id":"430426","messageId":"3d9ff69198e5a604b124bf861df4d6ecf6eb661e.1626536508.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.git.git.1626536507.gitgitgadget@gmail.com","subject":"[PATCH 5/9] pull: ensure --rebase overrides ability to ff","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-17T15:41:43Z","receivedAt":"2021-07-17T15:42:01Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nNow that the handling of fast-forward-only in combination with rebases\nhas been moved before the merge-vs-rebase logic, we have an unnecessary\nspecial fast-forward case left within the rebase logic.  Actually, more\nthan unnecessary, it's actually a violation of the rules.  As per\nhttps://lore.kernel.org/git/xmqqwnpqot4m.fsf@gitster.g/, --rebase is\nsupposed to override all ff flags other than an explicit --ff-only.\nEnsure that it does so by removing the fast-forward special case that\nexists within the rebase logic.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/pull.c  | 8 +-------\n t/t5520-pull.sh | 6 +++---\n 2 files changed, 4 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 5c9cbea37c9..5ba376a7487 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -1070,13 +1070,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \t\t    submodule_touches_in_range(the_repository, &upstream, &curr_head))\n \t\t\tdie(_(\"cannot rebase with locally recorded submodule modifications\"));\n \n-\t\tif (can_ff) {\n-\t\t\t/* we can fast-forward this without invoking rebase */\n-\t\t\topt_ff = \"--ff-only\";\n-\t\t\tret = run_merge();\n-\t\t} else {\n-\t\t\tret = run_rebase(&newbase, &upstream);\n-\t\t}\n+\t\tret = run_rebase(&newbase, &upstream);\n \n \t\tif (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON ||\n \t\t\t     recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND))\ndiff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\nindex e2c0c510222..4b50488141f 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -295,7 +295,7 @@ test_expect_success '--rebase (merge) fast forward' '\n \t# The above only validates the result.  Did we actually bypass rebase?\n \tgit reflog -1 >reflog.actual &&\n \tsed \"s/^[0-9a-f][0-9a-f]*/OBJID/\" reflog.actual >reflog.fuzzy &&\n-\techo \"OBJID HEAD@{0}: pull --rebase . ff: Fast-forward\" >reflog.expected &&\n+\techo \"OBJID HEAD@{0}: pull --rebase . ff (finish): returning to refs/heads/to-rebase\" >reflog.expected &&\n \ttest_cmp reflog.expected reflog.fuzzy\n '\n \n@@ -307,8 +307,8 @@ test_expect_success '--rebase (am) fast forward' '\n \n \t# The above only validates the result.  Did we actually bypass rebase?\n \tgit reflog -1 >reflog.actual &&\n-\tsed \"s/^[0-9a-f][0-9a-f]*/OBJID/\" reflog.actual >reflog.fuzzy &&\n-\techo \"OBJID HEAD@{0}: pull --rebase . ff: Fast-forward\" >reflog.expected &&\n+\tsed -e \"s/^[0-9a-f][0-9a-f]*/OBJID/\" -e \"s/[0-9a-f][0-9a-f]*$/OBJID/\" reflog.actual >reflog.fuzzy &&\n+\techo \"OBJID HEAD@{0}: rebase finished: refs/heads/to-rebase onto OBJID\" >reflog.expected &&\n \ttest_cmp reflog.expected reflog.fuzzy\n '\n \n-- \ngitgitgadget\n\n"},{"id":"430427","messageId":"b379fea097d65a28f1791f7f2f9432b6689a977f.1626536508.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.git.git.1626536507.gitgitgadget@gmail.com","subject":"[PATCH 6/9] pull: make --rebase and --no-rebase override pull.ff=only","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-17T15:41:44Z","receivedAt":"2021-07-17T15:42:01Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFix the last few precedence tests failing in t7601 by now implementing\nthe logic to have --[no-]rebase override a pull.ff=only config setting.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/pull.c               | 5 ++++-\n t/t7601-merge-pull-config.sh | 4 ++--\n 2 files changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 5ba376a7487..7e7c90f6a10 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -966,8 +966,11 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \n \tparse_repo_refspecs(argc, argv, &repo, &refspecs);\n \n-\tif (!opt_ff)\n+\tif (!opt_ff) {\n \t\topt_ff = xstrdup_or_null(config_get_ff());\n+\t\tif (opt_rebase >= 0 && opt_ff && !strcmp(opt_ff, \"--ff-only\"))\n+\t\t\topt_ff = \"--ff\";\n+\t}\n \n \tif (opt_rebase < 0)\n \t\topt_rebase = config_get_rebase(&rebase_unspecified);\ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex 23a1fbc17b3..12787d07289 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -244,11 +244,11 @@ test_expect_success '--ff-only takes precedence over pull.rebase=false' '\n \ttest_attempts_fast_forward -c pull.rebase=false pull --ff-only\n '\n \n-test_expect_failure '--no-rebase overrides pull.ff=only' '\n+test_expect_success '--no-rebase overrides pull.ff=only' '\n \ttest_does_need_full_merge -c pull.ff=only pull --no-rebase\n '\n \n-test_expect_failure '--rebase takes precedence over pull.ff=only' '\n+test_expect_success '--rebase takes precedence over pull.ff=only' '\n \ttest_does_rebase -c pull.ff=only pull --rebase\n '\n \n-- \ngitgitgadget\n\n"},{"id":"430428","messageId":"d1952f014f20d2770c74a311df27f956c8b95e21.1626536508.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.git.git.1626536507.gitgitgadget@gmail.com","subject":"[PATCH 8/9] pull: update docs & code for option compatibility with rebasing","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-17T15:41:46Z","receivedAt":"2021-07-17T15:42:02Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\ngit-pull.txt includes merge-options.txt, which is written assuming\nmerges will happen.  git-pull has allowed rebases for many years; update\nthe documentation to reflect that.\n\nWhile at it, pass any `--signoff` flag through to the rebase backend too\nso that we don't have to document it as merge-specific.  Rebase has\nsupported the --signoff flag for years now as well.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-pull.txt      |  2 ++\n Documentation/merge-options.txt | 25 +++++++++++++++++++++++++\n builtin/pull.c                  |  2 ++\n 3 files changed, 29 insertions(+)\n\ndiff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\nindex cad3f6bfcee..6e6e95a7595 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -125,6 +125,8 @@ When false, merge the current branch into the upstream branch.\n +\n When `interactive`, enable the interactive mode of rebase.\n +\n+Note that `--ff-only` takes precedence over any `--rebase` flag.\n++\n See `pull.rebase`, `branch.<name>.rebase` and `branch.autoSetupRebase` in\n linkgit:git-config[1] if you want to make `git pull` always use\n `--rebase` instead of merging.\ndiff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt\nindex eb0aabd396f..3b70abf3c87 100644\n--- a/Documentation/merge-options.txt\n+++ b/Documentation/merge-options.txt\n@@ -2,6 +2,9 @@\n --no-commit::\n \tPerform the merge and commit the result. This option can\n \tbe used to override --no-commit.\n+ifdef::git-pull[]\n+\tOnly useful when merging.\n+endif::git-pull[]\n +\n With --no-commit perform the merge and stop just before creating\n a merge commit, to give the user a chance to inspect and further\n@@ -58,6 +61,10 @@ could instead be resolved as a fast-forward.\n +\n With `--ff-only`, resolve the merge as a fast-forward when possible.\n When not possible, refuse to merge and exit with a non-zero status.\n+ifdef::git-pull[]\n++\n+Note that `--no-ff` and `--ff` are ignored when rebasing is requested.\n+endif::git-pull[]\n \n -S[<keyid>]::\n --gpg-sign[=<keyid>]::\n@@ -73,6 +80,9 @@ When not possible, refuse to merge and exit with a non-zero status.\n \tIn addition to branch names, populate the log message with\n \tone-line descriptions from at most <n> actual commits that are being\n \tmerged. See also linkgit:git-fmt-merge-msg[1].\n+ifdef::git-pull[]\n+\tOnly useful when merging.\n+endif::git-pull[]\n +\n With --no-log do not list one-line descriptions from the\n actual commits being merged.\n@@ -102,10 +112,17 @@ With --no-squash perform the merge and commit the result. This\n option can be used to override --squash.\n +\n With --squash, --commit is not allowed, and will fail.\n+ifdef::git-pull[]\n++\n+Only useful when merging.\n+endif::git-pull[]\n \n --no-verify::\n \tThis option bypasses the pre-merge and commit-msg hooks.\n \tSee also linkgit:githooks[5].\n+ifdef::git-pull[]\n+\tOnly useful when merging.\n+endif::git-pull[]\n \n -s <strategy>::\n --strategy=<strategy>::\n@@ -127,6 +144,10 @@ With --squash, --commit is not allowed, and will fail.\n \tdefault trust model, this means the signing key has been signed by\n \ta trusted key.  If the tip commit of the side branch is not signed\n \twith a valid key, the merge is aborted.\n+ifdef::git-pull[]\n++\n+Only useful when merging.\n+endif::git-pull[]\n \n --summary::\n --no-summary::\n@@ -166,3 +187,7 @@ endif::git-pull[]\n \tprojects that started their lives independently. As that is\n \ta very rare occasion, no configuration variable to enable\n \tthis by default exists and will not be added.\n+ifdef::git-pull[]\n++\n+Only useful when merging.\n+endif::git-pull[]\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 2d7f2d765ab..3a61b92f328 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -893,6 +893,8 @@ static int run_rebase(const struct object_id *newbase,\n \tstrvec_pushv(&args, opt_strategy_opts.v);\n \tif (opt_gpg_sign)\n \t\tstrvec_push(&args, opt_gpg_sign);\n+\tif (opt_signoff)\n+\t\tstrvec_push(&args, opt_signoff);\n \tif (opt_autostash == 0)\n \t\tstrvec_push(&args, \"--no-autostash\");\n \telse if (opt_autostash == 1)\n-- \ngitgitgadget\n\n"},{"id":"430429","messageId":"dca0455898a838b46546a29ee79f0a8b112fbff5.1626536508.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.git.git.1626536507.gitgitgadget@gmail.com","subject":"[PATCH 7/9] pull: abort by default when fast-forwarding is not possible","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-17T15:41:45Z","receivedAt":"2021-07-17T15:42:03Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe have for some time shown a long warning when the user does not\nspecify how to reconcile divergent branches with git pull.  Make it an\nerror now.\n\nInitial-patch-by: Alex Henrie <alexhenrie24@gmail.com>\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-pull.txt    | 20 +++++++++++-------\n builtin/pull.c                | 10 +++++----\n t/t4013-diff-various.sh       |  2 +-\n t/t5520-pull.sh               | 20 +++++++++---------\n t/t5521-pull-options.sh       |  4 ++--\n t/t5524-pull-msg.sh           |  4 ++--\n t/t5553-set-upstream.sh       | 14 ++++++------\n t/t5604-clone-reference.sh    |  4 ++--\n t/t6402-merge-rename.sh       | 18 ++++++++--------\n t/t6409-merge-subtree.sh      |  6 +++---\n t/t6417-merge-ours-theirs.sh  | 10 ++++-----\n t/t7601-merge-pull-config.sh  | 40 +++++++++++++++++------------------\n t/t7603-merge-reduce-heads.sh |  2 +-\n 13 files changed, 80 insertions(+), 74 deletions(-)\n\ndiff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\nindex 5c3fb67c014..cad3f6bfcee 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -15,14 +15,18 @@ SYNOPSIS\n DESCRIPTION\n -----------\n \n-Incorporates changes from a remote repository into the current\n-branch.  In its default mode, `git pull` is shorthand for\n-`git fetch` followed by `git merge FETCH_HEAD`.\n-\n-More precisely, 'git pull' runs 'git fetch' with the given\n-parameters and calls 'git merge' to merge the retrieved branch\n-heads into the current branch.\n-With `--rebase`, it runs 'git rebase' instead of 'git merge'.\n+Incorporates changes from a remote repository into the current branch.\n+If the current branch is behind the remote, then by default it will\n+fast-forward the current branch to match the remote.  If the current\n+branch and the remote have diverged, the user needs to specify how to\n+reconcile the divergent branches with `--no-ff`, `--ff`, or `--rebase`\n+(or the corresponding configuration options in `pull.ff` or\n+`pull.rebase`).\n+\n+More precisely, `git pull` runs `git fetch` with the given parameters\n+and then depending on configuration options or command line flags,\n+will call either `git merge` or `git rebase` to reconcile diverging\n+branches.\n \n <repository> should be the name of a remote repository as\n passed to linkgit:git-fetch[1].  <refspec> can name an\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 7e7c90f6a10..2d7f2d765ab 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -927,9 +927,9 @@ static int get_can_ff(struct object_id *orig_head, struct object_id *orig_merge_\n \n static void show_advice_pull_non_ff(void)\n {\n-\tadvise(_(\"Pulling without specifying how to reconcile divergent branches is\\n\"\n-\t\t \"discouraged. You can squelch this message by running one of the following\\n\"\n-\t\t \"commands sometime before your next pull:\\n\"\n+\tadvise(_(\"You have divergent branches and need to specify how to reconcile them.\\n\"\n+\t\t \"You can do so by running one of the following commands sometime before\\n\"\n+\t\t \"your next pull:\\n\"\n \t\t \"\\n\"\n \t\t \"  git config pull.rebase false  # merge (the default strategy)\\n\"\n \t\t \"  git config pull.rebase true   # rebase\\n\"\n@@ -1057,8 +1057,10 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \t\t\treturn run_merge();\n \t}\n \t/* If no action specified and we can't fast forward, then warn. */\n-\tif (!opt_ff && rebase_unspecified && !can_ff)\n+\tif (!opt_ff && rebase_unspecified && !can_ff) {\n \t\tshow_advice_pull_non_ff();\n+\t\tdie(_(\"Need to specify how to reconcile divergent branches.\"));\n+\t}\n \n \tif (opt_rebase) {\n \t\tint ret = 0;\ndiff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh\nindex 7fadc985ccc..eb989f7f191 100755\n--- a/t/t4013-diff-various.sh\n+++ b/t/t4013-diff-various.sh\n@@ -65,7 +65,7 @@ test_expect_success setup '\n \texport GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&\n \n \tgit checkout master &&\n-\tgit pull -s ours . side &&\n+\tgit pull -s ours --no-rebase . side &&\n \n \tGIT_AUTHOR_DATE=\"2006-06-26 00:05:00 +0000\" &&\n \tGIT_COMMITTER_DATE=\"2006-06-26 00:05:00 +0000\" &&\ndiff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\nindex 4b50488141f..56f5864b269 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -136,12 +136,12 @@ test_expect_success 'the default remote . should not break explicit pull' '\n \tgit reset --hard HEAD^ &&\n \techo file >expect &&\n \ttest_cmp expect file &&\n-\tgit pull . second &&\n+\tgit pull --no-rebase . second &&\n \techo modified >expect &&\n \ttest_cmp expect file &&\n \tgit reflog -1 >reflog.actual &&\n \tsed \"s/^[0-9a-f][0-9a-f]*/OBJID/\" reflog.actual >reflog.fuzzy &&\n-\techo \"OBJID HEAD@{0}: pull . second: Fast-forward\" >reflog.expected &&\n+\techo \"OBJID HEAD@{0}: pull --no-rebase . second: Fast-forward\" >reflog.expected &&\n \ttest_cmp reflog.expected reflog.fuzzy\n '\n \n@@ -226,7 +226,7 @@ test_expect_success 'fail if the index has unresolved entries' '\n \ttest_commit modified2 file &&\n \tgit ls-files -u >unmerged &&\n \ttest_must_be_empty unmerged &&\n-\ttest_must_fail git pull . second &&\n+\ttest_must_fail git pull --no-rebase . second &&\n \tgit ls-files -u >unmerged &&\n \ttest_file_not_empty unmerged &&\n \tcp file expected &&\n@@ -409,37 +409,37 @@ test_expect_success 'pull --rebase --no-autostash & rebase.autostash unset' '\n \n test_expect_success 'pull succeeds with dirty working directory and merge.autostash set' '\n \ttest_config merge.autostash true &&\n-\ttest_pull_autostash 2\n+\ttest_pull_autostash 2 --no-rebase\n '\n \n test_expect_success 'pull --autostash & merge.autostash=true' '\n \ttest_config merge.autostash true &&\n-\ttest_pull_autostash 2 --autostash\n+\ttest_pull_autostash 2 --autostash --no-rebase\n '\n \n test_expect_success 'pull --autostash & merge.autostash=false' '\n \ttest_config merge.autostash false &&\n-\ttest_pull_autostash 2 --autostash\n+\ttest_pull_autostash 2 --autostash --no-rebase\n '\n \n test_expect_success 'pull --autostash & merge.autostash unset' '\n \ttest_unconfig merge.autostash &&\n-\ttest_pull_autostash 2 --autostash\n+\ttest_pull_autostash 2 --autostash --no-rebase\n '\n \n test_expect_success 'pull --no-autostash & merge.autostash=true' '\n \ttest_config merge.autostash true &&\n-\ttest_pull_autostash_fail --no-autostash\n+\ttest_pull_autostash_fail --no-autostash --no-rebase\n '\n \n test_expect_success 'pull --no-autostash & merge.autostash=false' '\n \ttest_config merge.autostash false &&\n-\ttest_pull_autostash_fail --no-autostash\n+\ttest_pull_autostash_fail --no-autostash --no-rebase\n '\n \n test_expect_success 'pull --no-autostash & merge.autostash unset' '\n \ttest_unconfig merge.autostash &&\n-\ttest_pull_autostash_fail --no-autostash\n+\ttest_pull_autostash_fail --no-autostash --no-rebase\n '\n \n test_expect_success 'pull.rebase' '\ndiff --git a/t/t5521-pull-options.sh b/t/t5521-pull-options.sh\nindex 63a688bdbf5..7601c919fdc 100755\n--- a/t/t5521-pull-options.sh\n+++ b/t/t5521-pull-options.sh\n@@ -113,7 +113,7 @@ test_expect_success 'git pull --force' '\n \tgit pull two &&\n \ttest_commit A &&\n \tgit branch -f origin &&\n-\tgit pull --all --force\n+\tgit pull --no-rebase --all --force\n \t)\n '\n \n@@ -179,7 +179,7 @@ test_expect_success 'git pull --allow-unrelated-histories' '\n \t(\n \t\tcd dst &&\n \t\ttest_must_fail git pull ../src side &&\n-\t\tgit pull --allow-unrelated-histories ../src side\n+\t\tgit pull --no-rebase --allow-unrelated-histories ../src side\n \t)\n '\n \ndiff --git a/t/t5524-pull-msg.sh b/t/t5524-pull-msg.sh\nindex c278adaa5a2..b2be3605f5a 100755\n--- a/t/t5524-pull-msg.sh\n+++ b/t/t5524-pull-msg.sh\n@@ -28,7 +28,7 @@ test_expect_success setup '\n test_expect_success pull '\n (\n \tcd cloned &&\n-\tgit pull --log &&\n+\tgit pull --no-rebase --log &&\n \tgit log -2 &&\n \tgit cat-file commit HEAD >result &&\n \tgrep Dollar result\n@@ -41,7 +41,7 @@ test_expect_success '--log=1 limits shortlog length' '\n \tgit reset --hard HEAD^ &&\n \ttest \"$(cat afile)\" = original &&\n \ttest \"$(cat bfile)\" = added &&\n-\tgit pull --log=1 &&\n+\tgit pull --no-rebase --log=1 &&\n \tgit log -3 &&\n \tgit cat-file commit HEAD >result &&\n \tgrep Dollar result &&\ndiff --git a/t/t5553-set-upstream.sh b/t/t5553-set-upstream.sh\nindex b1d614ce18c..9c12c0f8c32 100755\n--- a/t/t5553-set-upstream.sh\n+++ b/t/t5553-set-upstream.sh\n@@ -108,27 +108,27 @@ test_expect_success 'setup commit on main and other pull' '\n \n test_expect_success 'pull --set-upstream upstream main sets branch main but not other' '\n \tclear_config main other &&\n-\tgit pull --set-upstream upstream main &&\n+\tgit pull --no-rebase --set-upstream upstream main &&\n \tcheck_config main upstream refs/heads/main &&\n \tcheck_config_missing other\n '\n \n test_expect_success 'pull --set-upstream main:other2 does not set the branch other2' '\n \tclear_config other2 &&\n-\tgit pull --set-upstream upstream main:other2 &&\n+\tgit pull --no-rebase --set-upstream upstream main:other2 &&\n \tcheck_config_missing other2\n '\n \n test_expect_success 'pull --set-upstream upstream other sets branch main' '\n \tclear_config main other &&\n-\tgit pull --set-upstream upstream other &&\n+\tgit pull --no-rebase --set-upstream upstream other &&\n \tcheck_config main upstream refs/heads/other &&\n \tcheck_config_missing other\n '\n \n test_expect_success 'pull --set-upstream upstream tag does not set the tag' '\n \tclear_config three &&\n-\tgit pull --tags --set-upstream upstream three &&\n+\tgit pull --no-rebase --tags --set-upstream upstream three &&\n \tcheck_config_missing three\n '\n \n@@ -144,16 +144,16 @@ test_expect_success 'pull --set-upstream http://nosuchdomain.example.com fails w\n \n test_expect_success 'pull --set-upstream upstream HEAD sets branch HEAD' '\n \tclear_config main other &&\n-\tgit pull --set-upstream upstream HEAD &&\n+\tgit pull --no-rebase --set-upstream upstream HEAD &&\n \tcheck_config main upstream HEAD &&\n \tgit checkout other &&\n-\tgit pull --set-upstream upstream HEAD &&\n+\tgit pull --no-rebase --set-upstream upstream HEAD &&\n \tcheck_config other upstream HEAD\n '\n \n test_expect_success 'pull --set-upstream upstream with more than one branch does nothing' '\n \tclear_config main three &&\n-\tgit pull --set-upstream upstream main three &&\n+\tgit pull --no-rebase --set-upstream upstream main three &&\n \tcheck_config_missing main &&\n \tcheck_config_missing three\n '\ndiff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh\nindex e845d621f61..24340e6d56e 100755\n--- a/t/t5604-clone-reference.sh\n+++ b/t/t5604-clone-reference.sh\n@@ -87,7 +87,7 @@ test_expect_success 'updating origin' '\n '\n \n test_expect_success 'pulling changes from origin' '\n-\tgit -C C pull origin\n+\tgit -C C pull --no-rebase origin\n '\n \n # the 2 local objects are commit and tree from the merge\n@@ -96,7 +96,7 @@ test_expect_success 'that alternate to origin gets used' '\n '\n \n test_expect_success 'pulling changes from origin' '\n-\tgit -C D pull origin\n+\tgit -C D pull --no-rebase origin\n '\n \n # the 5 local objects are expected; file3 blob, commit in A to add it\ndiff --git a/t/t6402-merge-rename.sh b/t/t6402-merge-rename.sh\nindex 425dad97d54..02a842697b8 100755\n--- a/t/t6402-merge-rename.sh\n+++ b/t/t6402-merge-rename.sh\n@@ -103,7 +103,7 @@ test_expect_success 'setup' '\n test_expect_success 'pull renaming branch into unrenaming one' \\\n '\n \tgit show-branch &&\n-\ttest_expect_code 1 git pull . white &&\n+\ttest_expect_code 1 git pull --no-rebase . white &&\n \tgit ls-files -s &&\n \tgit ls-files -u B >b.stages &&\n \ttest_line_count = 3 b.stages &&\n@@ -121,7 +121,7 @@ test_expect_success 'pull renaming branch into another renaming one' \\\n \trm -f B &&\n \tgit reset --hard &&\n \tgit checkout red &&\n-\ttest_expect_code 1 git pull . white &&\n+\ttest_expect_code 1 git pull --no-rebase . white &&\n \tgit ls-files -u B >b.stages &&\n \ttest_line_count = 3 b.stages &&\n \tgit ls-files -s N >n.stages &&\n@@ -137,7 +137,7 @@ test_expect_success 'pull unrenaming branch into renaming one' \\\n '\n \tgit reset --hard &&\n \tgit show-branch &&\n-\ttest_expect_code 1 git pull . main &&\n+\ttest_expect_code 1 git pull --no-rebase . main &&\n \tgit ls-files -u B >b.stages &&\n \ttest_line_count = 3 b.stages &&\n \tgit ls-files -s N >n.stages &&\n@@ -153,7 +153,7 @@ test_expect_success 'pull conflicting renames' \\\n '\n \tgit reset --hard &&\n \tgit show-branch &&\n-\ttest_expect_code 1 git pull . blue &&\n+\ttest_expect_code 1 git pull --no-rebase . blue &&\n \tgit ls-files -u A >a.stages &&\n \ttest_line_count = 1 a.stages &&\n \tgit ls-files -u B >b.stages &&\n@@ -173,7 +173,7 @@ test_expect_success 'interference with untracked working tree file' '\n \tgit reset --hard &&\n \tgit show-branch &&\n \techo >A this file should not matter &&\n-\ttest_expect_code 1 git pull . white &&\n+\ttest_expect_code 1 git pull --no-rebase . white &&\n \ttest_path_is_file A\n '\n \n@@ -183,7 +183,7 @@ test_expect_success 'interference with untracked working tree file' '\n \tgit show-branch &&\n \trm -f A &&\n \techo >A this file should not matter &&\n-\ttest_expect_code 1 git pull . red &&\n+\ttest_expect_code 1 git pull --no-rebase . red &&\n \ttest_path_is_file A\n '\n \n@@ -193,7 +193,7 @@ test_expect_success 'interference with untracked working tree file' '\n \tgit checkout -f main &&\n \tgit tag -f anchor &&\n \tgit show-branch &&\n-\tgit pull . yellow &&\n+\tgit pull --no-rebase . yellow &&\n \ttest_path_is_missing M &&\n \tgit reset --hard anchor\n '\n@@ -220,7 +220,7 @@ test_expect_success 'updated working tree file should prevent the merge' '\n \techo >>M one line addition &&\n \tcat M >M.saved &&\n \tgit update-index M &&\n-\ttest_expect_code 128 git pull . yellow &&\n+\ttest_expect_code 128 git pull --no-rebase . yellow &&\n \ttest_cmp M M.saved &&\n \trm -f M.saved\n '\n@@ -232,7 +232,7 @@ test_expect_success 'interference with untracked working tree file' '\n \tgit tag -f anchor &&\n \tgit show-branch &&\n \techo >M this file should not matter &&\n-\tgit pull . main &&\n+\tgit pull --no-rebase . main &&\n \ttest_path_is_file M &&\n \t! {\n \t\tgit ls-files -s |\ndiff --git a/t/t6409-merge-subtree.sh b/t/t6409-merge-subtree.sh\nindex d406b2343cb..ba7890ec521 100755\n--- a/t/t6409-merge-subtree.sh\n+++ b/t/t6409-merge-subtree.sh\n@@ -100,7 +100,7 @@ test_expect_success 'merge update' '\n \tgit checkout -b topic_2 &&\n \tgit commit -m \"update git-gui\" &&\n \tcd ../git &&\n-\tgit pull -s subtree gui topic_2 &&\n+\tgit pull --no-rebase -s subtree gui topic_2 &&\n \tgit ls-files -s >actual &&\n \t(\n \t\techo \"100644 $o3 0\tgit-gui/git-gui.sh\" &&\n@@ -129,7 +129,7 @@ test_expect_success 'initial ambiguous subtree' '\n test_expect_success 'merge using explicit' '\n \tcd ../git &&\n \tgit reset --hard topic_2 &&\n-\tgit pull -Xsubtree=git-gui gui topic_2 &&\n+\tgit pull --no-rebase -Xsubtree=git-gui gui topic_2 &&\n \tgit ls-files -s >actual &&\n \t(\n \t\techo \"100644 $o3 0\tgit-gui/git-gui.sh\" &&\n@@ -142,7 +142,7 @@ test_expect_success 'merge using explicit' '\n test_expect_success 'merge2 using explicit' '\n \tcd ../git &&\n \tgit reset --hard topic_2 &&\n-\tgit pull -Xsubtree=git-gui2 gui topic_2 &&\n+\tgit pull --no-rebase -Xsubtree=git-gui2 gui topic_2 &&\n \tgit ls-files -s >actual &&\n \t(\n \t\techo \"100644 $o1 0\tgit-gui/git-gui.sh\" &&\ndiff --git a/t/t6417-merge-ours-theirs.sh b/t/t6417-merge-ours-theirs.sh\nindex ac9aee9a662..ec065d6a658 100755\n--- a/t/t6417-merge-ours-theirs.sh\n+++ b/t/t6417-merge-ours-theirs.sh\n@@ -69,11 +69,11 @@ test_expect_success 'binary file with -Xours/-Xtheirs' '\n '\n \n test_expect_success 'pull passes -X to underlying merge' '\n-\tgit reset --hard main && git pull -s recursive -Xours . side &&\n-\tgit reset --hard main && git pull -s recursive -X ours . side &&\n-\tgit reset --hard main && git pull -s recursive -Xtheirs . side &&\n-\tgit reset --hard main && git pull -s recursive -X theirs . side &&\n-\tgit reset --hard main && test_must_fail git pull -s recursive -X bork . side\n+\tgit reset --hard main && git pull --no-rebase -s recursive -Xours . side &&\n+\tgit reset --hard main && git pull --no-rebase -s recursive -X ours . side &&\n+\tgit reset --hard main && git pull --no-rebase -s recursive -Xtheirs . side &&\n+\tgit reset --hard main && git pull --no-rebase -s recursive -X theirs . side &&\n+\tgit reset --hard main && test_must_fail git pull --no-rebase -s recursive -X bork . side\n '\n \n test_expect_success SYMLINKS 'symlink with -Xours/-Xtheirs' '\ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex 12787d07289..673b92afbab 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -27,120 +27,120 @@ test_expect_success 'setup' '\n \tgit tag c3\n '\n \n-test_expect_success 'pull.rebase not set' '\n+test_expect_success 'pull.rebase not set, ff possible' '\n \tgit reset --hard c0 &&\n \tgit pull . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and pull.ff=true' '\n \tgit reset --hard c0 &&\n \ttest_config pull.ff true &&\n \tgit pull . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and pull.ff=false' '\n \tgit reset --hard c0 &&\n \ttest_config pull.ff false &&\n \tgit pull . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and pull.ff=only' '\n \tgit reset --hard c0 &&\n \ttest_config pull.ff only &&\n \tgit pull . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --rebase given' '\n \tgit reset --hard c0 &&\n \tgit pull --rebase . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --no-rebase given' '\n \tgit reset --hard c0 &&\n \tgit pull --no-rebase . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --ff given' '\n \tgit reset --hard c0 &&\n \tgit pull --ff . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --no-ff given' '\n \tgit reset --hard c0 &&\n \tgit pull --no-ff . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --ff-only given' '\n \tgit reset --hard c0 &&\n \tgit pull --ff-only . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set (not-fast-forward)' '\n \tgit reset --hard c2 &&\n-\tgit -c color.advice=always pull . c1 2>err &&\n+\ttest_must_fail git -c color.advice=always pull . c1 2>err &&\n \ttest_decode_color <err >decoded &&\n \ttest_i18ngrep \"<YELLOW>hint: \" decoded &&\n-\ttest_i18ngrep \"Pulling without specifying how to reconcile\" decoded\n+\ttest_i18ngrep \"You have divergent branches\" decoded\n '\n \n test_expect_success 'pull.rebase not set and pull.ff=true (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \ttest_config pull.ff true &&\n \tgit pull . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and pull.ff=false (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \ttest_config pull.ff false &&\n \tgit pull . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and pull.ff=only (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \ttest_config pull.ff only &&\n \ttest_must_fail git pull . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --rebase given (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \tgit pull --rebase . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --no-rebase given (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \tgit pull --no-rebase . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --ff given (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \tgit pull --ff . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --no-ff given (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \tgit pull --no-ff . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --ff-only given (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \ttest_must_fail git pull --ff-only . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_does_rebase() {\ndiff --git a/t/t7603-merge-reduce-heads.sh b/t/t7603-merge-reduce-heads.sh\nindex 98948955ae5..27cd94ad6f7 100755\n--- a/t/t7603-merge-reduce-heads.sh\n+++ b/t/t7603-merge-reduce-heads.sh\n@@ -68,7 +68,7 @@ test_expect_success 'merge c1 with c2, c3, c4, c5' '\n \n test_expect_success 'pull c2, c3, c4, c5 into c1' '\n \tgit reset --hard c1 &&\n-\tgit pull . c2 c3 c4 c5 &&\n+\tgit pull --no-rebase . c2 c3 c4 c5 &&\n \ttest \"$(git rev-parse c1)\" != \"$(git rev-parse HEAD)\" &&\n \ttest \"$(git rev-parse c1)\" = \"$(git rev-parse HEAD^1)\" &&\n \ttest \"$(git rev-parse c2)\" = \"$(git rev-parse HEAD^2)\" &&\n-- \ngitgitgadget\n\n"},{"id":"430430","messageId":"3d8df24677269ddc379e0bfe5a6acc7f1dfd4fee.1626536508.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.git.git.1626536507.gitgitgadget@gmail.com","subject":"[PATCH 9/9] pull: fix handling of multiple heads","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-17T15:41:47Z","receivedAt":"2021-07-17T15:42:05Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWith multiple heads, we should not allow rebasing or fast-forwarding.\nAlso, it seems wrong to have our can_ff computation return true, so fix\nthat while we are at it too (we won't actually use the can_ff flag due\nto setting opt_ff to \"--no-ff\", but it's confusing to leave it as\ncomputed to be true).\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/pull.c               | 20 ++++++++++++++++----\n t/t7601-merge-pull-config.sh |  4 ++--\n 2 files changed, 18 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 3a61b92f328..beaf6ee0653 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -913,12 +913,18 @@ static int run_rebase(const struct object_id *newbase,\n \treturn ret;\n }\n \n-static int get_can_ff(struct object_id *orig_head, struct object_id *orig_merge_head)\n+static int get_can_ff(struct object_id *orig_head,\n+\t\t      struct oid_array *merge_heads)\n {\n \tint ret;\n \tstruct commit_list *list = NULL;\n \tstruct commit *merge_head, *head;\n+\tstruct object_id *orig_merge_head;\n \n+\tif (merge_heads->nr > 1)\n+\t\treturn 0;\n+\n+\torig_merge_head = &merge_heads->oid[0];\n \thead = lookup_commit_reference(the_repository, orig_head);\n \tcommit_list_insert(head, &list);\n \tmerge_head = lookup_commit_reference(the_repository, orig_merge_head);\n@@ -1046,10 +1052,16 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \t\t\tdie(_(\"Cannot merge multiple branches into empty head.\"));\n \t\treturn pull_into_void(merge_heads.oid, &curr_head);\n \t}\n-\tif (opt_rebase && merge_heads.nr > 1)\n-\t\tdie(_(\"Cannot rebase onto multiple branches.\"));\n+\tif (merge_heads.nr > 1) {\n+\t\tif (opt_rebase)\n+\t\t\tdie(_(\"Cannot rebase onto multiple branches.\"));\n+\t\tif (opt_ff && !strcmp(opt_ff, \"--ff-only\"))\n+\t\t\tdie(_(\"Cannot fast-forward to multiple branches.\"));\n+\t\tif (!opt_ff)\n+\t\t\topt_ff = \"--no-ff\";\n+\t}\n \n-\tcan_ff = get_can_ff(&orig_head, &merge_heads.oid[0]);\n+\tcan_ff = get_can_ff(&orig_head, &merge_heads);\n \n \t/* ff-only takes precedence over rebase */\n \tif (opt_ff && !strcmp(opt_ff, \"--ff-only\")) {\ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex 673b92afbab..29105b5b1ed 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -297,7 +297,7 @@ test_expect_success 'pull.rebase=true takes precedence over --ff' '\n \n # End of precedence rules\n \n-test_expect_failure 'Multiple heads does not warn about fast forwarding' '\n+test_expect_success 'Multiple heads does not warn about fast forwarding' '\n \tgit reset --hard c1 &&\n \tgit pull . c2 c3 2>err &&\n \ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n@@ -307,7 +307,7 @@ test_expect_success 'Cannot fast-forward with multiple heads' '\n \tgit reset --hard c0 &&\n \ttest_must_fail git -c pull.ff=only pull . c1 c2 c3 2>err &&\n \ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err &&\n-\ttest_i18ngrep \"Not possible to fast-forward, aborting\" err\n+\ttest_i18ngrep \"Cannot fast-forward to multiple branches\" err\n '\n \n test_expect_success 'Cannot rebase with multiple heads' '\n-- \ngitgitgadget\n"},{"id":"430436","messageId":"60f31b54b0429_25f2208b9@natae.notmuch","threadId":"56121","inReplyTo":"6cb771297f5f7d5bb0c6734bcb3fe6d3b8bb4c88.1626536508.git.gitgitgadget@gmail.com","subject":"RE: [PATCH 1/9] t7601: add relative precedence tests for merge and rebase flags/options","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-17T18:03:00Z","receivedAt":"2021-07-17T18:03:04Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Elijah Newren via GitGitGadget wrote:\n> From: Elijah Newren <newren@gmail.com>\n> \n> The interaction of rebase and merge flags and options was not well\n> tested.  Add several tests to check for correct behavior from the\n> following rules:\n>     * --ff-only takes precedence over --[no-]rebase\n>       * Corollary: pull.ff=only overrides pull.rebase\n>     * --rebase[=!false] takes precedence over --no-ff and --ff\n>       * Corollary: pull.rebase=!false overrides pull.ff=!only\n>     * command line flags take precedence over config, except:\n>       * --no-rebase heeds pull.ff=!only\n>       * pull.rebase=!false takes precedence over --no-ff and --ff\n\nThis is wrong, --ff-only is meant only for merge, not rebase.\n\nYou are testing for behavior that 1) has not been agreed to, and 2) is\nnot documented.\n\nThis is what the current documentation says about all the --*ff*\noptions:\n\n  --ff::\n  --no-ff::\n  --ff-only::\n    Specifies how a merge is handled when the merged-in history is\n    already a descendant of the current history.  `--ff` is the\n    default unless merging an annotated (and possibly signed) tag\n    that is not stored in its natural place in the `refs/tags/`\n    hierarchy, in which case `--no-ff` is assumed.\n  +\n  With `--ff`, when possible resolve the merge as a fast-forward (only\n  update the branch pointer to match the merged branch; do not create a\n  merge commit).  When not possible (when the merged-in history is not a\n  descendant of the current history), create a merge commit.\n  +\n  With `--no-ff`, create a merge commit in all cases, even when the merge\n  could instead be resolved as a fast-forward.\n  +\n  With `--ff-only`, resolve the merge as a fast-forward when possible.\n  When not possible, refuse to merge and exit with a non-zero status.\n\n-- \nFelipe Contreras\n"},{"id":"430437","messageId":"60f31bc6a42f8_25f2208b4@natae.notmuch","threadId":"56121","inReplyTo":"329802382bfa24241c2333bd38284aa77e3eb9f0.1626536508.git.gitgitgadget@gmail.com","subject":"RE: [PATCH 2/9] t7601: add tests of interactions with multiple merge heads and config","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-17T18:04:54Z","receivedAt":"2021-07-17T18:05:01Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Elijah Newren via GitGitGadget wrote:\n> From: Elijah Newren <newren@gmail.com>\n> \n> There were already code checking that --rebase was incompatible with\n> a merge of multiple heads.  However, we were sometimes throwing warnings\n> about lack of specification of rebase vs. merge when given multiple\n> heads.  Since rebasing is disallowed with multiple merge heads, that\n> seems like a poor warning to print; we should instead just assume\n> merging is wanted.\n\nWhere is that explained in the documentation?\n\n-- \nFelipe Contreras\n"},{"id":"430438","messageId":"60f31e1a928f2_25f2208cb@natae.notmuch","threadId":"56121","inReplyTo":"ae54afd8b0118aa1e2b90503aee75a8fddecae1a.1626536508.git.gitgitgadget@gmail.com","subject":"RE: [PATCH 3/9] pull: abort if --ff-only is given and fast-forwarding is impossible","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-17T18:14:50Z","receivedAt":"2021-07-17T18:14:59Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Alex Henrie via GitGitGadget wrote:\n> From: Alex Henrie <alexhenrie24@gmail.com>\n> \n> The warning about pulling without specifying how to reconcile divergent\n> branches says that after setting pull.rebase to true, --ff-only can\n> still be passed on the command line to require a fast-forward. Make that\n> actually work.\n\nOnce again, this behavior is not explained in the documentation.\n\nAnd this breaks existing behavior.\n\nA person currently can configure `pull.ff=only` to tell git that by\ndefault she wants `git pull` to fail if the merge is not a fast-forward,\nbut she can override that default behavior with `git pull --rebase`.\n\nNow that fails as well.\n\nSo even though the warning has been saying that users need to specify if\nto merge or rebase, and the user has specified a rebase, it now fails,\nso it's a step backwards.\n\n-- \nFelipe Contreras\n"},{"id":"430439","messageId":"60f31fedbbd67_25f2208d@natae.notmuch","threadId":"56121","inReplyTo":"de4b460b09d3a3b6848f9f9eaa5520b31a3b453a.1626536508.git.gitgitgadget@gmail.com","subject":"RE: [PATCH 4/9] pull: since --ff-only overrides, handle it first","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-17T18:22:37Z","receivedAt":"2021-07-17T18:22:41Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Elijah Newren via GitGitGadget wrote:\n> From: Elijah Newren <newren@gmail.com>\n> \n> There are both merge and rebase branches in the logic, and previously\n> both had to handle fast-forwarding.  Merge handled that implicitly\n> (because git merge handles it directly), while in rebase it was\n> explicit.  Given that the --ff-only flag is meant to override any\n> --rebase or --no-rebase, make the code reflect that by handling\n> --ff-only before the merge-vs-rebase logic.\n> \n> No functional changes, just making it easier to verify that the codeflow\n> matches our precedence rules.\n\nBut it does have a functional changes. Now you are calling run_merge()\nwithout update_submodules().\n\n-- \nFelipe Contreras\n"},{"id":"430440","messageId":"60f3209797d62_25f2208d2@natae.notmuch","threadId":"56121","inReplyTo":"3d9ff69198e5a604b124bf861df4d6ecf6eb661e.1626536508.git.gitgitgadget@gmail.com","subject":"RE: [PATCH 5/9] pull: ensure --rebase overrides ability to ff","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-17T18:25:27Z","receivedAt":"2021-07-17T18:25:31Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Elijah Newren via GitGitGadget wrote:\n> From: Elijah Newren <newren@gmail.com>\n> \n> Now that the handling of fast-forward-only in combination with rebases\n> has been moved before the merge-vs-rebase logic, we have an unnecessary\n> special fast-forward case left within the rebase logic.  Actually, more\n> than unnecessary, it's actually a violation of the rules.  As per\n> https://lore.kernel.org/git/xmqqwnpqot4m.fsf@gitster.g/, --rebase is\n> supposed to override all ff flags other than an explicit --ff-only.\n> Ensure that it does so by removing the fast-forward special case that\n> exists within the rebase logic.\n\nWhat Junio said in one mail are not the rules.\n\nThis goes against what is described in the documentation.\n\n-- \nFelipe Contreras\n"},{"id":"430441","messageId":"60f3215c9f3ce_25f22081c@natae.notmuch","threadId":"56121","inReplyTo":"b379fea097d65a28f1791f7f2f9432b6689a977f.1626536508.git.gitgitgadget@gmail.com","subject":"RE: [PATCH 6/9] pull: make --rebase and --no-rebase override pull.ff=only","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-17T18:28:44Z","receivedAt":"2021-07-17T18:28:48Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Elijah Newren via GitGitGadget wrote:\n> From: Elijah Newren <newren@gmail.com>\n> \n> Fix the last few precedence tests failing in t7601 by now implementing\n> the logic to have --[no-]rebase override a pull.ff=only config setting.\n\nThis is a semantic change that breaks current behavior.\n\nNow this does something different:\n\n  git -c pull.ff=only pull --merge\n\n*And* the new behavior is not documented anywhere.\n\n-- \nFelipe Contreras\n"},{"id":"430442","messageId":"60f321f2cae74_25f2208b5@natae.notmuch","threadId":"56121","inReplyTo":"dca0455898a838b46546a29ee79f0a8b112fbff5.1626536508.git.gitgitgadget@gmail.com","subject":"RE: [PATCH 7/9] pull: abort by default when fast-forwarding is not possible","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-17T18:31:14Z","receivedAt":"2021-07-17T18:31:20Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Elijah Newren via GitGitGadget wrote:\n> From: Elijah Newren <newren@gmail.com>\n> \n> We have for some time shown a long warning when the user does not\n> specify how to reconcile divergent branches with git pull.  Make it an\n> error now.\n\nOnce again. We have not warned the users of this upcoming change a\n*single* day. We have not told them how to turn the new behavior, and\nhow to keep the old behavior.\n\nIf you want to guarantee user complaints, this is the way to do it.\n\n-- \nFelipe Contreras\n"},{"id":"430443","messageId":"60f323c5e82e1_25f2208cf@natae.notmuch","threadId":"56121","inReplyTo":"pull.1049.git.git.1626536507.gitgitgadget@gmail.com","subject":"RE: [PATCH 0/9] Handle pull option precedence","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-17T18:39:01Z","receivedAt":"2021-07-17T18:39:17Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Elijah Newren via GitGitGadget wrote:\n> Based on a recent list of rules for flag/option precedence for git-pull[1]\n> from Junio (particularly focusing on rebase vs. merge vs. fast-forward),\n> here's an attempt to implement and document it. Given multiple recent\n> surprises from users about some of these behaviors[2][3] and a coworker just\n> yesterday expressing some puzzlement with git-pull and rebase vs. merge, it\n> seems like a good time to address some of these issues.\n> \n> Since the handling of conflicting options was holding up two of Alex's\n> patches[4][5], and his patches fix some of the tests, I also include those\n> two patches in my series, with a few small changes to the first (so I've\n> kept him as author) and more substantial changes to the second (so I've\n> given him an Initial-patch-by attribution).\n\nOnce again this is the wrong approach.\n\n  1. It's inconsistent with the current documentation\n  2. It treats configuration and command line arguments differently when\n     the documentation says they are the same\n  3. It breaks behavior users are currently relying on\n  4. It changes default behavior without warning users that such\n     behavior is going to change (0 days of deprecation)\n\n-- \nFelipe Contreras\n"},{"id":"430535","messageId":"xmqq35samac9.fsf@gitster.g","threadId":"56121","inReplyTo":"6cb771297f5f7d5bb0c6734bcb3fe6d3b8bb4c88.1626536508.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/9] t7601: add relative precedence tests for merge and rebase flags/options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-07-19T18:23:34Z","receivedAt":"2021-07-19T19:15:02Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +test_does_rebase() {\n\nStyle: missing SP before ().\n\n> +\tgit reset --hard c2 &&\n> +\tgit \"$@\" . c1 &&\n\nOK.\n\nIt is clear, from the \".\" (this repository), that this is designed\nto test nothing but \"git pull\", and it is somewhat unfortunate that\nwe cannot spell out 'git pull' here (instead we ask the callers to\nalways pass 'pull' subcommand from the command line), but that is\nunderstandable, as the primary reason we lack 'pull' from this\ncommand line is because we expect to pass one-shot configuration to\nthe command via \"-c pull.rebase=X\".\n\n> +\t# Check that we actually did a rebase\n> +\tgit rev-list --count HEAD >actual &&\n> +\tgit rev-list --merges --count HEAD >>actual &&\n> +\ttest_write_lines 3 0 >expect &&\n\nThe answer is 3 and zero.  OK.  The point of having this helper is\nnot because we want to do \"pull --rebase\" of different histories on\ntop of different base, but because we want to ensure that with\ndifferent configuration and command line options, the same \"pull\"\nwill result in the same flattening rebase.  So it is not a problem\nat all that these numbers are hardcoded (it might make it less\nfragile to count only commits above c2, but it probably would not\nmatter).\n\n> +\ttest_cmp expect actual &&\n> +\trm actual expect\n> +}\n\nOK.\n\n> +test_does_merge_noff() {\n> +\tgit reset --hard c0 &&\n> +\tgit \"$@\" . c1 &&\n> +\t# Check that we actually did a merge\n> +\tgit rev-list --count HEAD >actual &&\n> +\tgit rev-list --merges --count HEAD >>actual &&\n> +\ttest_write_lines 3 1 >expect &&\n> +\ttest_cmp expect actual &&\n> +\trm actual expect\n> +}\n> +\n> +test_does_merge_ff() {\n> +\tgit reset --hard c0 &&\n> +\tgit \"$@\" . c1 &&\n> +\t# Check that we actually did a merge\n> +\tgit rev-list --count HEAD >actual &&\n> +\tgit rev-list --merges --count HEAD >>actual &&\n> +\ttest_write_lines 2 0 >expect &&\n> +\ttest_cmp expect actual &&\n> +\trm actual expect\n> +}\n> +\n> +test_does_need_full_merge() {\n> +\tgit reset --hard c2 &&\n> +\tgit \"$@\" . c1 &&\n> +\t# Check that we actually did a merge\n> +\tgit rev-list --count HEAD >actual &&\n> +\tgit rev-list --merges --count HEAD >>actual &&\n> +\ttest_write_lines 4 1 >expect &&\n> +\ttest_cmp expect actual &&\n> +\trm actual expect\n> +}\n> +\n> +test_attempts_fast_forward() {\n> +\tgit reset --hard c2 &&\n> +\ttest_must_fail git \"$@\" . c1 2>err &&\n> +\ttest_i18ngrep \"Not possible to fast-forward, aborting\" err\n> +}\n\nThe same reasoning says these test_does_X helpers make sense.  I am\nnot sure about the name does_need_full_merge though---what does it\nwant to ensure is not very clear to me.  Is it named that way because\nyou found \"test_does_merge\" (when contrasted to \"test_does_merge_ff\")\nsounds too weak?\n\n> +#\n> +# Rule 1: --ff-only takes precedence over --[no-]rebase\n> +# (Corollary: pull.ff=only overrides pull.rebase)\n> +#\n> +test_expect_failure '--ff-only takes precedence over --rebase' '\n> +\ttest_attempts_fast_forward pull --rebase --ff-only\n> +'\n> +\n> +test_expect_failure '--ff-only takes precedence over --rebase even if first' '\n> +\ttest_attempts_fast_forward pull --ff-only --rebase\n> +'\n> +\n> +test_expect_success '--ff-only takes precedence over --no-rebase' '\n> +\ttest_attempts_fast_forward pull --ff-only --no-rebase\n> +'\n> +\n> +test_expect_failure 'pull.ff=only overrides pull.rebase=true' '\n> +\ttest_attempts_fast_forward -c pull.ff=only -c pull.rebase=true pull\n> +'\n> +\n> +test_expect_success 'pull.ff=only overrides pull.rebase=false' '\n> +\ttest_attempts_fast_forward -c pull.ff=only -c pull.rebase=false pull\n> +'\n\nOK.  These all ensure that when the history does not fast-forward,\nthe command will fail when --ff-only tells us to allow only\nfast-forward.  I am not sure \"takes precedence\" is a meaningful\nlabel, though.  It is more like \"ff-only means ff-only and fails\nwhen the upstream history is not a descendant, no matter how the\npossible integration is set to be performed\".\n\n> +# Rule 2: --rebase=[!false] takes precedence over --no-ff and --ff\n> +# (Corollary: pull.rebase=!false overrides pull.ff=!only)\n> +test_expect_success '--rebase takes precedence over --no-ff' '\n> +\ttest_does_rebase pull --rebase --no-ff\n> +'\n> +\n> +test_expect_success '--rebase takes precedence over --ff' '\n> +\ttest_does_rebase pull --rebase --ff\n> +'\n> +\n> +test_expect_success 'pull.rebase=true takes precedence over pull.ff=false' '\n> +\ttest_does_rebase -c pull.rebase=true -c pull.ff=false pull\n> +'\n> +\n> +test_expect_success 'pull.rebase=true takes precedence over pull.ff=true' '\n> +\ttest_does_rebase -c pull.rebase=true -c pull.ff=true pull\n> +'\n\nSounds sensible.  Again, I do not view this as precedence, though.\n\"--ff\" is merely \"if there is nothing else needs to be done, it is\nOK to fast-forward to their history\", so with --rebase, it either\n(1) gets ignored when we have something to be done, i.e. our own\ndevelopment to replay on top of their history, or (2) becomes a\nno-op as there truly isn't any development of our own.\n\nAnd \"--no-ff\" is more or less a meaningless thing to say (\"I do not\nwant to just fast-forward when I do not have anything meaningful to\nadd, I want an empty merge commit to mark where I was\") in the\ncontext of \"--rebase\".  Erroring out only when their histroy is\ndescendant of ours and \"--no-ff\" and \"--rebase=<set>\" are given\nexplicitly from the command line might make sense, but I do not\nthink of a sensible corrective action the end-user wants to do after\nseeing such an error (after all, there was nothing to rebase on top\nof their history), so I think ignoring is a more acceptable outcome\nwhen we have nothing to replay.\n\nDo we ensure that \"pull --rebase --ff\" fast-forwards when the\nhistory truly fast-forwards?  test_does_rebase only and always\nchecks what happens when pulling c1 into c2 and nothing else, so I\ndo not think the above are testing that case.\n\nIOW, I think \"test_does_merge_ff pull --rebase --ff\" wants to be\nthere somewhere?\n\n> +# Rule 3: command line flags take precedence over config\n> +test_expect_failure '--ff-only takes precedence over pull.rebase=true' '\n> +\ttest_attempts_fast_forward -c pull.rebase=true pull --ff-only\n> +'\n> +\n> +test_expect_success '--ff-only takes precedence over pull.rebase=false' '\n> +\ttest_attempts_fast_forward -c pull.rebase=false pull --ff-only\n> +'\n\nBoth are good.\n\n> +test_expect_failure '--no-rebase overrides pull.ff=only' '\n> +\ttest_does_need_full_merge -c pull.ff=only pull --no-rebase\n> +'\n> +\n> +test_expect_success '--rebase takes precedence over pull.ff=only' '\n> +\ttest_does_rebase -c pull.ff=only pull --rebase\n> +'\n\nOK.\n\n> +test_expect_success '--rebase takes precedence over pull.ff=true' '\n> +\ttest_does_rebase -c pull.ff=true pull --rebase\n> +'\n> +\n> +test_expect_success '--rebase takes precedence over pull.ff=false' '\n> +\ttest_does_rebase -c pull.ff=false pull --rebase\n> +'\n> +\n> +test_expect_success '--rebase takes precedence over pull.ff unset' '\n> +\ttest_does_rebase pull --rebase\n> +'\n\nThese three are correct but again I do not see them as precedence\nmatter.\n\n> +# Rule 4: --no-rebase heeds pull.ff=!only or explict --ff or --no-ff\n> +\n> +test_expect_success '--no-rebase works with --no-ff' '\n> +\ttest_does_merge_noff pull --no-rebase --no-ff\n> +'\n\nOK.\n\n> +test_expect_success '--no-rebase works with --ff' '\n> +\ttest_does_merge_ff pull --no-rebase --ff\n> +'\n\nOK.\n\n> +test_expect_success '--no-rebase does ff if pull.ff unset' '\n> +\ttest_does_merge_ff pull --no-rebase\n> +'\n\nOK.\n\n> +test_expect_success '--no-rebase heeds pull.ff=true' '\n> +\ttest_does_merge_ff -c pull.ff=true pull --no-rebase\n> +'\n\nOK (pull.ff=true is the default anyway).\n\n> +test_expect_success '--no-rebase heeds pull.ff=false' '\n> +\ttest_does_merge_noff -c pull.ff=false pull --no-rebase\n> +'\n\nOK.\n\n> +# Rule 5: pull.rebase=!false takes precedence over --no-ff and --ff\n> +test_expect_success 'pull.rebase=true takes precedence over --no-ff' '\n> +\ttest_does_rebase -c pull.rebase=true pull --no-ff\n> +'\n\nOK.  When we do have our own commits they do not have, there is no\npoint in letting --no-ff do anything special.  It would be sensible\nto replay ours on top of theirs.\n\n\n> +test_expect_success 'pull.rebase=true takes precedence over --ff' '\n> +\ttest_does_rebase -c pull.rebase=true pull --ff\n> +'\n\nAgain, I am not sure if this is \"precedence\" issue.  \"ff\" merely\nmeans \"fast-forwarding is allowed, when we do not have anything to\nadd to their history\", and we do have our own work in the test\nscenario test_does_rebase presents us, so rebasing would be quite\nsensible.  Similarly\n\n    test_does_need_full_merge -c pull.rebase=false pull --ff\n\nwould be true, right?\n\n> +# End of precedence rules\n> +\n>  test_expect_success 'merge c1 with c2' '\n>  \tgit reset --hard c1 &&\n>  \ttest -f c0.c &&\n\nThe series of new tests makes me wonder if there is a good way to\nensure we covered full matrix, but I didn't see any that smelled\nwrong.\n\nThanks.\n\n"},{"id":"430667","messageId":"CABPp-BFMHhxgaLnA+gLHJENVfzVv6j2Ax4G6y+XsrU3wBJq6rQ@mail.gmail.com","threadId":"56121","inReplyTo":"xmqq35samac9.fsf@gitster.g","subject":"Re: [PATCH 1/9] t7601: add relative precedence tests for merge and rebase flags/options","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-07-20T17:10:58Z","receivedAt":"2021-07-20T17:13:29Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Mon, Jul 19, 2021 at 11:23 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > +test_does_rebase() {\n>\n> Style: missing SP before ().\n\nWill fix.\n\n...\n> > +test_does_merge_noff() {\n> > +     git reset --hard c0 &&\n> > +     git \"$@\" . c1 &&\n> > +     # Check that we actually did a merge\n> > +     git rev-list --count HEAD >actual &&\n> > +     git rev-list --merges --count HEAD >>actual &&\n> > +     test_write_lines 3 1 >expect &&\n> > +     test_cmp expect actual &&\n> > +     rm actual expect\n> > +}\n> > +\n> > +test_does_merge_ff() {\n> > +     git reset --hard c0 &&\n> > +     git \"$@\" . c1 &&\n> > +     # Check that we actually did a merge\n> > +     git rev-list --count HEAD >actual &&\n> > +     git rev-list --merges --count HEAD >>actual &&\n> > +     test_write_lines 2 0 >expect &&\n> > +     test_cmp expect actual &&\n> > +     rm actual expect\n> > +}\n> > +\n> > +test_does_need_full_merge() {\n> > +     git reset --hard c2 &&\n> > +     git \"$@\" . c1 &&\n> > +     # Check that we actually did a merge\n> > +     git rev-list --count HEAD >actual &&\n> > +     git rev-list --merges --count HEAD >>actual &&\n> > +     test_write_lines 4 1 >expect &&\n> > +     test_cmp expect actual &&\n> > +     rm actual expect\n> > +}\n> > +\n> > +test_attempts_fast_forward() {\n> > +     git reset --hard c2 &&\n> > +     test_must_fail git \"$@\" . c1 2>err &&\n> > +     test_i18ngrep \"Not possible to fast-forward, aborting\" err\n> > +}\n>\n> The same reasoning says these test_does_X helpers make sense.  I am\n> not sure about the name does_need_full_merge though---what does it\n> want to ensure is not very clear to me.  Is it named that way because\n> you found \"test_does_merge\" (when contrasted to \"test_does_merge_ff\")\n> sounds too weak?\n\nI should probably rename the functions for clarity; I'm testing the\nmatrix of ff/noff possibilities:\n\n# Prefers merge over fast-forward; rename from test_does_merge\ntest_does_merge_when_ff_possible ()\n\n# Prefers fast-forward over merge; rename from test_does_merge_ff\ntest_does_fast_forward ()\n\n# Attempts fast forward and bails since it is impossible\ntest_attempts_fast_forward ()\n\n# Does a merge when a fast forward is not possible\ntest_falls_back_to_full_merge ()\n\n\nIn particular, `test_does_need_full_merge` would become\n`test_falls_back_to_full_merge`\n\n\n> > +#\n> > +# Rule 1: --ff-only takes precedence over --[no-]rebase\n> > +# (Corollary: pull.ff=only overrides pull.rebase)\n> > +#\n> > +test_expect_failure '--ff-only takes precedence over --rebase' '\n> > +     test_attempts_fast_forward pull --rebase --ff-only\n> > +'\n> > +\n> > +test_expect_failure '--ff-only takes precedence over --rebase even if first' '\n> > +     test_attempts_fast_forward pull --ff-only --rebase\n> > +'\n> > +\n> > +test_expect_success '--ff-only takes precedence over --no-rebase' '\n> > +     test_attempts_fast_forward pull --ff-only --no-rebase\n> > +'\n> > +\n> > +test_expect_failure 'pull.ff=only overrides pull.rebase=true' '\n> > +     test_attempts_fast_forward -c pull.ff=only -c pull.rebase=true pull\n> > +'\n> > +\n> > +test_expect_success 'pull.ff=only overrides pull.rebase=false' '\n> > +     test_attempts_fast_forward -c pull.ff=only -c pull.rebase=false pull\n> > +'\n>\n> OK.  These all ensure that when the history does not fast-forward,\n> the command will fail when --ff-only tells us to allow only\n> fast-forward.  I am not sure \"takes precedence\" is a meaningful\n> label, though.  It is more like \"ff-only means ff-only and fails\n> when the upstream history is not a descendant, no matter how the\n> possible integration is set to be performed\".\n\nSo, I think you're saying you view fast-forwards as a subset of valid\nrebases (and fast-forwards are also a subset of valid rmerges), and\nthus you view --ff-only --rebase as an instruction to only proceed if\nboth command line flags can be satisfied.\n\nThat makes sense, but I don't know how to put that into a test\ndescription that isn't ridiculously long.  I tried replacing \"takes\nprecedence over\" with just \"overrides\" but you might not like that\neither.  If you've got better wording for the comments before each\ngroup and the test descriptions, I'm all ears.  Otherwise I'll just\ntake my best stab at it.\n\n> > +# Rule 2: --rebase=[!false] takes precedence over --no-ff and --ff\n> > +# (Corollary: pull.rebase=!false overrides pull.ff=!only)\n> > +test_expect_success '--rebase takes precedence over --no-ff' '\n> > +     test_does_rebase pull --rebase --no-ff\n> > +'\n> > +\n> > +test_expect_success '--rebase takes precedence over --ff' '\n> > +     test_does_rebase pull --rebase --ff\n> > +'\n> > +\n> > +test_expect_success 'pull.rebase=true takes precedence over pull.ff=false' '\n> > +     test_does_rebase -c pull.rebase=true -c pull.ff=false pull\n> > +'\n> > +\n> > +test_expect_success 'pull.rebase=true takes precedence over pull.ff=true' '\n> > +     test_does_rebase -c pull.rebase=true -c pull.ff=true pull\n> > +'\n>\n> Sounds sensible.  Again, I do not view this as precedence, though.\n> \"--ff\" is merely \"if there is nothing else needs to be done, it is\n> OK to fast-forward to their history\", so with --rebase, it either\n> (1) gets ignored when we have something to be done, i.e. our own\n> development to replay on top of their history, or (2) becomes a\n> no-op as there truly isn't any development of our own.\n>\n> And \"--no-ff\" is more or less a meaningless thing to say (\"I do not\n> want to just fast-forward when I do not have anything meaningful to\n> add, I want an empty merge commit to mark where I was\") in the\n> context of \"--rebase\".  Erroring out only when their histroy is\n> descendant of ours and \"--no-ff\" and \"--rebase=<set>\" are given\n> explicitly from the command line might make sense, but I do not\n> think of a sensible corrective action the end-user wants to do after\n> seeing such an error (after all, there was nothing to rebase on top\n> of their history), so I think ignoring is a more acceptable outcome\n> when we have nothing to replay.\n>\n> Do we ensure that \"pull --rebase --ff\" fast-forwards when the\n> history truly fast-forwards?  test_does_rebase only and always\n> checks what happens when pulling c1 into c2 and nothing else, so I\n> do not think the above are testing that case.\n>\n> IOW, I think \"test_does_merge_ff pull --rebase --ff\" wants to be\n> there somewhere?\n\nSounds good; I added \"--rebase --ff fast-forwards when possible\" test.\n\n...\n> > +test_expect_success '--rebase takes precedence over pull.ff=true' '\n> > +     test_does_rebase -c pull.ff=true pull --rebase\n> > +'\n> > +\n> > +test_expect_success '--rebase takes precedence over pull.ff=false' '\n> > +     test_does_rebase -c pull.ff=false pull --rebase\n> > +'\n> > +\n> > +test_expect_success '--rebase takes precedence over pull.ff unset' '\n> > +     test_does_rebase pull --rebase\n> > +'\n>\n> These three are correct but again I do not see them as precedence\n> matter.\n>\n...\n>\n> > +test_expect_success 'pull.rebase=true takes precedence over --ff' '\n> > +     test_does_rebase -c pull.rebase=true pull --ff\n> > +'\n>\n> Again, I am not sure if this is \"precedence\" issue.  \"ff\" merely\n> means \"fast-forwarding is allowed, when we do not have anything to\n> add to their history\", and we do have our own work in the test\n> scenario test_does_rebase presents us, so rebasing would be quite\n> sensible.  Similarly\n>\n>     test_does_need_full_merge -c pull.rebase=false pull --ff\n>\n> would be true, right?\n\nYep, I added that test since you thought to ask to make sure it's covered.\n\n> > +# End of precedence rules\n> > +\n> >  test_expect_success 'merge c1 with c2' '\n> >       git reset --hard c1 &&\n> >       test -f c0.c &&\n>\n> The series of new tests makes me wonder if there is a good way to\n> ensure we covered full matrix, but I didn't see any that smelled\n> wrong.\n\nFair question.  I covered all the ones in\nhttps://lore.kernel.org/git/xmqqwnpqot4m.fsf@gitster.g/, except that\nwhen '*' was given, I might have just picked a representative example\nrather than all possible options.  I also just used --rebase as a\nproxy for any --rebase=* option other than --rebase=false.  Of course,\nthat table may have been incomplete but it at least covered the ones I\ncould think of.\n"},{"id":"430676","messageId":"xmqq35s8j15h.fsf@gitster.g","threadId":"56121","inReplyTo":"CABPp-BFMHhxgaLnA+gLHJENVfzVv6j2Ax4G6y+XsrU3wBJq6rQ@mail.gmail.com","subject":"Re: [PATCH 1/9] t7601: add relative precedence tests for merge and rebase flags/options","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-07-20T18:22:34Z","receivedAt":"2021-07-20T18:26:49Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n>> OK.  These all ensure that when the history does not fast-forward,\n>> the command will fail when --ff-only tells us to allow only\n>> fast-forward.  I am not sure \"takes precedence\" is a meaningful\n>> label, though.  It is more like \"ff-only means ff-only and fails\n>> when the upstream history is not a descendant, no matter how the\n>> possible integration is set to be performed\".\n>\n> So, I think you're saying you view fast-forwards as a subset of valid\n> rebases (and fast-forwards are also a subset of valid rmerges), and\n> thus you view --ff-only --rebase as an instruction to only proceed if\n> both command line flags can be satisfied.\n\nAh, I didn't think of it myself, but now you put it in these words,\nI do agree that the view makes sense.  When we have nothing of our\nown, a degenerated form of a rebase is a fast-forward, even more so\nthan a fast-forward being a degenerated form of a merge.\n\n> That makes sense, but I don't know how to put that into a test\n> description that isn't ridiculously long.\n\nMe neither.  Let's not waste too much brain-cycles over this.\n\nThanks.\n"},{"id":"430694","messageId":"60f73213233d7_14a799208f5@natae.notmuch","threadId":"56121","inReplyTo":"xmqq35samac9.fsf@gitster.g","subject":"Re: [PATCH 1/9] t7601: add relative precedence tests for merge and rebase flags/options","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-20T20:29:07Z","receivedAt":"2021-07-20T20:43:25Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Junio C Hamano wrote:\n> \"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> > +test_expect_failure '--no-rebase overrides pull.ff=only' '\n> > +\ttest_does_need_full_merge -c pull.ff=only pull --no-rebase\n> > +'\n> > +\n> > +test_expect_success '--rebase takes precedence over pull.ff=only' '\n> > +\ttest_does_rebase -c pull.ff=only pull --rebase\n> > +'\n> \n> OK.\n\nSince when is it OK to test for a new behavior that 1. will break\ncompatibility, 2. is not documented, and 3. has zero consensus?\n\n-- \nFelipe Contreras\n"},{"id":"430711","messageId":"xmqq4kcoh97y.fsf@gitster.g","threadId":"56121","inReplyTo":"329802382bfa24241c2333bd38284aa77e3eb9f0.1626536508.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 2/9] t7601: add tests of interactions with multiple merge heads and config","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-07-20T23:11:13Z","receivedAt":"2021-07-20T23:11:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +test_expect_failure 'Multiple heads does not warn about fast forwarding' '\n> +\tgit reset --hard c1 &&\n> +\tgit pull . c2 c3 2>err &&\n> +\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n> +'\n\nThis does not look like \"warning about fast-forwarding\".\n\nBut more importantly, are we sure we want to expect this outcome?\n\nWe are at c1 and try to integrate with c2 and c3 at the same time,\nneither of which is a descendant of c1.\n\nWe know that the only possible action is to create an octopus in\nthis case, and that it is pretty much fundamental (i.e. it is not\nlike \"rebase\" with further development will be able to handle this\ncase).  I however do not know if it is also obvious to total newbies\nwho haven't even chosen between merge and rebase.  I can see them\ncomplaining \"why didn't I get asked to choose between rebase and\nmerge\" if we went ahead and created an octopus merge, especially the\nones who would choose pull.rebase=yes once they learned Git a bit\nmore.\n\n> +test_expect_success 'Cannot fast-forward with multiple heads' '\n> +\tgit reset --hard c0 &&\n> +\ttest_must_fail git -c pull.ff=only pull . c1 c2 c3 2>err &&\n> +\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err &&\n> +\ttest_i18ngrep \"Not possible to fast-forward, aborting\" err\n> +'\n\nThis one looks sensible to me.\n\n> +test_expect_success 'Cannot rebase with multiple heads' '\n> +\tgit reset --hard c0 &&\n> +\ttest_must_fail git -c pull.rebase=true pull . c1 c2 c3 2>err &&\n> +\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err &&\n> +\ttest_i18ngrep \"Cannot rebase onto multiple branches.\" err\n> +'\n\nThis one, too.\n\nThanks.\n"},{"id":"430712","messageId":"xmqqwnpkftjv.fsf@gitster.g","threadId":"56121","inReplyTo":"3d9ff69198e5a604b124bf861df4d6ecf6eb661e.1626536508.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 5/9] pull: ensure --rebase overrides ability to ff","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-07-20T23:35:00Z","receivedAt":"2021-07-20T23:39:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Elijah Newren <newren@gmail.com>\n>\n> Now that the handling of fast-forward-only in combination with rebases\n> has been moved before the merge-vs-rebase logic, we have an unnecessary\n> special fast-forward case left within the rebase logic.\n\nIt is correct to say that we could call run_rebase() and it will do\nthe right thing, even when can_ff is true, in this codepath.\n\nBut I am not sure if you want to do this as a part of this series.\n\nThe code in question comes from 33b842a1 (pull: fast-forward \"pull\n--rebase=true\", 2016-06-29).  It was meant as a mere optimization\nto avoid calling run_rebase() when we know we have nothing to replay\non top of their head after we fast-forward, and doing a pure\nfast-forward is easier by calling run_merge().\n\nIn other words, the code this patch touches should not have anything\nto do with the rebase-vs-fast-forward logic.\n\nNow, if a benchmarking tells us that there is no difference between\ncalling an empty run_rebase() and run_merge(), I'd be perfectly fine\nwith this change, with the justification that we are \"discarding an\nearlier optimization that has become irrelevant\".  But that would be\ntotally outside the theme of this topic, I would think.\n\nThanks.\n\n> @@ -1070,13 +1070,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n>  \t\t    submodule_touches_in_range(the_repository, &upstream, &curr_head))\n>  \t\t\tdie(_(\"cannot rebase with locally recorded submodule modifications\"));\n>  \n> -\t\tif (can_ff) {\n> -\t\t\t/* we can fast-forward this without invoking rebase */\n> -\t\t\topt_ff = \"--ff-only\";\n> -\t\t\tret = run_merge();\n> -\t\t} else {\n> -\t\t\tret = run_rebase(&newbase, &upstream);\n> -\t\t}\n> +\t\tret = run_rebase(&newbase, &upstream);\n>  \n>  \t\tif (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON ||\n>  \t\t\t     recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND))\n> diff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\n> index e2c0c510222..4b50488141f 100755\n> --- a/t/t5520-pull.sh\n> +++ b/t/t5520-pull.sh\n> @@ -295,7 +295,7 @@ test_expect_success '--rebase (merge) fast forward' '\n>  \t# The above only validates the result.  Did we actually bypass rebase?\n>  \tgit reflog -1 >reflog.actual &&\n>  \tsed \"s/^[0-9a-f][0-9a-f]*/OBJID/\" reflog.actual >reflog.fuzzy &&\n> -\techo \"OBJID HEAD@{0}: pull --rebase . ff: Fast-forward\" >reflog.expected &&\n> +\techo \"OBJID HEAD@{0}: pull --rebase . ff (finish): returning to refs/heads/to-rebase\" >reflog.expected &&\n>  \ttest_cmp reflog.expected reflog.fuzzy\n>  '\n>  \n> @@ -307,8 +307,8 @@ test_expect_success '--rebase (am) fast forward' '\n>  \n>  \t# The above only validates the result.  Did we actually bypass rebase?\n>  \tgit reflog -1 >reflog.actual &&\n> -\tsed \"s/^[0-9a-f][0-9a-f]*/OBJID/\" reflog.actual >reflog.fuzzy &&\n> -\techo \"OBJID HEAD@{0}: pull --rebase . ff: Fast-forward\" >reflog.expected &&\n> +\tsed -e \"s/^[0-9a-f][0-9a-f]*/OBJID/\" -e \"s/[0-9a-f][0-9a-f]*$/OBJID/\" reflog.actual >reflog.fuzzy &&\n> +\techo \"OBJID HEAD@{0}: rebase finished: refs/heads/to-rebase onto OBJID\" >reflog.expected &&\n>  \ttest_cmp reflog.expected reflog.fuzzy\n>  '\n"},{"id":"430713","messageId":"xmqqpmvcfsol.fsf@gitster.g","threadId":"56121","inReplyTo":"b379fea097d65a28f1791f7f2f9432b6689a977f.1626536508.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 6/9] pull: make --rebase and --no-rebase override pull.ff=only","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-07-20T23:53:46Z","receivedAt":"2021-07-20T23:54:08Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Elijah Newren <newren@gmail.com>\n>\n> Fix the last few precedence tests failing in t7601 by now implementing\n> the logic to have --[no-]rebase override a pull.ff=only config setting.\n\nBecause we only have opt_rebase and opt_ff, unless we are in a very\nearly part of the code, we cannot tell if \"opt_rebase >= 0\" we see\ncame from the command line or from the configuration, and because\npull.rebase, pull.ff, --rebase=<kind>, and <--ff|--ff-only|--no-ff>\nhave intricate interactions among them, we'd probably need to behave\ndifferently depending on where \"opt_rebase >= 0\" came from.\n\nThe new code works only because we haven't called config_get_rebase()\nyet in this part.  If we swapped the order of \"if ff is not set,\nthen figure it out from the configuration\" (the code you are\nextending) and \"if rebase is not set, read from the configuration\",\nthen the logic would not work.\n\nThe resulting code looks correct, but it is subtle and may deserve\nan in-code comment.\n\n> Signed-off-by: Elijah Newren <newren@gmail.com>\n> ---\n>  builtin/pull.c               | 5 ++++-\n>  t/t7601-merge-pull-config.sh | 4 ++--\n>  2 files changed, 6 insertions(+), 3 deletions(-)\n>\n> diff --git a/builtin/pull.c b/builtin/pull.c\n> index 5ba376a7487..7e7c90f6a10 100644\n> --- a/builtin/pull.c\n> +++ b/builtin/pull.c\n> @@ -966,8 +966,11 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n>  \n>  \tparse_repo_refspecs(argc, argv, &repo, &refspecs);\n>  \n> -\tif (!opt_ff)\n> +\tif (!opt_ff) {\n>  \t\topt_ff = xstrdup_or_null(config_get_ff());\n\n\t\t/*\n\t\t * Do we have --rebase=<kind> or --no-rebase from the\n\t\t * command line (note that we haven't read pull.rebase yet)?\n\t\t * If so, defeat the configured pull.ff=only; instead of\n\t\t * failing to integrate divergent histories, we want the\n\t\t * rebase or merge to happen.\n\t\t */\n\n> +\t\tif (opt_rebase >= 0 && opt_ff && !strcmp(opt_ff, \"--ff-only\"))\n> +\t\t\topt_ff = \"--ff\";\n> +\t}\n\nThanks.\n"},{"id":"430714","messageId":"60f765a9c777e_14ef2420835@natae.notmuch","threadId":"56121","inReplyTo":"xmqqpmvcfsol.fsf@gitster.g","subject":"Re: [PATCH 6/9] pull: make --rebase and --no-rebase override pull.ff=only","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-21T00:09:13Z","receivedAt":"2021-07-21T00:10:42Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Junio C Hamano wrote:\n> \"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > Fix the last few precedence tests failing in t7601 by now implementing\n> > the logic to have --[no-]rebase override a pull.ff=only config setting.\n> \n> Because we only have opt_rebase and opt_ff, unless we are in a very\n> early part of the code, we cannot tell if \"opt_rebase >= 0\" we see\n> came from the command line or from the configuration, and because\n> pull.rebase, pull.ff, --rebase=<kind>, and <--ff|--ff-only|--no-ff>\n> have intricate interactions among them, we'd probably need to behave\n> differently depending on where \"opt_rebase >= 0\" came from.\n\nWhere is that backwards-incompatible change explained in the\ndocumentation?\n\n-- \nFelipe Contreras\n"},{"id":"430715","messageId":"CABPp-BE334GU9fuMOKRNK+f5jqv=r2P7gQ5tOhtVLe1gtmNCqw@mail.gmail.com","threadId":"56121","inReplyTo":"xmqqwnpkftjv.fsf@gitster.g","subject":"Re: [PATCH 5/9] pull: ensure --rebase overrides ability to ff","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-07-21T00:14:54Z","receivedAt":"2021-07-21T00:15:22Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Jul 20, 2021 at 4:35 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > Now that the handling of fast-forward-only in combination with rebases\n> > has been moved before the merge-vs-rebase logic, we have an unnecessary\n> > special fast-forward case left within the rebase logic.\n>\n> It is correct to say that we could call run_rebase() and it will do\n> the right thing, even when can_ff is true, in this codepath.\n>\n> But I am not sure if you want to do this as a part of this series.\n\nIt turns out my commit message was wrong, and so was the patch.  My\nchanges did have a functional change, substituting one form of\nsubmodule breakage with another.\n\nI'll replace this with a patch that fixes the submodule issue.  (When\n--ff-only is specified, we should not rebase submodules.  We should\nfast-forward both the parent module and the submodules.)\n"},{"id":"430716","messageId":"xmqq5yx4frlc.fsf@gitster.g","threadId":"56121","inReplyTo":"d1952f014f20d2770c74a311df27f956c8b95e21.1626536508.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 8/9] pull: update docs & code for option compatibility with rebasing","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-07-21T00:17:19Z","receivedAt":"2021-07-21T00:17:34Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> From: Elijah Newren <newren@gmail.com>\n>\n> git-pull.txt includes merge-options.txt, which is written assuming\n> merges will happen.  git-pull has allowed rebases for many years; update\n> the documentation to reflect that.\n\nYes, as I might have said elsewhere recently, \"pull --rebase\" has\nbeen afterthought and the way how it interacts with many niche\n\"features\" of \"git pull\" has never been designed but started with\nwhatever the code happened to do and then fixed/tweaked as people\nfound it suboptimal.  Hopefully with this series we are covering\nthese interactions and whipping them into a much better shape with\ncoherent semantics.\n\n> diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\n> index cad3f6bfcee..6e6e95a7595 100644\n> --- a/Documentation/git-pull.txt\n> +++ b/Documentation/git-pull.txt\n> @@ -125,6 +125,8 @@ When false, merge the current branch into the upstream branch.\n>  +\n>  When `interactive`, enable the interactive mode of rebase.\n>  +\n> +Note that `--ff-only` takes precedence over any `--rebase` flag.\n> ++\n\n\"`ff-only` overrides any `--rebase` flag\"?\n\n> @@ -58,6 +61,10 @@ could instead be resolved as a fast-forward.\n>  +\n>  With `--ff-only`, resolve the merge as a fast-forward when possible.\n>  When not possible, refuse to merge and exit with a non-zero status.\n\nThis part may want to become conditional with git-pull[] to use a\nverb different than \"merge\"?\n\n> +ifdef::git-pull[]\n> ++\n> +Note that `--no-ff` and `--ff` are ignored when rebasing is requested.\n> +endif::git-pull[]\n\nOK.\n"},{"id":"430717","messageId":"CABPp-BGkiNfy4s9uxFWPYfqVRLj3rT10pJuARW9e3tPrkDbS9w@mail.gmail.com","threadId":"56121","inReplyTo":"xmqq5yx4frlc.fsf@gitster.g","subject":"Re: [PATCH 8/9] pull: update docs & code for option compatibility with rebasing","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-07-21T00:44:02Z","receivedAt":"2021-07-21T00:44:23Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Jul 20, 2021 at 5:17 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > From: Elijah Newren <newren@gmail.com>\n> >\n> > git-pull.txt includes merge-options.txt, which is written assuming\n> > merges will happen.  git-pull has allowed rebases for many years; update\n> > the documentation to reflect that.\n>\n> Yes, as I might have said elsewhere recently, \"pull --rebase\" has\n> been afterthought and the way how it interacts with many niche\n> \"features\" of \"git pull\" has never been designed but started with\n> whatever the code happened to do and then fixed/tweaked as people\n> found it suboptimal.  Hopefully with this series we are covering\n> these interactions and whipping them into a much better shape with\n> coherent semantics.\n>\n> > diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\n> > index cad3f6bfcee..6e6e95a7595 100644\n> > --- a/Documentation/git-pull.txt\n> > +++ b/Documentation/git-pull.txt\n> > @@ -125,6 +125,8 @@ When false, merge the current branch into the upstream branch.\n> >  +\n> >  When `interactive`, enable the interactive mode of rebase.\n> >  +\n> > +Note that `--ff-only` takes precedence over any `--rebase` flag.\n> > ++\n>\n> \"`ff-only` overrides any `--rebase` flag\"?\n\nYeah, ignore that, I had already ripped it out after your comments on\npatch 1 yesterday.  I've also got more changes to the pull\ndocumentation to try to do more of what your sentence above says,\n\"covering these interactions and whipping them into a much better\nshape.\"\n\n> > @@ -58,6 +61,10 @@ could instead be resolved as a fast-forward.\n> >  +\n> >  With `--ff-only`, resolve the merge as a fast-forward when possible.\n> >  When not possible, refuse to merge and exit with a non-zero status.\n>\n> This part may want to become conditional with git-pull[] to use a\n> verb different than \"merge\"?\n\nYep, that's one of the other changes I'm adding and will send out soon.\n\n> > +ifdef::git-pull[]\n> > ++\n> > +Note that `--no-ff` and `--ff` are ignored when rebasing is requested.\n> > +endif::git-pull[]\n>\n> OK.\n"},{"id":"430719","messageId":"CABPp-BFk--NUdSy2OpF1XfJhkrWu_ucFSxZxnu7M5gy1HHdwSg@mail.gmail.com","threadId":"56121","inReplyTo":"xmqq4kcoh97y.fsf@gitster.g","subject":"Re: [PATCH 2/9] t7601: add tests of interactions with multiple merge heads and config","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-07-21T00:45:18Z","receivedAt":"2021-07-21T00:45:42Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Tue, Jul 20, 2021 at 4:11 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > +test_expect_failure 'Multiple heads does not warn about fast forwarding' '\n> > +     git reset --hard c1 &&\n> > +     git pull . c2 c3 2>err &&\n> > +     test_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n> > +'\n>\n> This does not look like \"warning about fast-forwarding\".\n>\n> But more importantly, are we sure we want to expect this outcome?\n>\n> We are at c1 and try to integrate with c2 and c3 at the same time,\n> neither of which is a descendant of c1.\n>\n> We know that the only possible action is to create an octopus in\n> this case, and that it is pretty much fundamental (i.e. it is not\n> like \"rebase\" with further development will be able to handle this\n> case).  I however do not know if it is also obvious to total newbies\n> who haven't even chosen between merge and rebase.  I can see them\n> complaining \"why didn't I get asked to choose between rebase and\n> merge\" if we went ahead and created an octopus merge, especially the\n> ones who would choose pull.rebase=yes once they learned Git a bit\n> more.\n\nThat's a fair point; I'll modify the test accordingly (and update the\ndescription).\n\n> > +test_expect_success 'Cannot fast-forward with multiple heads' '\n> > +     git reset --hard c0 &&\n> > +     test_must_fail git -c pull.ff=only pull . c1 c2 c3 2>err &&\n> > +     test_i18ngrep ! \"Pulling without specifying how to reconcile\" err &&\n> > +     test_i18ngrep \"Not possible to fast-forward, aborting\" err\n> > +'\n>\n> This one looks sensible to me.\n>\n> > +test_expect_success 'Cannot rebase with multiple heads' '\n> > +     git reset --hard c0 &&\n> > +     test_must_fail git -c pull.rebase=true pull . c1 c2 c3 2>err &&\n> > +     test_i18ngrep ! \"Pulling without specifying how to reconcile\" err &&\n> > +     test_i18ngrep \"Cannot rebase onto multiple branches.\" err\n> > +'\n>\n> This one, too.\n>\n> Thanks.\n"},{"id":"430722","messageId":"xmqqr1fse9vu.fsf@gitster.g","threadId":"56121","inReplyTo":"CABPp-BGkiNfy4s9uxFWPYfqVRLj3rT10pJuARW9e3tPrkDbS9w@mail.gmail.com","subject":"Re: [PATCH 8/9] pull: update docs & code for option compatibility with rebasing","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-07-21T01:25:09Z","receivedAt":"2021-07-21T01:25:32Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Elijah Newren <newren@gmail.com> writes:\n\n>> > +Note that `--ff-only` takes precedence over any `--rebase` flag.\n>> > ++\n>>\n>> \"`ff-only` overrides any `--rebase` flag\"?\n>\n> Yeah, ignore that, I had already ripped it out after your comments on\n> patch 1 yesterday.  I've also got more changes to the pull\n> documentation to try to do more of what your sentence above says,\n> \"covering these interactions and whipping them into a much better\n> shape.\"\n\nOK.  After thinking about it further, \"overrides\" is not the word,\neither, I guess.  It is more like that there are \"merge\", \"rebase\"\nand \"fast-forward-only\" for the user to choose from, with \"merge\"\nhaving two sub-variants (i.e. what happens when we do not have any\ndevelopment of our own) and \"rebase\" with a few sub-variants\n(i.e. interactive? shape-preserving?).\n\nIn any case, when the user says \"I'll take nothing but fast-forward\nupdate to their history\", it is a bug if we did not fail when their\nhistory is not a descendant of ours, and we have already fixed it in\nan earlier part of the series.  This would be a good place to\nclarify what the correct behaviour ought to be, if the existing\ndocumentation is not already.\n\nThanks.\n"},{"id":"430723","messageId":"c45cd2396664e915a1d962e6d131505df51b118e.1626831744.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.v2.git.git.1626831744.gitgitgadget@gmail.com","subject":"[PATCH v2 3/8] pull: abort if --ff-only is given and fast-forwarding is impossible","fromName":"Alex Henrie via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-21T01:42:19Z","receivedAt":"2021-07-21T01:42:58Z","isPatch":true,"sender":{"key":"alexhenrie24@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5951993?v=4"},"body":"From: Alex Henrie <alexhenrie24@gmail.com>\n\nThe warning about pulling without specifying how to reconcile divergent\nbranches says that after setting pull.rebase to true, --ff-only can\nstill be passed on the command line to require a fast-forward. Make that\nactually work.\n\nSigned-off-by: Alex Henrie <alexhenrie24@gmail.com>\n[en: updated tests; note 3 fixes and 1 new failure]\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n advice.c                     |  5 +++++\n advice.h                     |  1 +\n builtin/merge.c              |  2 +-\n builtin/pull.c               | 11 ++++++++---\n t/t7601-merge-pull-config.sh | 10 +++++-----\n 5 files changed, 20 insertions(+), 9 deletions(-)\n\ndiff --git a/advice.c b/advice.c\nindex 0b9c89c48ab..337e8f342bc 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -286,6 +286,11 @@ void NORETURN die_conclude_merge(void)\n \tdie(_(\"Exiting because of unfinished merge.\"));\n }\n \n+void NORETURN die_ff_impossible(void)\n+{\n+\tdie(_(\"Not possible to fast-forward, aborting.\"));\n+}\n+\n void advise_on_updating_sparse_paths(struct string_list *pathspec_list)\n {\n \tstruct string_list_item *item;\ndiff --git a/advice.h b/advice.h\nindex bd26c385d00..16240438387 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -95,6 +95,7 @@ void advise_if_enabled(enum advice_type type, const char *advice, ...);\n int error_resolve_conflict(const char *me);\n void NORETURN die_resolve_conflict(const char *me);\n void NORETURN die_conclude_merge(void);\n+void NORETURN die_ff_impossible(void);\n void advise_on_updating_sparse_paths(struct string_list *pathspec_list);\n void detach_advice(const char *new_name);\n \ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex a8a843b1f54..aa920ac524f 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1620,7 +1620,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t}\n \n \tif (fast_forward == FF_ONLY)\n-\t\tdie(_(\"Not possible to fast-forward, aborting.\"));\n+\t\tdie_ff_impossible();\n \n \tif (autostash)\n \t\tcreate_autostash(the_repository,\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 3e13f810843..d9796604825 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -1046,9 +1046,14 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \n \tcan_ff = get_can_ff(&orig_head, &merge_heads.oid[0]);\n \n-\tif (rebase_unspecified && !opt_ff && !can_ff) {\n-\t\tif (opt_verbosity >= 0)\n-\t\t\tshow_advice_pull_non_ff();\n+\tif (!can_ff) {\n+\t\tif (opt_ff) {\n+\t\t\tif (!strcmp(opt_ff, \"--ff-only\"))\n+\t\t\t\tdie_ff_impossible();\n+\t\t} else {\n+\t\t\tif (rebase_unspecified && opt_verbosity >= 0)\n+\t\t\t\tshow_advice_pull_non_ff();\n+\t\t}\n \t}\n \n \tif (opt_rebase) {\ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex 21db1e9e14b..d1f621725ad 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -209,11 +209,11 @@ test_attempts_fast_forward () {\n # Group 1: Interaction of --ff-only with --[no-]rebase\n # (And related interaction of pull.ff=only with pull.rebase)\n #\n-test_expect_failure '--ff-only overrides --rebase' '\n+test_expect_success '--ff-only overrides --rebase' '\n \ttest_attempts_fast_forward pull --rebase --ff-only\n '\n \n-test_expect_failure '--ff-only overrides --rebase even if first' '\n+test_expect_success '--ff-only overrides --rebase even if first' '\n \ttest_attempts_fast_forward pull --ff-only --rebase\n '\n \n@@ -221,7 +221,7 @@ test_expect_success '--ff-only overrides --no-rebase' '\n \ttest_attempts_fast_forward pull --ff-only --no-rebase\n '\n \n-test_expect_failure 'pull.ff=only overrides pull.rebase=true' '\n+test_expect_success 'pull.ff=only overrides pull.rebase=true' '\n \ttest_attempts_fast_forward -c pull.ff=only -c pull.rebase=true pull\n '\n \n@@ -252,7 +252,7 @@ test_expect_success 'pull.rebase=true overrides pull.ff=true' '\n '\n \n # Group 3: command line flags take precedence over config\n-test_expect_failure '--ff-only takes precedence over pull.rebase=true' '\n+test_expect_success '--ff-only takes precedence over pull.rebase=true' '\n \ttest_attempts_fast_forward -c pull.rebase=true pull --ff-only\n '\n \n@@ -264,7 +264,7 @@ test_expect_failure '--no-rebase takes precedence over pull.ff=only' '\n \ttest_falls_back_to_full_merge -c pull.ff=only pull --no-rebase\n '\n \n-test_expect_success '--rebase takes precedence over pull.ff=only' '\n+test_expect_failure '--rebase takes precedence over pull.ff=only' '\n \ttest_does_rebase -c pull.ff=only pull --rebase\n '\n \n-- \ngitgitgadget\n\n"},{"id":"430724","messageId":"175609272110e4f8d71dce3e6c4e6ca010a8f366.1626831744.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.v2.git.git.1626831744.gitgitgadget@gmail.com","subject":"[PATCH v2 1/8] t7601: test interaction of merge/rebase/fast-forward flags and options","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-21T01:42:17Z","receivedAt":"2021-07-21T01:43:08Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe interaction of rebase and merge flags and options was not well\ntested.  Add several tests to check for correct behavior from the\nfollowing rules:\n    * --ff-only vs. --[no-]rebase\n      (and the related pull.ff=only vs. pull.rebase)\n    * --rebase[=!false] vs. --no-ff and --ff\n      (and the related pull.rebase=!false overrides pull.ff=!only)\n    * command line flags take precedence over config, except:\n      * --no-rebase heeds pull.ff=!only\n      * pull.rebase=!false vs --no-ff and --ff\n\nFor more details behind these rules and a larger table of individual\ncases, refer to https://lore.kernel.org/git/xmqqwnpqot4m.fsf@gitster.g/\nand the links found therein.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t7601-merge-pull-config.sh | 182 +++++++++++++++++++++++++++++++++++\n 1 file changed, 182 insertions(+)\n\ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex 52e8ccc933a..a44e6e69f2b 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -143,6 +143,188 @@ test_expect_success 'pull.rebase not set and --ff-only given (not-fast-forward)'\n \ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n '\n \n+test_does_rebase () {\n+\tgit reset --hard c2 &&\n+\tgit \"$@\" . c1 &&\n+\t# Check that we actually did a rebase\n+\tgit rev-list --count HEAD >actual &&\n+\tgit rev-list --merges --count HEAD >>actual &&\n+\ttest_write_lines 3 0 >expect &&\n+\ttest_cmp expect actual &&\n+\trm actual expect\n+}\n+\n+# Prefers merge over fast-forward\n+test_does_merge_when_ff_possible () {\n+\tgit reset --hard c0 &&\n+\tgit \"$@\" . c1 &&\n+\t# Check that we actually did a merge\n+\tgit rev-list --count HEAD >actual &&\n+\tgit rev-list --merges --count HEAD >>actual &&\n+\ttest_write_lines 3 1 >expect &&\n+\ttest_cmp expect actual &&\n+\trm actual expect\n+}\n+\n+# Prefers fast-forward over merge or rebase\n+test_does_fast_forward () {\n+\tgit reset --hard c0 &&\n+\tgit \"$@\" . c1 &&\n+\n+\t# Check that we did not get any merges\n+\tgit rev-list --count HEAD >actual &&\n+\tgit rev-list --merges --count HEAD >>actual &&\n+\ttest_write_lines 2 0 >expect &&\n+\ttest_cmp expect actual &&\n+\n+\t# Check that we ended up at c1\n+\tgit rev-parse HEAD >actual &&\n+\tgit rev-parse c1^{commit} >expect &&\n+\ttest_cmp actual expect &&\n+\n+\t# Remove temporary files\n+\trm actual expect\n+}\n+\n+# Doesn't fail when fast-forward not possible; does a merge\n+test_falls_back_to_full_merge () {\n+\tgit reset --hard c2 &&\n+\tgit \"$@\" . c1 &&\n+\t# Check that we actually did a merge\n+\tgit rev-list --count HEAD >actual &&\n+\tgit rev-list --merges --count HEAD >>actual &&\n+\ttest_write_lines 4 1 >expect &&\n+\ttest_cmp expect actual &&\n+\trm actual expect\n+}\n+\n+# Attempts fast forward, which is impossible, and bails\n+test_attempts_fast_forward () {\n+\tgit reset --hard c2 &&\n+\ttest_must_fail git \"$@\" . c1 2>err &&\n+\ttest_i18ngrep \"Not possible to fast-forward, aborting\" err\n+}\n+\n+#\n+# Group 1: Interaction of --ff-only with --[no-]rebase\n+# (And related interaction of pull.ff=only with pull.rebase)\n+#\n+test_expect_failure '--ff-only overrides --rebase' '\n+\ttest_attempts_fast_forward pull --rebase --ff-only\n+'\n+\n+test_expect_failure '--ff-only overrides --rebase even if first' '\n+\ttest_attempts_fast_forward pull --ff-only --rebase\n+'\n+\n+test_expect_success '--ff-only overrides --no-rebase' '\n+\ttest_attempts_fast_forward pull --ff-only --no-rebase\n+'\n+\n+test_expect_failure 'pull.ff=only overrides pull.rebase=true' '\n+\ttest_attempts_fast_forward -c pull.ff=only -c pull.rebase=true pull\n+'\n+\n+test_expect_success 'pull.ff=only overrides pull.rebase=false' '\n+\ttest_attempts_fast_forward -c pull.ff=only -c pull.rebase=false pull\n+'\n+\n+# Group 2: --rebase=[!false] overrides --no-ff and --ff\n+# (And related interaction of pull.rebase=!false and pull.ff=!only)\n+test_expect_success '--rebase overrides --no-ff' '\n+\ttest_does_rebase pull --rebase --no-ff\n+'\n+\n+test_expect_success '--rebase overrides --ff' '\n+\ttest_does_rebase pull --rebase --ff\n+'\n+\n+test_expect_success '--rebase fast-forwards when possible' '\n+\ttest_does_fast_forward pull --rebase --ff\n+'\n+\n+test_expect_success 'pull.rebase=true overrides pull.ff=false' '\n+\ttest_does_rebase -c pull.rebase=true -c pull.ff=false pull\n+'\n+\n+test_expect_success 'pull.rebase=true overrides pull.ff=true' '\n+\ttest_does_rebase -c pull.rebase=true -c pull.ff=true pull\n+'\n+\n+# Group 3: command line flags take precedence over config\n+test_expect_failure '--ff-only takes precedence over pull.rebase=true' '\n+\ttest_attempts_fast_forward -c pull.rebase=true pull --ff-only\n+'\n+\n+test_expect_success '--ff-only takes precedence over pull.rebase=false' '\n+\ttest_attempts_fast_forward -c pull.rebase=false pull --ff-only\n+'\n+\n+test_expect_failure '--no-rebase takes precedence over pull.ff=only' '\n+\ttest_falls_back_to_full_merge -c pull.ff=only pull --no-rebase\n+'\n+\n+test_expect_success '--rebase takes precedence over pull.ff=only' '\n+\ttest_does_rebase -c pull.ff=only pull --rebase\n+'\n+\n+test_expect_success '--rebase overrides pull.ff=true' '\n+\ttest_does_rebase -c pull.ff=true pull --rebase\n+'\n+\n+test_expect_success '--rebase overrides pull.ff=false' '\n+\ttest_does_rebase -c pull.ff=false pull --rebase\n+'\n+\n+test_expect_success '--rebase overrides pull.ff unset' '\n+\ttest_does_rebase pull --rebase\n+'\n+\n+# Group 4: --no-rebase heeds pull.ff=!only or explict --ff or --no-ff\n+\n+test_expect_success '--no-rebase works with --no-ff' '\n+\ttest_does_merge_when_ff_possible pull --no-rebase --no-ff\n+'\n+\n+test_expect_success '--no-rebase works with --ff' '\n+\ttest_does_fast_forward pull --no-rebase --ff\n+'\n+\n+test_expect_success '--no-rebase does ff if pull.ff unset' '\n+\ttest_does_fast_forward pull --no-rebase\n+'\n+\n+test_expect_success '--no-rebase heeds pull.ff=true' '\n+\ttest_does_fast_forward -c pull.ff=true pull --no-rebase\n+'\n+\n+test_expect_success '--no-rebase heeds pull.ff=false' '\n+\ttest_does_merge_when_ff_possible -c pull.ff=false pull --no-rebase\n+'\n+\n+# Group 5: pull.rebase=!false in combination with --no-ff or --ff\n+test_expect_success 'pull.rebase=true and --no-ff' '\n+\ttest_does_rebase -c pull.rebase=true pull --no-ff\n+'\n+\n+test_expect_success 'pull.rebase=true and --ff' '\n+\ttest_does_rebase -c pull.rebase=true pull --ff\n+'\n+\n+test_expect_success 'pull.rebase=false and --no-ff' '\n+\ttest_does_merge_when_ff_possible -c pull.rebase=false pull --no-ff\n+'\n+\n+test_expect_success 'pull.rebase=false and --ff, ff possible' '\n+\ttest_does_fast_forward -c pull.rebase=false pull --ff\n+'\n+\n+test_expect_success 'pull.rebase=false and --ff, ff not possible' '\n+\ttest_falls_back_to_full_merge -c pull.rebase=false pull --ff\n+'\n+\n+# End of groupings for conflicting merge vs. rebase flags/options\n+\n test_expect_success 'merge c1 with c2' '\n \tgit reset --hard c1 &&\n \ttest -f c0.c &&\n-- \ngitgitgadget\n\n"},{"id":"430725","messageId":"66fe7f7f9341c11bc72eaf4cfa7b1d6c658e31a8.1626831744.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.v2.git.git.1626831744.gitgitgadget@gmail.com","subject":"[PATCH v2 2/8] t7601: add tests of interactions with multiple merge heads and config","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-21T01:42:18Z","receivedAt":"2021-07-21T01:43:18Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThere were already code checking that --rebase was incompatible with\na merge of multiple heads.  However, we were sometimes throwing warnings\nabout lack of specification of rebase vs. merge when given multiple\nheads.  Since rebasing is disallowed with multiple merge heads, that\nseems like a poor warning to print; we should instead just assume\nmerging is wanted.\n\nAdd a few tests checking multiple merge head behavior.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t7601-merge-pull-config.sh | 22 ++++++++++++++++++++++\n 1 file changed, 22 insertions(+)\n\ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex a44e6e69f2b..21db1e9e14b 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -325,6 +325,28 @@ test_expect_success 'pull.rebase=false and --ff, ff not possible' '\n \n # End of groupings for conflicting merge vs. rebase flags/options\n \n+test_expect_failure 'Multiple heads warns about inability to fast forward' '\n+\tgit reset --hard c1 &&\n+\ttest_must_fail git pull . c2 c3 2>err &&\n+\ttest_i18ngrep \"Pulling without specifying how to reconcile\" err\n+'\n+\n+test_expect_failure 'Multiple can never be fast forwarded' '\n+\tgit reset --hard c0 &&\n+\ttest_must_fail git -c pull.ff=only pull . c1 c2 c3 2>err &&\n+\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err &&\n+\t# In addition to calling out \"cannot fast-forward\", we very much\n+\t# want the \"multiple branches\" piece to be called out to users.\n+\ttest_i18ngrep \"Cannot fast-forward to multiple branches\" err\n+'\n+\n+test_expect_success 'Cannot rebase with multiple heads' '\n+\tgit reset --hard c0 &&\n+\ttest_must_fail git -c pull.rebase=true pull . c1 c2 c3 2>err &&\n+\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err &&\n+\ttest_i18ngrep \"Cannot rebase onto multiple branches.\" err\n+'\n+\n test_expect_success 'merge c1 with c2' '\n \tgit reset --hard c1 &&\n \ttest -f c0.c &&\n-- \ngitgitgadget\n\n"},{"id":"430726","messageId":"9b116f3d284840052945292f73c47bacdb2f86d6.1626831744.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.v2.git.git.1626831744.gitgitgadget@gmail.com","subject":"[PATCH v2 5/8] pull: make --rebase and --no-rebase override pull.ff=only","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-21T01:42:21Z","receivedAt":"2021-07-21T01:43:26Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFix the last few precedence tests failing in t7601 by now implementing\nthe logic to have --[no-]rebase override a pull.ff=only config setting.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/pull.c               | 16 +++++++++++++++-\n t/t7601-merge-pull-config.sh |  4 ++--\n 2 files changed, 17 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 92150f976cd..da1d4b19666 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -966,8 +966,22 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \n \tparse_repo_refspecs(argc, argv, &repo, &refspecs);\n \n-\tif (!opt_ff)\n+\tif (!opt_ff) {\n \t\topt_ff = xstrdup_or_null(config_get_ff());\n+\t\t/*\n+\t\t * A subtle point: opt_ff was set on the line above via\n+\t\t * reading from config.  opt_rebase, in contrast, is set\n+\t\t * before this point via command line options.  The setting\n+\t\t * of opt_rebase via reading from config (using\n+\t\t * config_get_rebase()) does not happen until later.  We\n+\t\t * are relying on the next if-condition happening before\n+\t\t * the config_get_rebase() call so that an explicit\n+\t\t * \"--rebase\" can override a config setting of\n+\t\t * pull.ff=only.\n+\t\t */\n+\t\tif (opt_rebase >= 0 && opt_ff && !strcmp(opt_ff, \"--ff-only\"))\n+\t\t\topt_ff = \"--ff\";\n+\t}\n \n \tif (opt_rebase < 0)\n \t\topt_rebase = config_get_rebase(&rebase_unspecified);\ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex d1f621725ad..0c0dfecd2a3 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -260,11 +260,11 @@ test_expect_success '--ff-only takes precedence over pull.rebase=false' '\n \ttest_attempts_fast_forward -c pull.rebase=false pull --ff-only\n '\n \n-test_expect_failure '--no-rebase takes precedence over pull.ff=only' '\n+test_expect_success '--no-rebase takes precedence over pull.ff=only' '\n \ttest_falls_back_to_full_merge -c pull.ff=only pull --no-rebase\n '\n \n-test_expect_failure '--rebase takes precedence over pull.ff=only' '\n+test_expect_success '--rebase takes precedence over pull.ff=only' '\n \ttest_does_rebase -c pull.ff=only pull --rebase\n '\n \n-- \ngitgitgadget\n\n"},{"id":"430727","messageId":"pull.1049.v2.git.git.1626831744.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.git.git.1626536507.gitgitgadget@gmail.com","subject":"[PATCH v2 0/8] Handle pull option precedence","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-21T01:42:16Z","receivedAt":"2021-07-21T01:43:29Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Based on a recent list of rules for flag/option precedence for git-pull[1]\nfrom Junio (particularly focusing on rebase vs. merge vs. fast-forward),\nhere's an attempt to implement and document it. Given multiple recent\nsurprises from users about some of these behaviors[2][3] and a coworker just\nyesterday expressing some puzzlement with git-pull and rebase vs. merge, it\nseems like a good time to address some of these issues.\n\nSince the handling of conflicting options was holding up two of Alex's\npatches[4][5], and his patches fix some of the tests, I also include those\ntwo patches in my series, with a few small changes to the first (so I've\nkept him as author) and more substantial changes to the second (so I've\ngiven him an Initial-patch-by attribution).\n\nChanges since v1:\n\n * Rebased on latest master (resolved a simple conflict with\n   dd/test-stdout-count-lines)\n * Patch 1: based on feedback from Junio, fixed some style issues, clarified\n   function names, added a few new tests, and took a stab at fixing up the\n   comments and test descriptions (but still unsure if I hit the mark on the\n   last point)\n * Patch 2: changed the test expectations for one of the multiple head tests\n   as per Junio's suggestion, and made one of the other tests expect a more\n   specific error message\n * Patches 4 & 5 were squashed and fixed: these now address a submodule bug\n   interaction with --ff-only\n * Old patch 6 (now 5): added a code comment explaining a subtle point\n * Old patch 8 (now 7): a few more documentation updates, especially making\n   --ff-only not sound merge-specific\n * Old patch 9 (now 8): Updates for new test expectation from patch 2\n\nQuick overview:\n\n * Patches 1-2: new testcases (see the commit messages for the rules)\n * Patch 3: Alex's recent patch (abort if --ff-only but can't do so)\n * Patches 4-6: fix the precedence parts Alex didn't cover\n * Patch 7: Alex's other patch, abort if rebase vs. merge not specified\n * Patch 8: Compatibility of git-pull with merge-options.txt (think\n   rebasing)\n * Patch 9: Fix multiple heads handling too\n\n[1] https://lore.kernel.org/git/xmqqwnpqot4m.fsf@gitster.g/ [2]\nhttps://lore.kernel.org/git/CAL3xRKdOyVWvcLXK7zoXtFPiHBjgL24zi5hhg+3yjowwSUPgmg@mail.gmail.com/\n[3]\nhttps://lore.kernel.org/git/c62933fb-96b2-99f5-7169-372f486f6e39@aixigo.com/\n[4]\nhttps://lore.kernel.org/git/20210711012604.947321-1-alexhenrie24@gmail.com/\n[5]\nhttps://lore.kernel.org/git/20210627000855.530985-1-alexhenrie24@gmail.com/\n\nAlex Henrie (1):\n  pull: abort if --ff-only is given and fast-forwarding is impossible\n\nElijah Newren (7):\n  t7601: test interaction of merge/rebase/fast-forward flags and options\n  t7601: add tests of interactions with multiple merge heads and config\n  pull: since --ff-only overrides, handle it first\n  pull: make --rebase and --no-rebase override pull.ff=only\n  pull: abort by default when fast-forwarding is not possible\n  pull: update docs & code for option compatibility with rebasing\n  pull: fix handling of multiple heads\n\n Documentation/git-merge.txt     |   2 +\n Documentation/git-pull.txt      |  21 +--\n Documentation/merge-options.txt |  40 ++++++\n advice.c                        |   5 +\n advice.h                        |   1 +\n builtin/merge.c                 |   2 +-\n builtin/pull.c                  |  63 ++++++---\n t/t4013-diff-various.sh         |   2 +-\n t/t5520-pull.sh                 |  26 ++--\n t/t5521-pull-options.sh         |   4 +-\n t/t5524-pull-msg.sh             |   4 +-\n t/t5553-set-upstream.sh         |  14 +-\n t/t5604-clone-reference.sh      |   4 +-\n t/t6402-merge-rename.sh         |  18 +--\n t/t6409-merge-subtree.sh        |   6 +-\n t/t6417-merge-ours-theirs.sh    |  10 +-\n t/t7601-merge-pull-config.sh    | 244 +++++++++++++++++++++++++++++---\n t/t7603-merge-reduce-heads.sh   |   2 +-\n 18 files changed, 375 insertions(+), 93 deletions(-)\n\n\nbase-commit: daab8a564f8bbac55f70f8bf86c070e001a9b006\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1049%2Fnewren%2Fhandle-pull-option-precedence-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1049/newren/handle-pull-option-precedence-v2\nPull-Request: https://github.com/git/git/pull/1049\n\nRange-diff vs v1:\n\n  1:  6cb771297f5 !  1:  17560927211 t7601: add relative precedence tests for merge and rebase flags/options\n     @@ Metadata\n      Author: Elijah Newren <newren@gmail.com>\n      \n       ## Commit message ##\n     -    t7601: add relative precedence tests for merge and rebase flags/options\n     +    t7601: test interaction of merge/rebase/fast-forward flags and options\n      \n          The interaction of rebase and merge flags and options was not well\n          tested.  Add several tests to check for correct behavior from the\n          following rules:\n     -        * --ff-only takes precedence over --[no-]rebase\n     -          * Corollary: pull.ff=only overrides pull.rebase\n     -        * --rebase[=!false] takes precedence over --no-ff and --ff\n     -          * Corollary: pull.rebase=!false overrides pull.ff=!only\n     +        * --ff-only vs. --[no-]rebase\n     +          (and the related pull.ff=only vs. pull.rebase)\n     +        * --rebase[=!false] vs. --no-ff and --ff\n     +          (and the related pull.rebase=!false overrides pull.ff=!only)\n              * command line flags take precedence over config, except:\n                * --no-rebase heeds pull.ff=!only\n     -          * pull.rebase=!false takes precedence over --no-ff and --ff\n     +          * pull.rebase=!false vs --no-ff and --ff\n      \n          For more details behind these rules and a larger table of individual\n          cases, refer to https://lore.kernel.org/git/xmqqwnpqot4m.fsf@gitster.g/\n     @@ t/t7601-merge-pull-config.sh: test_expect_success 'pull.rebase not set and --ff-\n       \ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n       '\n       \n     -+test_does_rebase() {\n     ++test_does_rebase () {\n      +\tgit reset --hard c2 &&\n      +\tgit \"$@\" . c1 &&\n      +\t# Check that we actually did a rebase\n     @@ t/t7601-merge-pull-config.sh: test_expect_success 'pull.rebase not set and --ff-\n      +\trm actual expect\n      +}\n      +\n     -+test_does_merge_noff() {\n     ++# Prefers merge over fast-forward\n     ++test_does_merge_when_ff_possible () {\n      +\tgit reset --hard c0 &&\n      +\tgit \"$@\" . c1 &&\n      +\t# Check that we actually did a merge\n     @@ t/t7601-merge-pull-config.sh: test_expect_success 'pull.rebase not set and --ff-\n      +\trm actual expect\n      +}\n      +\n     -+test_does_merge_ff() {\n     ++# Prefers fast-forward over merge or rebase\n     ++test_does_fast_forward () {\n      +\tgit reset --hard c0 &&\n      +\tgit \"$@\" . c1 &&\n     -+\t# Check that we actually did a merge\n     ++\n     ++\t# Check that we did not get any merges\n      +\tgit rev-list --count HEAD >actual &&\n      +\tgit rev-list --merges --count HEAD >>actual &&\n      +\ttest_write_lines 2 0 >expect &&\n      +\ttest_cmp expect actual &&\n     ++\n     ++\t# Check that we ended up at c1\n     ++\tgit rev-parse HEAD >actual &&\n     ++\tgit rev-parse c1^{commit} >expect &&\n     ++\ttest_cmp actual expect &&\n     ++\n     ++\t# Remove temporary files\n      +\trm actual expect\n      +}\n      +\n     -+test_does_need_full_merge() {\n     ++# Doesn't fail when fast-forward not possible; does a merge\n     ++test_falls_back_to_full_merge () {\n      +\tgit reset --hard c2 &&\n      +\tgit \"$@\" . c1 &&\n      +\t# Check that we actually did a merge\n     @@ t/t7601-merge-pull-config.sh: test_expect_success 'pull.rebase not set and --ff-\n      +\trm actual expect\n      +}\n      +\n     -+test_attempts_fast_forward() {\n     ++# Attempts fast forward, which is impossible, and bails\n     ++test_attempts_fast_forward () {\n      +\tgit reset --hard c2 &&\n      +\ttest_must_fail git \"$@\" . c1 2>err &&\n      +\ttest_i18ngrep \"Not possible to fast-forward, aborting\" err\n      +}\n      +\n      +#\n     -+# Rule 1: --ff-only takes precedence over --[no-]rebase\n     -+# (Corollary: pull.ff=only overrides pull.rebase)\n     ++# Group 1: Interaction of --ff-only with --[no-]rebase\n     ++# (And related interaction of pull.ff=only with pull.rebase)\n      +#\n     -+test_expect_failure '--ff-only takes precedence over --rebase' '\n     ++test_expect_failure '--ff-only overrides --rebase' '\n      +\ttest_attempts_fast_forward pull --rebase --ff-only\n      +'\n      +\n     -+test_expect_failure '--ff-only takes precedence over --rebase even if first' '\n     ++test_expect_failure '--ff-only overrides --rebase even if first' '\n      +\ttest_attempts_fast_forward pull --ff-only --rebase\n      +'\n      +\n     -+test_expect_success '--ff-only takes precedence over --no-rebase' '\n     ++test_expect_success '--ff-only overrides --no-rebase' '\n      +\ttest_attempts_fast_forward pull --ff-only --no-rebase\n      +'\n      +\n     @@ t/t7601-merge-pull-config.sh: test_expect_success 'pull.rebase not set and --ff-\n      +\ttest_attempts_fast_forward -c pull.ff=only -c pull.rebase=false pull\n      +'\n      +\n     -+# Rule 2: --rebase=[!false] takes precedence over --no-ff and --ff\n     -+# (Corollary: pull.rebase=!false overrides pull.ff=!only)\n     -+test_expect_success '--rebase takes precedence over --no-ff' '\n     ++# Group 2: --rebase=[!false] overrides --no-ff and --ff\n     ++# (And related interaction of pull.rebase=!false and pull.ff=!only)\n     ++test_expect_success '--rebase overrides --no-ff' '\n      +\ttest_does_rebase pull --rebase --no-ff\n      +'\n      +\n     -+test_expect_success '--rebase takes precedence over --ff' '\n     ++test_expect_success '--rebase overrides --ff' '\n      +\ttest_does_rebase pull --rebase --ff\n      +'\n      +\n     -+test_expect_success 'pull.rebase=true takes precedence over pull.ff=false' '\n     ++test_expect_success '--rebase fast-forwards when possible' '\n     ++\ttest_does_fast_forward pull --rebase --ff\n     ++'\n     ++\n     ++test_expect_success 'pull.rebase=true overrides pull.ff=false' '\n      +\ttest_does_rebase -c pull.rebase=true -c pull.ff=false pull\n      +'\n      +\n     -+test_expect_success 'pull.rebase=true takes precedence over pull.ff=true' '\n     ++test_expect_success 'pull.rebase=true overrides pull.ff=true' '\n      +\ttest_does_rebase -c pull.rebase=true -c pull.ff=true pull\n      +'\n      +\n     -+# Rule 3: command line flags take precedence over config\n     ++# Group 3: command line flags take precedence over config\n      +test_expect_failure '--ff-only takes precedence over pull.rebase=true' '\n      +\ttest_attempts_fast_forward -c pull.rebase=true pull --ff-only\n      +'\n     @@ t/t7601-merge-pull-config.sh: test_expect_success 'pull.rebase not set and --ff-\n      +\ttest_attempts_fast_forward -c pull.rebase=false pull --ff-only\n      +'\n      +\n     -+test_expect_failure '--no-rebase overrides pull.ff=only' '\n     -+\ttest_does_need_full_merge -c pull.ff=only pull --no-rebase\n     ++test_expect_failure '--no-rebase takes precedence over pull.ff=only' '\n     ++\ttest_falls_back_to_full_merge -c pull.ff=only pull --no-rebase\n      +'\n      +\n      +test_expect_success '--rebase takes precedence over pull.ff=only' '\n      +\ttest_does_rebase -c pull.ff=only pull --rebase\n      +'\n      +\n     -+test_expect_success '--rebase takes precedence over pull.ff=true' '\n     ++test_expect_success '--rebase overrides pull.ff=true' '\n      +\ttest_does_rebase -c pull.ff=true pull --rebase\n      +'\n      +\n     -+test_expect_success '--rebase takes precedence over pull.ff=false' '\n     ++test_expect_success '--rebase overrides pull.ff=false' '\n      +\ttest_does_rebase -c pull.ff=false pull --rebase\n      +'\n      +\n     -+test_expect_success '--rebase takes precedence over pull.ff unset' '\n     ++test_expect_success '--rebase overrides pull.ff unset' '\n      +\ttest_does_rebase pull --rebase\n      +'\n      +\n     -+# Rule 4: --no-rebase heeds pull.ff=!only or explict --ff or --no-ff\n     ++# Group 4: --no-rebase heeds pull.ff=!only or explict --ff or --no-ff\n      +\n      +test_expect_success '--no-rebase works with --no-ff' '\n     -+\ttest_does_merge_noff pull --no-rebase --no-ff\n     ++\ttest_does_merge_when_ff_possible pull --no-rebase --no-ff\n      +'\n      +\n      +test_expect_success '--no-rebase works with --ff' '\n     -+\ttest_does_merge_ff pull --no-rebase --ff\n     ++\ttest_does_fast_forward pull --no-rebase --ff\n      +'\n      +\n      +test_expect_success '--no-rebase does ff if pull.ff unset' '\n     -+\ttest_does_merge_ff pull --no-rebase\n     ++\ttest_does_fast_forward pull --no-rebase\n      +'\n      +\n      +test_expect_success '--no-rebase heeds pull.ff=true' '\n     -+\ttest_does_merge_ff -c pull.ff=true pull --no-rebase\n     ++\ttest_does_fast_forward -c pull.ff=true pull --no-rebase\n      +'\n      +\n      +test_expect_success '--no-rebase heeds pull.ff=false' '\n     -+\ttest_does_merge_noff -c pull.ff=false pull --no-rebase\n     ++\ttest_does_merge_when_ff_possible -c pull.ff=false pull --no-rebase\n      +'\n      +\n     -+# Rule 5: pull.rebase=!false takes precedence over --no-ff and --ff\n     -+test_expect_success 'pull.rebase=true takes precedence over --no-ff' '\n     ++# Group 5: pull.rebase=!false in combination with --no-ff or --ff\n     ++test_expect_success 'pull.rebase=true and --no-ff' '\n      +\ttest_does_rebase -c pull.rebase=true pull --no-ff\n      +'\n      +\n     -+test_expect_success 'pull.rebase=true takes precedence over --ff' '\n     ++test_expect_success 'pull.rebase=true and --ff' '\n      +\ttest_does_rebase -c pull.rebase=true pull --ff\n      +'\n      +\n     -+# End of precedence rules\n     ++test_expect_success 'pull.rebase=false and --no-ff' '\n     ++\ttest_does_merge_when_ff_possible -c pull.rebase=false pull --no-ff\n     ++'\n     ++\n     ++test_expect_success 'pull.rebase=false and --ff, ff possible' '\n     ++\ttest_does_fast_forward -c pull.rebase=false pull --ff\n     ++'\n     ++\n     ++test_expect_success 'pull.rebase=false and --ff, ff not possible' '\n     ++\ttest_falls_back_to_full_merge -c pull.rebase=false pull --ff\n     ++'\n     ++\n     ++# End of groupings for conflicting merge vs. rebase flags/options\n      +\n       test_expect_success 'merge c1 with c2' '\n       \tgit reset --hard c1 &&\n  2:  329802382bf !  2:  66fe7f7f934 t7601: add tests of interactions with multiple merge heads and config\n     @@ Commit message\n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n       ## t/t7601-merge-pull-config.sh ##\n     -@@ t/t7601-merge-pull-config.sh: test_expect_success 'pull.rebase=true takes precedence over --ff' '\n     +@@ t/t7601-merge-pull-config.sh: test_expect_success 'pull.rebase=false and --ff, ff not possible' '\n       \n     - # End of precedence rules\n     + # End of groupings for conflicting merge vs. rebase flags/options\n       \n     -+test_expect_failure 'Multiple heads does not warn about fast forwarding' '\n     ++test_expect_failure 'Multiple heads warns about inability to fast forward' '\n      +\tgit reset --hard c1 &&\n     -+\tgit pull . c2 c3 2>err &&\n     -+\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n     ++\ttest_must_fail git pull . c2 c3 2>err &&\n     ++\ttest_i18ngrep \"Pulling without specifying how to reconcile\" err\n      +'\n      +\n     -+test_expect_success 'Cannot fast-forward with multiple heads' '\n     ++test_expect_failure 'Multiple can never be fast forwarded' '\n      +\tgit reset --hard c0 &&\n      +\ttest_must_fail git -c pull.ff=only pull . c1 c2 c3 2>err &&\n      +\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err &&\n     -+\ttest_i18ngrep \"Not possible to fast-forward, aborting\" err\n     ++\t# In addition to calling out \"cannot fast-forward\", we very much\n     ++\t# want the \"multiple branches\" piece to be called out to users.\n     ++\ttest_i18ngrep \"Cannot fast-forward to multiple branches\" err\n      +'\n      +\n      +test_expect_success 'Cannot rebase with multiple heads' '\n  3:  ae54afd8b01 !  3:  c45cd239666 pull: abort if --ff-only is given and fast-forwarding is impossible\n     @@ builtin/pull.c: int cmd_pull(int argc, const char **argv, const char *prefix)\n       \tif (opt_rebase) {\n      \n       ## t/t7601-merge-pull-config.sh ##\n     -@@ t/t7601-merge-pull-config.sh: test_attempts_fast_forward() {\n     - # Rule 1: --ff-only takes precedence over --[no-]rebase\n     - # (Corollary: pull.ff=only overrides pull.rebase)\n     +@@ t/t7601-merge-pull-config.sh: test_attempts_fast_forward () {\n     + # Group 1: Interaction of --ff-only with --[no-]rebase\n     + # (And related interaction of pull.ff=only with pull.rebase)\n       #\n     --test_expect_failure '--ff-only takes precedence over --rebase' '\n     -+test_expect_success '--ff-only takes precedence over --rebase' '\n     +-test_expect_failure '--ff-only overrides --rebase' '\n     ++test_expect_success '--ff-only overrides --rebase' '\n       \ttest_attempts_fast_forward pull --rebase --ff-only\n       '\n       \n     --test_expect_failure '--ff-only takes precedence over --rebase even if first' '\n     -+test_expect_success '--ff-only takes precedence over --rebase even if first' '\n     +-test_expect_failure '--ff-only overrides --rebase even if first' '\n     ++test_expect_success '--ff-only overrides --rebase even if first' '\n       \ttest_attempts_fast_forward pull --ff-only --rebase\n       '\n       \n     -@@ t/t7601-merge-pull-config.sh: test_expect_success '--ff-only takes precedence over --no-rebase' '\n     +@@ t/t7601-merge-pull-config.sh: test_expect_success '--ff-only overrides --no-rebase' '\n       \ttest_attempts_fast_forward pull --ff-only --no-rebase\n       '\n       \n     @@ t/t7601-merge-pull-config.sh: test_expect_success '--ff-only takes precedence ov\n       \ttest_attempts_fast_forward -c pull.ff=only -c pull.rebase=true pull\n       '\n       \n     -@@ t/t7601-merge-pull-config.sh: test_expect_success 'pull.rebase=true takes precedence over pull.ff=true' '\n     +@@ t/t7601-merge-pull-config.sh: test_expect_success 'pull.rebase=true overrides pull.ff=true' '\n       '\n       \n     - # Rule 3: command line flags take precedence over config\n     + # Group 3: command line flags take precedence over config\n      -test_expect_failure '--ff-only takes precedence over pull.rebase=true' '\n      +test_expect_success '--ff-only takes precedence over pull.rebase=true' '\n       \ttest_attempts_fast_forward -c pull.rebase=true pull --ff-only\n       '\n       \n     -@@ t/t7601-merge-pull-config.sh: test_expect_failure '--no-rebase overrides pull.ff=only' '\n     - \ttest_does_need_full_merge -c pull.ff=only pull --no-rebase\n     +@@ t/t7601-merge-pull-config.sh: test_expect_failure '--no-rebase takes precedence over pull.ff=only' '\n     + \ttest_falls_back_to_full_merge -c pull.ff=only pull --no-rebase\n       '\n       \n      -test_expect_success '--rebase takes precedence over pull.ff=only' '\n  4:  de4b460b09d <  -:  ----------- pull: since --ff-only overrides, handle it first\n  5:  3d9ff69198e !  4:  1a821d3b1dd pull: ensure --rebase overrides ability to ff\n     @@ Metadata\n      Author: Elijah Newren <newren@gmail.com>\n      \n       ## Commit message ##\n     -    pull: ensure --rebase overrides ability to ff\n     +    pull: since --ff-only overrides, handle it first\n      \n     -    Now that the handling of fast-forward-only in combination with rebases\n     -    has been moved before the merge-vs-rebase logic, we have an unnecessary\n     -    special fast-forward case left within the rebase logic.  Actually, more\n     -    than unnecessary, it's actually a violation of the rules.  As per\n     -    https://lore.kernel.org/git/xmqqwnpqot4m.fsf@gitster.g/, --rebase is\n     -    supposed to override all ff flags other than an explicit --ff-only.\n     -    Ensure that it does so by removing the fast-forward special case that\n     -    exists within the rebase logic.\n     +    There are both merge and rebase branches in the logic, and previously\n     +    both had to handle fast-forwarding.  Merge handled that implicitly\n     +    (because git merge handles it directly), while in rebase it was\n     +    explicit.  Given that the --ff-only flag is meant to override any\n     +    --rebase or --no-rebase, make the code reflect that by handling\n     +    --ff-only before the merge-vs-rebase logic.\n     +\n     +    It turns out that this also fixes a bug for submodules.  Previously,\n     +    when --ff-only was given, the code would run `merge --ff-only` on the\n     +    main module, and then run `submodule update --recursive --rebase` on the\n     +    submodules.  With this change, we still run `merge --ff-only` on the\n     +    main module, but now run `submodule update --recursive --checkout` on\n     +    the submodules.  I believe this better reflects the intent of --ff-only\n     +    to have it apply to both the main module and the submodules.\n     +\n     +    (Sidenote: It is somewhat interesting that all merges pass `--checkout`\n     +    to submodule update, even when `--no-ff` is specified, meaning that it\n     +    will only do fast-forward merges for submodules.  This was discussed in\n     +    commit a6d7eb2c7a (\"pull: optionally rebase submodules (remote submodule\n     +    changes only)\", 2017-06-23).  The same limitations apply now as then, so\n     +    we are not trying to fix this at this time.)\n      \n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n       ## builtin/pull.c ##\n      @@ builtin/pull.c: int cmd_pull(int argc, const char **argv, const char *prefix)\n     + \n     + \tcan_ff = get_can_ff(&orig_head, &merge_heads.oid[0]);\n     + \n     +-\tif (!can_ff) {\n     +-\t\tif (opt_ff) {\n     +-\t\t\tif (!strcmp(opt_ff, \"--ff-only\"))\n     +-\t\t\t\tdie_ff_impossible();\n     +-\t\t} else {\n     +-\t\t\tif (rebase_unspecified && opt_verbosity >= 0)\n     +-\t\t\t\tshow_advice_pull_non_ff();\n     +-\t\t}\n     ++\t/* ff-only takes precedence over rebase */\n     ++\tif (opt_ff && !strcmp(opt_ff, \"--ff-only\")) {\n     ++\t\tif (!can_ff)\n     ++\t\t\tdie_ff_impossible();\n     ++\t\topt_rebase = REBASE_FALSE;\n     + \t}\n     ++\t/* If no action specified and we can't fast forward, then warn. */\n     ++\tif (!opt_ff && rebase_unspecified && !can_ff)\n     ++\t\tshow_advice_pull_non_ff();\n     + \n     + \tif (opt_rebase) {\n     + \t\tint ret = 0;\n     +@@ builtin/pull.c: int cmd_pull(int argc, const char **argv, const char *prefix)\n       \t\t    submodule_touches_in_range(the_repository, &upstream, &curr_head))\n       \t\t\tdie(_(\"cannot rebase with locally recorded submodule modifications\"));\n       \n  6:  b379fea097d !  5:  9b116f3d284 pull: make --rebase and --no-rebase override pull.ff=only\n     @@ builtin/pull.c: int cmd_pull(int argc, const char **argv, const char *prefix)\n      -\tif (!opt_ff)\n      +\tif (!opt_ff) {\n       \t\topt_ff = xstrdup_or_null(config_get_ff());\n     ++\t\t/*\n     ++\t\t * A subtle point: opt_ff was set on the line above via\n     ++\t\t * reading from config.  opt_rebase, in contrast, is set\n     ++\t\t * before this point via command line options.  The setting\n     ++\t\t * of opt_rebase via reading from config (using\n     ++\t\t * config_get_rebase()) does not happen until later.  We\n     ++\t\t * are relying on the next if-condition happening before\n     ++\t\t * the config_get_rebase() call so that an explicit\n     ++\t\t * \"--rebase\" can override a config setting of\n     ++\t\t * pull.ff=only.\n     ++\t\t */\n      +\t\tif (opt_rebase >= 0 && opt_ff && !strcmp(opt_ff, \"--ff-only\"))\n      +\t\t\topt_ff = \"--ff\";\n      +\t}\n     @@ t/t7601-merge-pull-config.sh: test_expect_success '--ff-only takes precedence ov\n       \ttest_attempts_fast_forward -c pull.rebase=false pull --ff-only\n       '\n       \n     --test_expect_failure '--no-rebase overrides pull.ff=only' '\n     -+test_expect_success '--no-rebase overrides pull.ff=only' '\n     - \ttest_does_need_full_merge -c pull.ff=only pull --no-rebase\n     +-test_expect_failure '--no-rebase takes precedence over pull.ff=only' '\n     ++test_expect_success '--no-rebase takes precedence over pull.ff=only' '\n     + \ttest_falls_back_to_full_merge -c pull.ff=only pull --no-rebase\n       '\n       \n      -test_expect_failure '--rebase takes precedence over pull.ff=only' '\n  7:  dca0455898a !  6:  f061f8b4e75 pull: abort by default when fast-forwarding is not possible\n     @@ builtin/pull.c: static int get_can_ff(struct object_id *orig_head, struct object\n       \t\t \"  git config pull.rebase false  # merge (the default strategy)\\n\"\n       \t\t \"  git config pull.rebase true   # rebase\\n\"\n      @@ builtin/pull.c: int cmd_pull(int argc, const char **argv, const char *prefix)\n     - \t\t\treturn run_merge();\n     + \t\topt_rebase = REBASE_FALSE;\n       \t}\n       \t/* If no action specified and we can't fast forward, then warn. */\n      -\tif (!opt_ff && rebase_unspecified && !can_ff)\n     @@ t/t6402-merge-rename.sh: test_expect_success 'setup' '\n      -\ttest_expect_code 1 git pull . white &&\n      +\ttest_expect_code 1 git pull --no-rebase . white &&\n       \tgit ls-files -s &&\n     - \tgit ls-files -u B >b.stages &&\n     - \ttest_line_count = 3 b.stages &&\n     + \ttest_stdout_line_count = 3 git ls-files -u B &&\n     + \ttest_stdout_line_count = 1 git ls-files -s N &&\n      @@ t/t6402-merge-rename.sh: test_expect_success 'pull renaming branch into another renaming one' \\\n       \trm -f B &&\n       \tgit reset --hard &&\n       \tgit checkout red &&\n      -\ttest_expect_code 1 git pull . white &&\n      +\ttest_expect_code 1 git pull --no-rebase . white &&\n     - \tgit ls-files -u B >b.stages &&\n     - \ttest_line_count = 3 b.stages &&\n     - \tgit ls-files -s N >n.stages &&\n     + \ttest_stdout_line_count = 3 git ls-files -u B &&\n     + \ttest_stdout_line_count = 1 git ls-files -s N &&\n     + \tsed -ne \"/^g/{\n      @@ t/t6402-merge-rename.sh: test_expect_success 'pull unrenaming branch into renaming one' \\\n       '\n       \tgit reset --hard &&\n       \tgit show-branch &&\n      -\ttest_expect_code 1 git pull . main &&\n      +\ttest_expect_code 1 git pull --no-rebase . main &&\n     - \tgit ls-files -u B >b.stages &&\n     - \ttest_line_count = 3 b.stages &&\n     - \tgit ls-files -s N >n.stages &&\n     + \ttest_stdout_line_count = 3 git ls-files -u B &&\n     + \ttest_stdout_line_count = 1 git ls-files -s N &&\n     + \tsed -ne \"/^g/{\n      @@ t/t6402-merge-rename.sh: test_expect_success 'pull conflicting renames' \\\n       '\n       \tgit reset --hard &&\n       \tgit show-branch &&\n      -\ttest_expect_code 1 git pull . blue &&\n      +\ttest_expect_code 1 git pull --no-rebase . blue &&\n     - \tgit ls-files -u A >a.stages &&\n     - \ttest_line_count = 1 a.stages &&\n     - \tgit ls-files -u B >b.stages &&\n     + \ttest_stdout_line_count = 1 git ls-files -u A &&\n     + \ttest_stdout_line_count = 1 git ls-files -u B &&\n     + \ttest_stdout_line_count = 1 git ls-files -u C &&\n      @@ t/t6402-merge-rename.sh: test_expect_success 'interference with untracked working tree file' '\n       \tgit reset --hard &&\n       \tgit show-branch &&\n     @@ t/t7601-merge-pull-config.sh: test_expect_success 'setup' '\n      +\ttest_i18ngrep ! \"You have divergent branches\" err\n       '\n       \n     - test_does_rebase() {\n     + test_does_rebase () {\n     +@@ t/t7601-merge-pull-config.sh: test_expect_success 'pull.rebase=false and --ff, ff not possible' '\n     + \n     + # End of groupings for conflicting merge vs. rebase flags/options\n     + \n     +-test_expect_failure 'Multiple heads warns about inability to fast forward' '\n     ++test_expect_success 'Multiple heads warns about inability to fast forward' '\n     + \tgit reset --hard c1 &&\n     + \ttest_must_fail git pull . c2 c3 2>err &&\n     +-\ttest_i18ngrep \"Pulling without specifying how to reconcile\" err\n     ++\ttest_i18ngrep \"You have divergent branches\" err\n     + '\n     + \n     + test_expect_failure 'Multiple can never be fast forwarded' '\n     + \tgit reset --hard c0 &&\n     + \ttest_must_fail git -c pull.ff=only pull . c1 c2 c3 2>err &&\n     +-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err &&\n     ++\ttest_i18ngrep ! \"You have divergent branches\" err &&\n     + \t# In addition to calling out \"cannot fast-forward\", we very much\n     + \t# want the \"multiple branches\" piece to be called out to users.\n     + \ttest_i18ngrep \"Cannot fast-forward to multiple branches\" err\n     +@@ t/t7601-merge-pull-config.sh: test_expect_failure 'Multiple can never be fast forwarded' '\n     + test_expect_success 'Cannot rebase with multiple heads' '\n     + \tgit reset --hard c0 &&\n     + \ttest_must_fail git -c pull.rebase=true pull . c1 c2 c3 2>err &&\n     +-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err &&\n     ++\ttest_i18ngrep ! \"You have divergent branches\" err &&\n     + \ttest_i18ngrep \"Cannot rebase onto multiple branches.\" err\n     + '\n     + \n      \n       ## t/t7603-merge-reduce-heads.sh ##\n      @@ t/t7603-merge-reduce-heads.sh: test_expect_success 'merge c1 with c2, c3, c4, c5' '\n  8:  d1952f014f2 !  7:  90d49e0fb78 pull: update docs & code for option compatibility with rebasing\n     @@ Commit message\n      \n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n     + ## Documentation/git-merge.txt ##\n     +@@ Documentation/git-merge.txt: merge has resulted in conflicts.\n     + \n     + OPTIONS\n     + -------\n     ++:git-merge: 1\n     ++\n     + include::merge-options.txt[]\n     + \n     + -m <msg>::\n     +\n       ## Documentation/git-pull.txt ##\n     -@@ Documentation/git-pull.txt: When false, merge the current branch into the upstream branch.\n     - +\n     - When `interactive`, enable the interactive mode of rebase.\n     - +\n     -+Note that `--ff-only` takes precedence over any `--rebase` flag.\n     -++\n     - See `pull.rebase`, `branch.<name>.rebase` and `branch.autoSetupRebase` in\n     - linkgit:git-config[1] if you want to make `git pull` always use\n     - `--rebase` instead of merging.\n     +@@ Documentation/git-pull.txt: Incorporates changes from a remote repository into the current branch.\n     + If the current branch is behind the remote, then by default it will\n     + fast-forward the current branch to match the remote.  If the current\n     + branch and the remote have diverged, the user needs to specify how to\n     +-reconcile the divergent branches with `--no-ff`, `--ff`, or `--rebase`\n     +-(or the corresponding configuration options in `pull.ff` or\n     +-`pull.rebase`).\n     ++reconcile the divergent branches with `--rebase` or `--no-rebase` (or\n     ++the corresponding configuration option in `pull.rebase`).\n     + \n     + More precisely, `git pull` runs `git fetch` with the given parameters\n     + and then depending on configuration options or command line flags,\n     +-will call either `git merge` or `git rebase` to reconcile diverging\n     ++will call either `git rebase` or `git merge` to reconcile diverging\n     + branches.\n     + \n     + <repository> should be the name of a remote repository as\n     +@@ Documentation/git-pull.txt: published that history already.  Do *not* use this option\n     + unless you have read linkgit:git-rebase[1] carefully.\n     + \n     + --no-rebase::\n     +-\tOverride earlier --rebase.\n     ++\tThis is shorthand for --rebase=false.\n     + \n     + Options related to fetching\n     + ~~~~~~~~~~~~~~~~~~~~~~~~~~~\n      \n       ## Documentation/merge-options.txt ##\n      @@\n     @@ Documentation/merge-options.txt\n       +\n       With --no-commit perform the merge and stop just before creating\n       a merge commit, to give the user a chance to inspect and further\n     -@@ Documentation/merge-options.txt: could instead be resolved as a fast-forward.\n     +@@ Documentation/merge-options.txt: set to `no` at the beginning of them.\n     + \tto `MERGE_MSG` before being passed on to the commit machinery in the\n     + \tcase of a merge conflict.\n     + \n     ++ifdef::git-merge[]\n     + --ff::\n     + --no-ff::\n     + --ff-only::\n     +@@ Documentation/merge-options.txt: set to `no` at the beginning of them.\n     + \tdefault unless merging an annotated (and possibly signed) tag\n     + \tthat is not stored in its natural place in the `refs/tags/`\n     + \thierarchy, in which case `--no-ff` is assumed.\n     ++endif::git-merge[]\n     ++ifdef::git-pull[]\n     ++--ff-only::\n     ++\tOnly update to the new history if there is no divergent local\n     ++\thistory.  This is the default when no method for reconciling\n     ++\tdivergent histories is provided (via the --rebase=* flags).\n     ++\n     ++--ff::\n     ++--no-ff::\n     ++\tWhen merging rather than rebasing, specifies how a merge is\n     ++\thandled when the merged-in history is already a descendant of\n     ++\tthe current history.  If merging is requested, `--ff` is the\n     ++\tdefault unless merging an annotated (and possibly signed) tag\n     ++\tthat is not stored in its natural place in the `refs/tags/`\n     ++\thierarchy, in which case `--no-ff` is assumed.\n     ++endif::git-pull[]\n     + +\n     + With `--ff`, when possible resolve the merge as a fast-forward (only\n     + update the branch pointer to match the merged branch; do not create a\n     +@@ Documentation/merge-options.txt: descendant of the current history), create a merge commit.\n     + +\n     + With `--no-ff`, create a merge commit in all cases, even when the merge\n     + could instead be resolved as a fast-forward.\n     ++ifdef::git-merge[]\n       +\n       With `--ff-only`, resolve the merge as a fast-forward when possible.\n       When not possible, refuse to merge and exit with a non-zero status.\n     -+ifdef::git-pull[]\n     -++\n     -+Note that `--no-ff` and `--ff` are ignored when rebasing is requested.\n     -+endif::git-pull[]\n     ++endif::git-merge[]\n       \n       -S[<keyid>]::\n       --gpg-sign[=<keyid>]::\n  9:  3d8df246772 !  8:  f03b15b7eb0 pull: fix handling of multiple heads\n     @@ Commit message\n          pull: fix handling of multiple heads\n      \n          With multiple heads, we should not allow rebasing or fast-forwarding.\n     -    Also, it seems wrong to have our can_ff computation return true, so fix\n     -    that while we are at it too (we won't actually use the can_ff flag due\n     -    to setting opt_ff to \"--no-ff\", but it's confusing to leave it as\n     -    computed to be true).\n     +    Make sure any fast-forward request calls out specifically the fact that\n     +    multiple branches are in play.  Also, since we cannot fast-forward to\n     +    multiple branches, fix our computation of can_ff.\n      \n          Signed-off-by: Elijah Newren <newren@gmail.com>\n      \n     @@ builtin/pull.c: int cmd_pull(int argc, const char **argv, const char *prefix)\n      +\t\t\tdie(_(\"Cannot rebase onto multiple branches.\"));\n      +\t\tif (opt_ff && !strcmp(opt_ff, \"--ff-only\"))\n      +\t\t\tdie(_(\"Cannot fast-forward to multiple branches.\"));\n     -+\t\tif (!opt_ff)\n     -+\t\t\topt_ff = \"--no-ff\";\n      +\t}\n       \n      -\tcan_ff = get_can_ff(&orig_head, &merge_heads.oid[0]);\n     @@ builtin/pull.c: int cmd_pull(int argc, const char **argv, const char *prefix)\n       \tif (opt_ff && !strcmp(opt_ff, \"--ff-only\")) {\n      \n       ## t/t7601-merge-pull-config.sh ##\n     -@@ t/t7601-merge-pull-config.sh: test_expect_success 'pull.rebase=true takes precedence over --ff' '\n     - \n     - # End of precedence rules\n     +@@ t/t7601-merge-pull-config.sh: test_expect_success 'Multiple heads warns about inability to fast forward' '\n     + \ttest_i18ngrep \"You have divergent branches\" err\n     + '\n       \n     --test_expect_failure 'Multiple heads does not warn about fast forwarding' '\n     -+test_expect_success 'Multiple heads does not warn about fast forwarding' '\n     - \tgit reset --hard c1 &&\n     - \tgit pull . c2 c3 2>err &&\n     - \ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n     -@@ t/t7601-merge-pull-config.sh: test_expect_success 'Cannot fast-forward with multiple heads' '\n     +-test_expect_failure 'Multiple can never be fast forwarded' '\n     ++test_expect_success 'Multiple can never be fast forwarded' '\n       \tgit reset --hard c0 &&\n       \ttest_must_fail git -c pull.ff=only pull . c1 c2 c3 2>err &&\n     - \ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err &&\n     --\ttest_i18ngrep \"Not possible to fast-forward, aborting\" err\n     -+\ttest_i18ngrep \"Cannot fast-forward to multiple branches\" err\n     - '\n     - \n     - test_expect_success 'Cannot rebase with multiple heads' '\n     + \ttest_i18ngrep ! \"You have divergent branches\" err &&\n\n-- \ngitgitgadget\n"},{"id":"430728","messageId":"f03b15b7eb03de2fe696e12622031bff484c4912.1626831744.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.v2.git.git.1626831744.gitgitgadget@gmail.com","subject":"[PATCH v2 8/8] pull: fix handling of multiple heads","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-21T01:42:24Z","receivedAt":"2021-07-21T01:43:35Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWith multiple heads, we should not allow rebasing or fast-forwarding.\nMake sure any fast-forward request calls out specifically the fact that\nmultiple branches are in play.  Also, since we cannot fast-forward to\nmultiple branches, fix our computation of can_ff.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/pull.c               | 18 ++++++++++++++----\n t/t7601-merge-pull-config.sh |  2 +-\n 2 files changed, 15 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex a15d4154093..d6631d23d17 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -913,12 +913,18 @@ static int run_rebase(const struct object_id *newbase,\n \treturn ret;\n }\n \n-static int get_can_ff(struct object_id *orig_head, struct object_id *orig_merge_head)\n+static int get_can_ff(struct object_id *orig_head,\n+\t\t      struct oid_array *merge_heads)\n {\n \tint ret;\n \tstruct commit_list *list = NULL;\n \tstruct commit *merge_head, *head;\n+\tstruct object_id *orig_merge_head;\n \n+\tif (merge_heads->nr > 1)\n+\t\treturn 0;\n+\n+\torig_merge_head = &merge_heads->oid[0];\n \thead = lookup_commit_reference(the_repository, orig_head);\n \tcommit_list_insert(head, &list);\n \tmerge_head = lookup_commit_reference(the_repository, orig_merge_head);\n@@ -1057,10 +1063,14 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \t\t\tdie(_(\"Cannot merge multiple branches into empty head.\"));\n \t\treturn pull_into_void(merge_heads.oid, &curr_head);\n \t}\n-\tif (opt_rebase && merge_heads.nr > 1)\n-\t\tdie(_(\"Cannot rebase onto multiple branches.\"));\n+\tif (merge_heads.nr > 1) {\n+\t\tif (opt_rebase)\n+\t\t\tdie(_(\"Cannot rebase onto multiple branches.\"));\n+\t\tif (opt_ff && !strcmp(opt_ff, \"--ff-only\"))\n+\t\t\tdie(_(\"Cannot fast-forward to multiple branches.\"));\n+\t}\n \n-\tcan_ff = get_can_ff(&orig_head, &merge_heads.oid[0]);\n+\tcan_ff = get_can_ff(&orig_head, &merge_heads);\n \n \t/* ff-only takes precedence over rebase */\n \tif (opt_ff && !strcmp(opt_ff, \"--ff-only\")) {\ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex 742ed3981c7..1f652f433ee 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -331,7 +331,7 @@ test_expect_success 'Multiple heads warns about inability to fast forward' '\n \ttest_i18ngrep \"You have divergent branches\" err\n '\n \n-test_expect_failure 'Multiple can never be fast forwarded' '\n+test_expect_success 'Multiple can never be fast forwarded' '\n \tgit reset --hard c0 &&\n \ttest_must_fail git -c pull.ff=only pull . c1 c2 c3 2>err &&\n \ttest_i18ngrep ! \"You have divergent branches\" err &&\n-- \ngitgitgadget\n"},{"id":"430729","messageId":"90d49e0fb7897512d7386a72787bb15c3f23a4f6.1626831744.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.v2.git.git.1626831744.gitgitgadget@gmail.com","subject":"[PATCH v2 7/8] pull: update docs & code for option compatibility with rebasing","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-21T01:42:23Z","receivedAt":"2021-07-21T01:43:48Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\ngit-pull.txt includes merge-options.txt, which is written assuming\nmerges will happen.  git-pull has allowed rebases for many years; update\nthe documentation to reflect that.\n\nWhile at it, pass any `--signoff` flag through to the rebase backend too\nso that we don't have to document it as merge-specific.  Rebase has\nsupported the --signoff flag for years now as well.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge.txt     |  2 ++\n Documentation/git-pull.txt      |  9 ++++----\n Documentation/merge-options.txt | 40 +++++++++++++++++++++++++++++++++\n builtin/pull.c                  |  2 ++\n 4 files changed, 48 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt\nindex 3819fadac1f..e4f3352eb58 100644\n--- a/Documentation/git-merge.txt\n+++ b/Documentation/git-merge.txt\n@@ -61,6 +61,8 @@ merge has resulted in conflicts.\n \n OPTIONS\n -------\n+:git-merge: 1\n+\n include::merge-options.txt[]\n \n -m <msg>::\ndiff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\nindex cad3f6bfcee..077961dfa1f 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -19,13 +19,12 @@ Incorporates changes from a remote repository into the current branch.\n If the current branch is behind the remote, then by default it will\n fast-forward the current branch to match the remote.  If the current\n branch and the remote have diverged, the user needs to specify how to\n-reconcile the divergent branches with `--no-ff`, `--ff`, or `--rebase`\n-(or the corresponding configuration options in `pull.ff` or\n-`pull.rebase`).\n+reconcile the divergent branches with `--rebase` or `--no-rebase` (or\n+the corresponding configuration option in `pull.rebase`).\n \n More precisely, `git pull` runs `git fetch` with the given parameters\n and then depending on configuration options or command line flags,\n-will call either `git merge` or `git rebase` to reconcile diverging\n+will call either `git rebase` or `git merge` to reconcile diverging\n branches.\n \n <repository> should be the name of a remote repository as\n@@ -136,7 +135,7 @@ published that history already.  Do *not* use this option\n unless you have read linkgit:git-rebase[1] carefully.\n \n --no-rebase::\n-\tOverride earlier --rebase.\n+\tThis is shorthand for --rebase=false.\n \n Options related to fetching\n ~~~~~~~~~~~~~~~~~~~~~~~~~~~\ndiff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt\nindex eb0aabd396f..907327bf759 100644\n--- a/Documentation/merge-options.txt\n+++ b/Documentation/merge-options.txt\n@@ -2,6 +2,9 @@\n --no-commit::\n \tPerform the merge and commit the result. This option can\n \tbe used to override --no-commit.\n+ifdef::git-pull[]\n+\tOnly useful when merging.\n+endif::git-pull[]\n +\n With --no-commit perform the merge and stop just before creating\n a merge commit, to give the user a chance to inspect and further\n@@ -39,6 +42,7 @@ set to `no` at the beginning of them.\n \tto `MERGE_MSG` before being passed on to the commit machinery in the\n \tcase of a merge conflict.\n \n+ifdef::git-merge[]\n --ff::\n --no-ff::\n --ff-only::\n@@ -47,6 +51,22 @@ set to `no` at the beginning of them.\n \tdefault unless merging an annotated (and possibly signed) tag\n \tthat is not stored in its natural place in the `refs/tags/`\n \thierarchy, in which case `--no-ff` is assumed.\n+endif::git-merge[]\n+ifdef::git-pull[]\n+--ff-only::\n+\tOnly update to the new history if there is no divergent local\n+\thistory.  This is the default when no method for reconciling\n+\tdivergent histories is provided (via the --rebase=* flags).\n+\n+--ff::\n+--no-ff::\n+\tWhen merging rather than rebasing, specifies how a merge is\n+\thandled when the merged-in history is already a descendant of\n+\tthe current history.  If merging is requested, `--ff` is the\n+\tdefault unless merging an annotated (and possibly signed) tag\n+\tthat is not stored in its natural place in the `refs/tags/`\n+\thierarchy, in which case `--no-ff` is assumed.\n+endif::git-pull[]\n +\n With `--ff`, when possible resolve the merge as a fast-forward (only\n update the branch pointer to match the merged branch; do not create a\n@@ -55,9 +75,11 @@ descendant of the current history), create a merge commit.\n +\n With `--no-ff`, create a merge commit in all cases, even when the merge\n could instead be resolved as a fast-forward.\n+ifdef::git-merge[]\n +\n With `--ff-only`, resolve the merge as a fast-forward when possible.\n When not possible, refuse to merge and exit with a non-zero status.\n+endif::git-merge[]\n \n -S[<keyid>]::\n --gpg-sign[=<keyid>]::\n@@ -73,6 +95,9 @@ When not possible, refuse to merge and exit with a non-zero status.\n \tIn addition to branch names, populate the log message with\n \tone-line descriptions from at most <n> actual commits that are being\n \tmerged. See also linkgit:git-fmt-merge-msg[1].\n+ifdef::git-pull[]\n+\tOnly useful when merging.\n+endif::git-pull[]\n +\n With --no-log do not list one-line descriptions from the\n actual commits being merged.\n@@ -102,10 +127,17 @@ With --no-squash perform the merge and commit the result. This\n option can be used to override --squash.\n +\n With --squash, --commit is not allowed, and will fail.\n+ifdef::git-pull[]\n++\n+Only useful when merging.\n+endif::git-pull[]\n \n --no-verify::\n \tThis option bypasses the pre-merge and commit-msg hooks.\n \tSee also linkgit:githooks[5].\n+ifdef::git-pull[]\n+\tOnly useful when merging.\n+endif::git-pull[]\n \n -s <strategy>::\n --strategy=<strategy>::\n@@ -127,6 +159,10 @@ With --squash, --commit is not allowed, and will fail.\n \tdefault trust model, this means the signing key has been signed by\n \ta trusted key.  If the tip commit of the side branch is not signed\n \twith a valid key, the merge is aborted.\n+ifdef::git-pull[]\n++\n+Only useful when merging.\n+endif::git-pull[]\n \n --summary::\n --no-summary::\n@@ -166,3 +202,7 @@ endif::git-pull[]\n \tprojects that started their lives independently. As that is\n \ta very rare occasion, no configuration variable to enable\n \tthis by default exists and will not be added.\n+ifdef::git-pull[]\n++\n+Only useful when merging.\n+endif::git-pull[]\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 94464ca196f..a15d4154093 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -893,6 +893,8 @@ static int run_rebase(const struct object_id *newbase,\n \tstrvec_pushv(&args, opt_strategy_opts.v);\n \tif (opt_gpg_sign)\n \t\tstrvec_push(&args, opt_gpg_sign);\n+\tif (opt_signoff)\n+\t\tstrvec_push(&args, opt_signoff);\n \tif (opt_autostash == 0)\n \t\tstrvec_push(&args, \"--no-autostash\");\n \telse if (opt_autostash == 1)\n-- \ngitgitgadget\n\n"},{"id":"430730","messageId":"f061f8b4e75a42f3c08b0a8fc63ac87c8f8d77ec.1626831744.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.v2.git.git.1626831744.gitgitgadget@gmail.com","subject":"[PATCH v2 6/8] pull: abort by default when fast-forwarding is not possible","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-21T01:42:22Z","receivedAt":"2021-07-21T01:43:50Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe have for some time shown a long warning when the user does not\nspecify how to reconcile divergent branches with git pull.  Make it an\nerror now.\n\nInitial-patch-by: Alex Henrie <alexhenrie24@gmail.com>\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-pull.txt    | 20 +++++++++------\n builtin/pull.c                | 10 +++++---\n t/t4013-diff-various.sh       |  2 +-\n t/t5520-pull.sh               | 20 +++++++--------\n t/t5521-pull-options.sh       |  4 +--\n t/t5524-pull-msg.sh           |  4 +--\n t/t5553-set-upstream.sh       | 14 +++++-----\n t/t5604-clone-reference.sh    |  4 +--\n t/t6402-merge-rename.sh       | 18 ++++++-------\n t/t6409-merge-subtree.sh      |  6 ++---\n t/t6417-merge-ours-theirs.sh  | 10 ++++----\n t/t7601-merge-pull-config.sh  | 48 +++++++++++++++++------------------\n t/t7603-merge-reduce-heads.sh |  2 +-\n 13 files changed, 84 insertions(+), 78 deletions(-)\n\ndiff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\nindex 5c3fb67c014..cad3f6bfcee 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -15,14 +15,18 @@ SYNOPSIS\n DESCRIPTION\n -----------\n \n-Incorporates changes from a remote repository into the current\n-branch.  In its default mode, `git pull` is shorthand for\n-`git fetch` followed by `git merge FETCH_HEAD`.\n-\n-More precisely, 'git pull' runs 'git fetch' with the given\n-parameters and calls 'git merge' to merge the retrieved branch\n-heads into the current branch.\n-With `--rebase`, it runs 'git rebase' instead of 'git merge'.\n+Incorporates changes from a remote repository into the current branch.\n+If the current branch is behind the remote, then by default it will\n+fast-forward the current branch to match the remote.  If the current\n+branch and the remote have diverged, the user needs to specify how to\n+reconcile the divergent branches with `--no-ff`, `--ff`, or `--rebase`\n+(or the corresponding configuration options in `pull.ff` or\n+`pull.rebase`).\n+\n+More precisely, `git pull` runs `git fetch` with the given parameters\n+and then depending on configuration options or command line flags,\n+will call either `git merge` or `git rebase` to reconcile diverging\n+branches.\n \n <repository> should be the name of a remote repository as\n passed to linkgit:git-fetch[1].  <refspec> can name an\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex da1d4b19666..94464ca196f 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -927,9 +927,9 @@ static int get_can_ff(struct object_id *orig_head, struct object_id *orig_merge_\n \n static void show_advice_pull_non_ff(void)\n {\n-\tadvise(_(\"Pulling without specifying how to reconcile divergent branches is\\n\"\n-\t\t \"discouraged. You can squelch this message by running one of the following\\n\"\n-\t\t \"commands sometime before your next pull:\\n\"\n+\tadvise(_(\"You have divergent branches and need to specify how to reconcile them.\\n\"\n+\t\t \"You can do so by running one of the following commands sometime before\\n\"\n+\t\t \"your next pull:\\n\"\n \t\t \"\\n\"\n \t\t \"  git config pull.rebase false  # merge (the default strategy)\\n\"\n \t\t \"  git config pull.rebase true   # rebase\\n\"\n@@ -1067,8 +1067,10 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \t\topt_rebase = REBASE_FALSE;\n \t}\n \t/* If no action specified and we can't fast forward, then warn. */\n-\tif (!opt_ff && rebase_unspecified && !can_ff)\n+\tif (!opt_ff && rebase_unspecified && !can_ff) {\n \t\tshow_advice_pull_non_ff();\n+\t\tdie(_(\"Need to specify how to reconcile divergent branches.\"));\n+\t}\n \n \tif (opt_rebase) {\n \t\tint ret = 0;\ndiff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh\nindex 7fadc985ccc..eb989f7f191 100755\n--- a/t/t4013-diff-various.sh\n+++ b/t/t4013-diff-various.sh\n@@ -65,7 +65,7 @@ test_expect_success setup '\n \texport GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&\n \n \tgit checkout master &&\n-\tgit pull -s ours . side &&\n+\tgit pull -s ours --no-rebase . side &&\n \n \tGIT_AUTHOR_DATE=\"2006-06-26 00:05:00 +0000\" &&\n \tGIT_COMMITTER_DATE=\"2006-06-26 00:05:00 +0000\" &&\ndiff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\nindex 4b50488141f..56f5864b269 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -136,12 +136,12 @@ test_expect_success 'the default remote . should not break explicit pull' '\n \tgit reset --hard HEAD^ &&\n \techo file >expect &&\n \ttest_cmp expect file &&\n-\tgit pull . second &&\n+\tgit pull --no-rebase . second &&\n \techo modified >expect &&\n \ttest_cmp expect file &&\n \tgit reflog -1 >reflog.actual &&\n \tsed \"s/^[0-9a-f][0-9a-f]*/OBJID/\" reflog.actual >reflog.fuzzy &&\n-\techo \"OBJID HEAD@{0}: pull . second: Fast-forward\" >reflog.expected &&\n+\techo \"OBJID HEAD@{0}: pull --no-rebase . second: Fast-forward\" >reflog.expected &&\n \ttest_cmp reflog.expected reflog.fuzzy\n '\n \n@@ -226,7 +226,7 @@ test_expect_success 'fail if the index has unresolved entries' '\n \ttest_commit modified2 file &&\n \tgit ls-files -u >unmerged &&\n \ttest_must_be_empty unmerged &&\n-\ttest_must_fail git pull . second &&\n+\ttest_must_fail git pull --no-rebase . second &&\n \tgit ls-files -u >unmerged &&\n \ttest_file_not_empty unmerged &&\n \tcp file expected &&\n@@ -409,37 +409,37 @@ test_expect_success 'pull --rebase --no-autostash & rebase.autostash unset' '\n \n test_expect_success 'pull succeeds with dirty working directory and merge.autostash set' '\n \ttest_config merge.autostash true &&\n-\ttest_pull_autostash 2\n+\ttest_pull_autostash 2 --no-rebase\n '\n \n test_expect_success 'pull --autostash & merge.autostash=true' '\n \ttest_config merge.autostash true &&\n-\ttest_pull_autostash 2 --autostash\n+\ttest_pull_autostash 2 --autostash --no-rebase\n '\n \n test_expect_success 'pull --autostash & merge.autostash=false' '\n \ttest_config merge.autostash false &&\n-\ttest_pull_autostash 2 --autostash\n+\ttest_pull_autostash 2 --autostash --no-rebase\n '\n \n test_expect_success 'pull --autostash & merge.autostash unset' '\n \ttest_unconfig merge.autostash &&\n-\ttest_pull_autostash 2 --autostash\n+\ttest_pull_autostash 2 --autostash --no-rebase\n '\n \n test_expect_success 'pull --no-autostash & merge.autostash=true' '\n \ttest_config merge.autostash true &&\n-\ttest_pull_autostash_fail --no-autostash\n+\ttest_pull_autostash_fail --no-autostash --no-rebase\n '\n \n test_expect_success 'pull --no-autostash & merge.autostash=false' '\n \ttest_config merge.autostash false &&\n-\ttest_pull_autostash_fail --no-autostash\n+\ttest_pull_autostash_fail --no-autostash --no-rebase\n '\n \n test_expect_success 'pull --no-autostash & merge.autostash unset' '\n \ttest_unconfig merge.autostash &&\n-\ttest_pull_autostash_fail --no-autostash\n+\ttest_pull_autostash_fail --no-autostash --no-rebase\n '\n \n test_expect_success 'pull.rebase' '\ndiff --git a/t/t5521-pull-options.sh b/t/t5521-pull-options.sh\nindex 63a688bdbf5..7601c919fdc 100755\n--- a/t/t5521-pull-options.sh\n+++ b/t/t5521-pull-options.sh\n@@ -113,7 +113,7 @@ test_expect_success 'git pull --force' '\n \tgit pull two &&\n \ttest_commit A &&\n \tgit branch -f origin &&\n-\tgit pull --all --force\n+\tgit pull --no-rebase --all --force\n \t)\n '\n \n@@ -179,7 +179,7 @@ test_expect_success 'git pull --allow-unrelated-histories' '\n \t(\n \t\tcd dst &&\n \t\ttest_must_fail git pull ../src side &&\n-\t\tgit pull --allow-unrelated-histories ../src side\n+\t\tgit pull --no-rebase --allow-unrelated-histories ../src side\n \t)\n '\n \ndiff --git a/t/t5524-pull-msg.sh b/t/t5524-pull-msg.sh\nindex c278adaa5a2..b2be3605f5a 100755\n--- a/t/t5524-pull-msg.sh\n+++ b/t/t5524-pull-msg.sh\n@@ -28,7 +28,7 @@ test_expect_success setup '\n test_expect_success pull '\n (\n \tcd cloned &&\n-\tgit pull --log &&\n+\tgit pull --no-rebase --log &&\n \tgit log -2 &&\n \tgit cat-file commit HEAD >result &&\n \tgrep Dollar result\n@@ -41,7 +41,7 @@ test_expect_success '--log=1 limits shortlog length' '\n \tgit reset --hard HEAD^ &&\n \ttest \"$(cat afile)\" = original &&\n \ttest \"$(cat bfile)\" = added &&\n-\tgit pull --log=1 &&\n+\tgit pull --no-rebase --log=1 &&\n \tgit log -3 &&\n \tgit cat-file commit HEAD >result &&\n \tgrep Dollar result &&\ndiff --git a/t/t5553-set-upstream.sh b/t/t5553-set-upstream.sh\nindex b1d614ce18c..9c12c0f8c32 100755\n--- a/t/t5553-set-upstream.sh\n+++ b/t/t5553-set-upstream.sh\n@@ -108,27 +108,27 @@ test_expect_success 'setup commit on main and other pull' '\n \n test_expect_success 'pull --set-upstream upstream main sets branch main but not other' '\n \tclear_config main other &&\n-\tgit pull --set-upstream upstream main &&\n+\tgit pull --no-rebase --set-upstream upstream main &&\n \tcheck_config main upstream refs/heads/main &&\n \tcheck_config_missing other\n '\n \n test_expect_success 'pull --set-upstream main:other2 does not set the branch other2' '\n \tclear_config other2 &&\n-\tgit pull --set-upstream upstream main:other2 &&\n+\tgit pull --no-rebase --set-upstream upstream main:other2 &&\n \tcheck_config_missing other2\n '\n \n test_expect_success 'pull --set-upstream upstream other sets branch main' '\n \tclear_config main other &&\n-\tgit pull --set-upstream upstream other &&\n+\tgit pull --no-rebase --set-upstream upstream other &&\n \tcheck_config main upstream refs/heads/other &&\n \tcheck_config_missing other\n '\n \n test_expect_success 'pull --set-upstream upstream tag does not set the tag' '\n \tclear_config three &&\n-\tgit pull --tags --set-upstream upstream three &&\n+\tgit pull --no-rebase --tags --set-upstream upstream three &&\n \tcheck_config_missing three\n '\n \n@@ -144,16 +144,16 @@ test_expect_success 'pull --set-upstream http://nosuchdomain.example.com fails w\n \n test_expect_success 'pull --set-upstream upstream HEAD sets branch HEAD' '\n \tclear_config main other &&\n-\tgit pull --set-upstream upstream HEAD &&\n+\tgit pull --no-rebase --set-upstream upstream HEAD &&\n \tcheck_config main upstream HEAD &&\n \tgit checkout other &&\n-\tgit pull --set-upstream upstream HEAD &&\n+\tgit pull --no-rebase --set-upstream upstream HEAD &&\n \tcheck_config other upstream HEAD\n '\n \n test_expect_success 'pull --set-upstream upstream with more than one branch does nothing' '\n \tclear_config main three &&\n-\tgit pull --set-upstream upstream main three &&\n+\tgit pull --no-rebase --set-upstream upstream main three &&\n \tcheck_config_missing main &&\n \tcheck_config_missing three\n '\ndiff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh\nindex e845d621f61..24340e6d56e 100755\n--- a/t/t5604-clone-reference.sh\n+++ b/t/t5604-clone-reference.sh\n@@ -87,7 +87,7 @@ test_expect_success 'updating origin' '\n '\n \n test_expect_success 'pulling changes from origin' '\n-\tgit -C C pull origin\n+\tgit -C C pull --no-rebase origin\n '\n \n # the 2 local objects are commit and tree from the merge\n@@ -96,7 +96,7 @@ test_expect_success 'that alternate to origin gets used' '\n '\n \n test_expect_success 'pulling changes from origin' '\n-\tgit -C D pull origin\n+\tgit -C D pull --no-rebase origin\n '\n \n # the 5 local objects are expected; file3 blob, commit in A to add it\ndiff --git a/t/t6402-merge-rename.sh b/t/t6402-merge-rename.sh\nindex 3da2896e3bc..3a32b1a45cf 100755\n--- a/t/t6402-merge-rename.sh\n+++ b/t/t6402-merge-rename.sh\n@@ -103,7 +103,7 @@ test_expect_success 'setup' '\n test_expect_success 'pull renaming branch into unrenaming one' \\\n '\n \tgit show-branch &&\n-\ttest_expect_code 1 git pull . white &&\n+\ttest_expect_code 1 git pull --no-rebase . white &&\n \tgit ls-files -s &&\n \ttest_stdout_line_count = 3 git ls-files -u B &&\n \ttest_stdout_line_count = 1 git ls-files -s N &&\n@@ -119,7 +119,7 @@ test_expect_success 'pull renaming branch into another renaming one' \\\n \trm -f B &&\n \tgit reset --hard &&\n \tgit checkout red &&\n-\ttest_expect_code 1 git pull . white &&\n+\ttest_expect_code 1 git pull --no-rebase . white &&\n \ttest_stdout_line_count = 3 git ls-files -u B &&\n \ttest_stdout_line_count = 1 git ls-files -s N &&\n \tsed -ne \"/^g/{\n@@ -133,7 +133,7 @@ test_expect_success 'pull unrenaming branch into renaming one' \\\n '\n \tgit reset --hard &&\n \tgit show-branch &&\n-\ttest_expect_code 1 git pull . main &&\n+\ttest_expect_code 1 git pull --no-rebase . main &&\n \ttest_stdout_line_count = 3 git ls-files -u B &&\n \ttest_stdout_line_count = 1 git ls-files -s N &&\n \tsed -ne \"/^g/{\n@@ -147,7 +147,7 @@ test_expect_success 'pull conflicting renames' \\\n '\n \tgit reset --hard &&\n \tgit show-branch &&\n-\ttest_expect_code 1 git pull . blue &&\n+\ttest_expect_code 1 git pull --no-rebase . blue &&\n \ttest_stdout_line_count = 1 git ls-files -u A &&\n \ttest_stdout_line_count = 1 git ls-files -u B &&\n \ttest_stdout_line_count = 1 git ls-files -u C &&\n@@ -163,7 +163,7 @@ test_expect_success 'interference with untracked working tree file' '\n \tgit reset --hard &&\n \tgit show-branch &&\n \techo >A this file should not matter &&\n-\ttest_expect_code 1 git pull . white &&\n+\ttest_expect_code 1 git pull --no-rebase . white &&\n \ttest_path_is_file A\n '\n \n@@ -173,7 +173,7 @@ test_expect_success 'interference with untracked working tree file' '\n \tgit show-branch &&\n \trm -f A &&\n \techo >A this file should not matter &&\n-\ttest_expect_code 1 git pull . red &&\n+\ttest_expect_code 1 git pull --no-rebase . red &&\n \ttest_path_is_file A\n '\n \n@@ -183,7 +183,7 @@ test_expect_success 'interference with untracked working tree file' '\n \tgit checkout -f main &&\n \tgit tag -f anchor &&\n \tgit show-branch &&\n-\tgit pull . yellow &&\n+\tgit pull --no-rebase . yellow &&\n \ttest_path_is_missing M &&\n \tgit reset --hard anchor\n '\n@@ -210,7 +210,7 @@ test_expect_success 'updated working tree file should prevent the merge' '\n \techo >>M one line addition &&\n \tcat M >M.saved &&\n \tgit update-index M &&\n-\ttest_expect_code 128 git pull . yellow &&\n+\ttest_expect_code 128 git pull --no-rebase . yellow &&\n \ttest_cmp M M.saved &&\n \trm -f M.saved\n '\n@@ -222,7 +222,7 @@ test_expect_success 'interference with untracked working tree file' '\n \tgit tag -f anchor &&\n \tgit show-branch &&\n \techo >M this file should not matter &&\n-\tgit pull . main &&\n+\tgit pull --no-rebase . main &&\n \ttest_path_is_file M &&\n \t! {\n \t\tgit ls-files -s |\ndiff --git a/t/t6409-merge-subtree.sh b/t/t6409-merge-subtree.sh\nindex d406b2343cb..ba7890ec521 100755\n--- a/t/t6409-merge-subtree.sh\n+++ b/t/t6409-merge-subtree.sh\n@@ -100,7 +100,7 @@ test_expect_success 'merge update' '\n \tgit checkout -b topic_2 &&\n \tgit commit -m \"update git-gui\" &&\n \tcd ../git &&\n-\tgit pull -s subtree gui topic_2 &&\n+\tgit pull --no-rebase -s subtree gui topic_2 &&\n \tgit ls-files -s >actual &&\n \t(\n \t\techo \"100644 $o3 0\tgit-gui/git-gui.sh\" &&\n@@ -129,7 +129,7 @@ test_expect_success 'initial ambiguous subtree' '\n test_expect_success 'merge using explicit' '\n \tcd ../git &&\n \tgit reset --hard topic_2 &&\n-\tgit pull -Xsubtree=git-gui gui topic_2 &&\n+\tgit pull --no-rebase -Xsubtree=git-gui gui topic_2 &&\n \tgit ls-files -s >actual &&\n \t(\n \t\techo \"100644 $o3 0\tgit-gui/git-gui.sh\" &&\n@@ -142,7 +142,7 @@ test_expect_success 'merge using explicit' '\n test_expect_success 'merge2 using explicit' '\n \tcd ../git &&\n \tgit reset --hard topic_2 &&\n-\tgit pull -Xsubtree=git-gui2 gui topic_2 &&\n+\tgit pull --no-rebase -Xsubtree=git-gui2 gui topic_2 &&\n \tgit ls-files -s >actual &&\n \t(\n \t\techo \"100644 $o1 0\tgit-gui/git-gui.sh\" &&\ndiff --git a/t/t6417-merge-ours-theirs.sh b/t/t6417-merge-ours-theirs.sh\nindex ac9aee9a662..ec065d6a658 100755\n--- a/t/t6417-merge-ours-theirs.sh\n+++ b/t/t6417-merge-ours-theirs.sh\n@@ -69,11 +69,11 @@ test_expect_success 'binary file with -Xours/-Xtheirs' '\n '\n \n test_expect_success 'pull passes -X to underlying merge' '\n-\tgit reset --hard main && git pull -s recursive -Xours . side &&\n-\tgit reset --hard main && git pull -s recursive -X ours . side &&\n-\tgit reset --hard main && git pull -s recursive -Xtheirs . side &&\n-\tgit reset --hard main && git pull -s recursive -X theirs . side &&\n-\tgit reset --hard main && test_must_fail git pull -s recursive -X bork . side\n+\tgit reset --hard main && git pull --no-rebase -s recursive -Xours . side &&\n+\tgit reset --hard main && git pull --no-rebase -s recursive -X ours . side &&\n+\tgit reset --hard main && git pull --no-rebase -s recursive -Xtheirs . side &&\n+\tgit reset --hard main && git pull --no-rebase -s recursive -X theirs . side &&\n+\tgit reset --hard main && test_must_fail git pull --no-rebase -s recursive -X bork . side\n '\n \n test_expect_success SYMLINKS 'symlink with -Xours/-Xtheirs' '\ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex 0c0dfecd2a3..742ed3981c7 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -27,120 +27,120 @@ test_expect_success 'setup' '\n \tgit tag c3\n '\n \n-test_expect_success 'pull.rebase not set' '\n+test_expect_success 'pull.rebase not set, ff possible' '\n \tgit reset --hard c0 &&\n \tgit pull . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and pull.ff=true' '\n \tgit reset --hard c0 &&\n \ttest_config pull.ff true &&\n \tgit pull . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and pull.ff=false' '\n \tgit reset --hard c0 &&\n \ttest_config pull.ff false &&\n \tgit pull . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and pull.ff=only' '\n \tgit reset --hard c0 &&\n \ttest_config pull.ff only &&\n \tgit pull . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --rebase given' '\n \tgit reset --hard c0 &&\n \tgit pull --rebase . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --no-rebase given' '\n \tgit reset --hard c0 &&\n \tgit pull --no-rebase . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --ff given' '\n \tgit reset --hard c0 &&\n \tgit pull --ff . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --no-ff given' '\n \tgit reset --hard c0 &&\n \tgit pull --no-ff . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --ff-only given' '\n \tgit reset --hard c0 &&\n \tgit pull --ff-only . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set (not-fast-forward)' '\n \tgit reset --hard c2 &&\n-\tgit -c color.advice=always pull . c1 2>err &&\n+\ttest_must_fail git -c color.advice=always pull . c1 2>err &&\n \ttest_decode_color <err >decoded &&\n \ttest_i18ngrep \"<YELLOW>hint: \" decoded &&\n-\ttest_i18ngrep \"Pulling without specifying how to reconcile\" decoded\n+\ttest_i18ngrep \"You have divergent branches\" decoded\n '\n \n test_expect_success 'pull.rebase not set and pull.ff=true (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \ttest_config pull.ff true &&\n \tgit pull . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and pull.ff=false (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \ttest_config pull.ff false &&\n \tgit pull . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and pull.ff=only (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \ttest_config pull.ff only &&\n \ttest_must_fail git pull . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --rebase given (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \tgit pull --rebase . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --no-rebase given (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \tgit pull --no-rebase . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --ff given (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \tgit pull --ff . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --no-ff given (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \tgit pull --no-ff . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --ff-only given (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \ttest_must_fail git pull --ff-only . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_does_rebase () {\n@@ -325,16 +325,16 @@ test_expect_success 'pull.rebase=false and --ff, ff not possible' '\n \n # End of groupings for conflicting merge vs. rebase flags/options\n \n-test_expect_failure 'Multiple heads warns about inability to fast forward' '\n+test_expect_success 'Multiple heads warns about inability to fast forward' '\n \tgit reset --hard c1 &&\n \ttest_must_fail git pull . c2 c3 2>err &&\n-\ttest_i18ngrep \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep \"You have divergent branches\" err\n '\n \n test_expect_failure 'Multiple can never be fast forwarded' '\n \tgit reset --hard c0 &&\n \ttest_must_fail git -c pull.ff=only pull . c1 c2 c3 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err &&\n+\ttest_i18ngrep ! \"You have divergent branches\" err &&\n \t# In addition to calling out \"cannot fast-forward\", we very much\n \t# want the \"multiple branches\" piece to be called out to users.\n \ttest_i18ngrep \"Cannot fast-forward to multiple branches\" err\n@@ -343,7 +343,7 @@ test_expect_failure 'Multiple can never be fast forwarded' '\n test_expect_success 'Cannot rebase with multiple heads' '\n \tgit reset --hard c0 &&\n \ttest_must_fail git -c pull.rebase=true pull . c1 c2 c3 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err &&\n+\ttest_i18ngrep ! \"You have divergent branches\" err &&\n \ttest_i18ngrep \"Cannot rebase onto multiple branches.\" err\n '\n \ndiff --git a/t/t7603-merge-reduce-heads.sh b/t/t7603-merge-reduce-heads.sh\nindex 98948955ae5..27cd94ad6f7 100755\n--- a/t/t7603-merge-reduce-heads.sh\n+++ b/t/t7603-merge-reduce-heads.sh\n@@ -68,7 +68,7 @@ test_expect_success 'merge c1 with c2, c3, c4, c5' '\n \n test_expect_success 'pull c2, c3, c4, c5 into c1' '\n \tgit reset --hard c1 &&\n-\tgit pull . c2 c3 c4 c5 &&\n+\tgit pull --no-rebase . c2 c3 c4 c5 &&\n \ttest \"$(git rev-parse c1)\" != \"$(git rev-parse HEAD)\" &&\n \ttest \"$(git rev-parse c1)\" = \"$(git rev-parse HEAD^1)\" &&\n \ttest \"$(git rev-parse c2)\" = \"$(git rev-parse HEAD^2)\" &&\n-- \ngitgitgadget\n\n"},{"id":"430731","messageId":"1a821d3b1ddf22b62b14d3b573015c3d8c90e2de.1626831744.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.v2.git.git.1626831744.gitgitgadget@gmail.com","subject":"[PATCH v2 4/8] pull: since --ff-only overrides, handle it first","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-21T01:42:20Z","receivedAt":"2021-07-21T01:43:54Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThere are both merge and rebase branches in the logic, and previously\nboth had to handle fast-forwarding.  Merge handled that implicitly\n(because git merge handles it directly), while in rebase it was\nexplicit.  Given that the --ff-only flag is meant to override any\n--rebase or --no-rebase, make the code reflect that by handling\n--ff-only before the merge-vs-rebase logic.\n\nIt turns out that this also fixes a bug for submodules.  Previously,\nwhen --ff-only was given, the code would run `merge --ff-only` on the\nmain module, and then run `submodule update --recursive --rebase` on the\nsubmodules.  With this change, we still run `merge --ff-only` on the\nmain module, but now run `submodule update --recursive --checkout` on\nthe submodules.  I believe this better reflects the intent of --ff-only\nto have it apply to both the main module and the submodules.\n\n(Sidenote: It is somewhat interesting that all merges pass `--checkout`\nto submodule update, even when `--no-ff` is specified, meaning that it\nwill only do fast-forward merges for submodules.  This was discussed in\ncommit a6d7eb2c7a (\"pull: optionally rebase submodules (remote submodule\nchanges only)\", 2017-06-23).  The same limitations apply now as then, so\nwe are not trying to fix this at this time.)\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/pull.c  | 24 +++++++++---------------\n t/t5520-pull.sh |  6 +++---\n 2 files changed, 12 insertions(+), 18 deletions(-)\n\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex d9796604825..92150f976cd 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -1046,15 +1046,15 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \n \tcan_ff = get_can_ff(&orig_head, &merge_heads.oid[0]);\n \n-\tif (!can_ff) {\n-\t\tif (opt_ff) {\n-\t\t\tif (!strcmp(opt_ff, \"--ff-only\"))\n-\t\t\t\tdie_ff_impossible();\n-\t\t} else {\n-\t\t\tif (rebase_unspecified && opt_verbosity >= 0)\n-\t\t\t\tshow_advice_pull_non_ff();\n-\t\t}\n+\t/* ff-only takes precedence over rebase */\n+\tif (opt_ff && !strcmp(opt_ff, \"--ff-only\")) {\n+\t\tif (!can_ff)\n+\t\t\tdie_ff_impossible();\n+\t\topt_rebase = REBASE_FALSE;\n \t}\n+\t/* If no action specified and we can't fast forward, then warn. */\n+\tif (!opt_ff && rebase_unspecified && !can_ff)\n+\t\tshow_advice_pull_non_ff();\n \n \tif (opt_rebase) {\n \t\tint ret = 0;\n@@ -1069,13 +1069,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \t\t    submodule_touches_in_range(the_repository, &upstream, &curr_head))\n \t\t\tdie(_(\"cannot rebase with locally recorded submodule modifications\"));\n \n-\t\tif (can_ff) {\n-\t\t\t/* we can fast-forward this without invoking rebase */\n-\t\t\topt_ff = \"--ff-only\";\n-\t\t\tret = run_merge();\n-\t\t} else {\n-\t\t\tret = run_rebase(&newbase, &upstream);\n-\t\t}\n+\t\tret = run_rebase(&newbase, &upstream);\n \n \t\tif (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON ||\n \t\t\t     recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND))\ndiff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\nindex e2c0c510222..4b50488141f 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -295,7 +295,7 @@ test_expect_success '--rebase (merge) fast forward' '\n \t# The above only validates the result.  Did we actually bypass rebase?\n \tgit reflog -1 >reflog.actual &&\n \tsed \"s/^[0-9a-f][0-9a-f]*/OBJID/\" reflog.actual >reflog.fuzzy &&\n-\techo \"OBJID HEAD@{0}: pull --rebase . ff: Fast-forward\" >reflog.expected &&\n+\techo \"OBJID HEAD@{0}: pull --rebase . ff (finish): returning to refs/heads/to-rebase\" >reflog.expected &&\n \ttest_cmp reflog.expected reflog.fuzzy\n '\n \n@@ -307,8 +307,8 @@ test_expect_success '--rebase (am) fast forward' '\n \n \t# The above only validates the result.  Did we actually bypass rebase?\n \tgit reflog -1 >reflog.actual &&\n-\tsed \"s/^[0-9a-f][0-9a-f]*/OBJID/\" reflog.actual >reflog.fuzzy &&\n-\techo \"OBJID HEAD@{0}: pull --rebase . ff: Fast-forward\" >reflog.expected &&\n+\tsed -e \"s/^[0-9a-f][0-9a-f]*/OBJID/\" -e \"s/[0-9a-f][0-9a-f]*$/OBJID/\" reflog.actual >reflog.fuzzy &&\n+\techo \"OBJID HEAD@{0}: rebase finished: refs/heads/to-rebase onto OBJID\" >reflog.expected &&\n \ttest_cmp reflog.expected reflog.fuzzy\n '\n \n-- \ngitgitgadget\n\n"},{"id":"430767","messageId":"60f80feeb4461_1305dc208b8@natae.notmuch","threadId":"56121","inReplyTo":"pull.1049.v2.git.git.1626831744.gitgitgadget@gmail.com","subject":"RE: [PATCH v2 0/8] Handle pull option precedence","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-21T12:15:42Z","receivedAt":"2021-07-21T12:15:55Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"It's a bit tedious to keep repeating all the ways this approach is\nflawed, but here we go again.\n\nElijah Newren via GitGitGadget wrote:\n> Based on a recent list of rules for flag/option precedence for git-pull[1]\n> from Junio (particularly focusing on rebase vs. merge vs. fast-forward),\n> here's an attempt to implement and document it. Given multiple recent\n> surprises from users about some of these behaviors[2][3]\n\nIt might have been a surprise for you, but Son Luong Ngoc's usage of the\noptions is correct.\n\n>  * Patches 1-2: new testcases (see the commit messages for the rules)\n\nThese test cases check for backwards incompatible changes, not what the\ncode is doing (and is supposed to be doing) right now.\n\n>  * Patch 3: Alex's recent patch (abort if --ff-only but can't do so)\n\nThis breaks backwards compatibility.\n\nUsers will complain.\n\n>  * Patches 4-6: fix the precedence parts Alex didn't cover\n\nYet another backwards incompatible change.\n\n>  * Patch 7: Alex's other patch, abort if rebase vs. merge not specified\n\nMore brekage without a single day of deprecation warning.\n\n>  * Patch 8: Compatibility of git-pull with merge-options.txt (think\n>    rebasing)\n\nThis doesn't update anything in Documentation/config, which is now\nclearly broken.\n\n-- \nFelipe Contreras\n"},{"id":"430768","messageId":"60f811ffd40b8_1305dc2082a@natae.notmuch","threadId":"56121","inReplyTo":"175609272110e4f8d71dce3e6c4e6ca010a8f366.1626831744.git.gitgitgadget@gmail.com","subject":"RE: [PATCH v2 1/8] t7601: test interaction of merge/rebase/fast-forward flags and options","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-21T12:24:31Z","receivedAt":"2021-07-21T12:24:36Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Elijah Newren via GitGitGadget wrote:\n> From: Elijah Newren <newren@gmail.com>\n> \n> The interaction of rebase and merge flags and options was not well\n> tested.  Add several tests to check for correct behavior from the\n> following rules:\n>     * --ff-only vs. --[no-]rebase\n>       (and the related pull.ff=only vs. pull.rebase)\n>     * --rebase[=!false] vs. --no-ff and --ff\n>       (and the related pull.rebase=!false overrides pull.ff=!only)\n\nThis breaks current behavior.\n\n>     * command line flags take precedence over config, except:\n>       * --no-rebase heeds pull.ff=!only\n>       * pull.rebase=!false vs --no-ff and --ff\n\nThis isn't explaind anywhere.\n\n> --- a/t/t7601-merge-pull-config.sh\n> +++ b/t/t7601-merge-pull-config.sh\n> @@ -143,6 +143,188 @@\n\n> +#\n> +# Group 1: Interaction of --ff-only with --[no-]rebase\n> +# (And related interaction of pull.ff=only with pull.rebase)\n> +#\n> +test_expect_failure '--ff-only overrides --rebase' '\n> +\ttest_attempts_fast_forward pull --rebase --ff-only\n> +'\n> +\n> +test_expect_failure '--ff-only overrides --rebase even if first' '\n> +\ttest_attempts_fast_forward pull --ff-only --rebase\n> +'\n> +\n> +test_expect_success '--ff-only overrides --no-rebase' '\n> +\ttest_attempts_fast_forward pull --ff-only --no-rebase\n> +'\n\nThese make absolutely no sense. Why does --rebase override pull.ff=only,\nbut not --ff-only?\n\n> +test_expect_failure 'pull.ff=only overrides pull.rebase=true' '\n> +\ttest_attempts_fast_forward -c pull.ff=only -c pull.rebase=true pull\n> +'\n> +\n> +test_expect_success 'pull.ff=only overrides pull.rebase=false' '\n> +\ttest_attempts_fast_forward -c pull.ff=only -c pull.rebase=false pull\n> +'\n\nThis breaks existing behavior.\n\nUsers expect `-c pull.ff=only -c pull.rebase=true` to do a rebase.\n\n> +test_expect_failure '--no-rebase takes precedence over pull.ff=only' '\n> +\ttest_falls_back_to_full_merge -c pull.ff=only pull --no-rebase\n> +'\n\nThis breaks existing behavior.\n\n-- \nFelipe Contreras\n"},{"id":"430769","messageId":"60f8124b341c2_1305dc2081@natae.notmuch","threadId":"56121","inReplyTo":"c45cd2396664e915a1d962e6d131505df51b118e.1626831744.git.gitgitgadget@gmail.com","subject":"RE: [PATCH v2 3/8] pull: abort if --ff-only is given and fast-forwarding is impossible","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-21T12:25:47Z","receivedAt":"2021-07-21T12:25:52Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Alex Henrie via GitGitGadget wrote:\n> From: Alex Henrie <alexhenrie24@gmail.com>\n> \n> The warning about pulling without specifying how to reconcile divergent\n> branches says that after setting pull.rebase to true, --ff-only can\n> still be passed on the command line to require a fast-forward. Make that\n> actually work.\n\nThis is a backwards-incompatible change.\n\n-- \nFelipe Contreras\n"},{"id":"430770","messageId":"60f812818d139_1305dc20895@natae.notmuch","threadId":"56121","inReplyTo":"9b116f3d284840052945292f73c47bacdb2f86d6.1626831744.git.gitgitgadget@gmail.com","subject":"RE: [PATCH v2 5/8] pull: make --rebase and --no-rebase override pull.ff=only","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-21T12:26:41Z","receivedAt":"2021-07-21T12:26:45Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Elijah Newren via GitGitGadget wrote:\n> From: Elijah Newren <newren@gmail.com>\n> \n> Fix the last few precedence tests failing in t7601 by now implementing\n> the logic to have --[no-]rebase override a pull.ff=only config setting.\n\nOnce again, this breaks existing behavior.\n\n-- \nFelipe Contreras\n"},{"id":"430771","messageId":"60f812afb2f69_1305dc208c8@natae.notmuch","threadId":"56121","inReplyTo":"f061f8b4e75a42f3c08b0a8fc63ac87c8f8d77ec.1626831744.git.gitgitgadget@gmail.com","subject":"RE: [PATCH v2 6/8] pull: abort by default when fast-forwarding is not possible","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-21T12:27:27Z","receivedAt":"2021-07-21T12:27:35Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Elijah Newren via GitGitGadget wrote:\n> From: Elijah Newren <newren@gmail.com>\n> \n> We have for some time shown a long warning when the user does not\n> specify how to reconcile divergent branches with git pull.  Make it an\n> error now.\n\nYes, but the warning doesn't say anything about changing the defaults or\ndeprecation.\n\n-- \nFelipe Contreras\n"},{"id":"430786","messageId":"5f334459-26ef-6bf6-d72e-31564b10cd2c@aixigo.com","threadId":"56121","inReplyTo":"1a821d3b1ddf22b62b14d3b573015c3d8c90e2de.1626831744.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 4/8] pull: since --ff-only overrides, handle it first","fromName":"Matthias Baumgarten","fromEmail":"matthias.baumgarten@aixigo.com","sentAt":"2021-07-21T19:18:38Z","receivedAt":"2021-07-21T19:18:49Z","isPatch":true,"sender":{"key":"matthias.baumgarten@aixigo.com","avatar":null},"body":"On 7/21/21 3:42 AM, Elijah Newren via GitGitGadget wrote:\n> There are both merge and rebase branches in the logic, and previously\n> both had to handle fast-forwarding.  Merge handled that implicitly\n> (because git merge handles it directly), while in rebase it was\n> explicit.  Given that the --ff-only flag is meant to override any\n> --rebase or --no-rebase, make the code reflect that by handling\n> --ff-only before the merge-vs-rebase logic.\n\nGreat. That now will work as I would expect it to.\n\n-- \naixigo AG\nKarl-Friedrich-Str. 68, 52072 Aachen, Germany\nphone: +49 (0)241 559709-390, fax: +49 (0)241 559709-99\nemail: matthias.baumgarten@aixigo.com\nweb: https://www.aixigo.com\nDistrict Court Aachen – HRB 8057\nBoard: Christian Friedrich, Tobias Haustein\nChairman of the Supervisory Board: Dr. Roland Schlager\n"},{"id":"430789","messageId":"xmqqlf5zctey.fsf@gitster.g","threadId":"56121","inReplyTo":"1a821d3b1ddf22b62b14d3b573015c3d8c90e2de.1626831744.git.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 4/8] pull: since --ff-only overrides, handle it first","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-07-21T20:18:29Z","receivedAt":"2021-07-21T20:18:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> -\tif (!can_ff) {\n> -\t\tif (opt_ff) {\n> -\t\t\tif (!strcmp(opt_ff, \"--ff-only\"))\n> -\t\t\t\tdie_ff_impossible();\n> -\t\t} else {\n> -\t\t\tif (rebase_unspecified && opt_verbosity >= 0)\n> -\t\t\t\tshow_advice_pull_non_ff();\n> -\t\t}\n> +\t/* ff-only takes precedence over rebase */\n> +\tif (opt_ff && !strcmp(opt_ff, \"--ff-only\")) {\n> +\t\tif (!can_ff)\n> +\t\t\tdie_ff_impossible();\n> +\t\topt_rebase = REBASE_FALSE;\n>  \t}\n> +\t/* If no action specified and we can't fast forward, then warn. */\n> +\tif (!opt_ff && rebase_unspecified && !can_ff)\n> +\t\tshow_advice_pull_non_ff();\n\nThis part makes sense, but ...\n\n> @@ -1069,13 +1069,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n>  \t\t    submodule_touches_in_range(the_repository, &upstream, &curr_head))\n>  \t\t\tdie(_(\"cannot rebase with locally recorded submodule modifications\"));\n>  \n> -\t\tif (can_ff) {\n> -\t\t\t/* we can fast-forward this without invoking rebase */\n> -\t\t\topt_ff = \"--ff-only\";\n> -\t\t\tret = run_merge();\n> -\t\t} else {\n> -\t\t\tret = run_rebase(&newbase, &upstream);\n> -\t\t}\n> +\t\tret = run_rebase(&newbase, &upstream);\n\n... as I already pointed out, this does not seem to belong to the\nchange.\n\nWhat makes this hunk necessary?\n\nWe used to use run_merge() to fast-forward, now we let run_rebase()\nto first \"checkout\" their tip, which ends up to be a fast-forward in\nthe \"can_ff\" situation.  As a side effect, opt_ff gets contaminated\nwith the current code, but that would not affect what happens after\nthis part (i.e. call to rebase_submodules()).\n"},{"id":"430797","messageId":"60f88f0b8776b_1c9e6920863@natae.notmuch","threadId":"56121","inReplyTo":"5f334459-26ef-6bf6-d72e-31564b10cd2c@aixigo.com","subject":"Re: [PATCH v2 4/8] pull: since --ff-only overrides, handle it first","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-21T21:18:03Z","receivedAt":"2021-07-21T21:18:08Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Matthias Baumgarten wrote:\n> On 7/21/21 3:42 AM, Elijah Newren via GitGitGadget wrote:\n> > There are both merge and rebase branches in the logic, and previously\n> > both had to handle fast-forwarding.  Merge handled that implicitly\n> > (because git merge handles it directly), while in rebase it was\n> > explicit.  Given that the --ff-only flag is meant to override any\n> > --rebase or --no-rebase, make the code reflect that by handling\n> > --ff-only before the merge-vs-rebase logic.\n> \n> Great. That now will work as I would expect it to.\n\nYes, but what about the current users that have setup pull.ff=only\nalready?\n\nThey are already relying on existing behavior.\n\nWhat do you think users currently expect this to do?\n\n  [pull]\n    ff = only\n    rebase = true\n\n  git pull --no-rebase\n\n-- \nFelipe Contreras\n"},{"id":"430866","messageId":"CABPp-BG--c4Q8bFv20382dyQJgAWqZymT+gGD0eR2v0QB2RQxg@mail.gmail.com","threadId":"56121","inReplyTo":"xmqqlf5zctey.fsf@gitster.g","subject":"Re: [PATCH v2 4/8] pull: since --ff-only overrides, handle it first","fromName":"Elijah Newren","fromEmail":"newren@gmail.com","sentAt":"2021-07-22T03:42:09Z","receivedAt":"2021-07-22T03:42:25Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"On Wed, Jul 21, 2021 at 1:18 PM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> \"Elijah Newren via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n>\n> > -     if (!can_ff) {\n> > -             if (opt_ff) {\n> > -                     if (!strcmp(opt_ff, \"--ff-only\"))\n> > -                             die_ff_impossible();\n> > -             } else {\n> > -                     if (rebase_unspecified && opt_verbosity >= 0)\n> > -                             show_advice_pull_non_ff();\n> > -             }\n> > +     /* ff-only takes precedence over rebase */\n> > +     if (opt_ff && !strcmp(opt_ff, \"--ff-only\")) {\n> > +             if (!can_ff)\n> > +                     die_ff_impossible();\n> > +             opt_rebase = REBASE_FALSE;\n> >       }\n> > +     /* If no action specified and we can't fast forward, then warn. */\n> > +     if (!opt_ff && rebase_unspecified && !can_ff)\n> > +             show_advice_pull_non_ff();\n>\n> This part makes sense, but ...\n>\n> > @@ -1069,13 +1069,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n> >                   submodule_touches_in_range(the_repository, &upstream, &curr_head))\n> >                       die(_(\"cannot rebase with locally recorded submodule modifications\"));\n> >\n> > -             if (can_ff) {\n> > -                     /* we can fast-forward this without invoking rebase */\n> > -                     opt_ff = \"--ff-only\";\n> > -                     ret = run_merge();\n> > -             } else {\n> > -                     ret = run_rebase(&newbase, &upstream);\n> > -             }\n> > +             ret = run_rebase(&newbase, &upstream);\n>\n> ... as I already pointed out, this does not seem to belong to the\n> change.\n>\n> What makes this hunk necessary?\n>\n> We used to use run_merge() to fast-forward, now we let run_rebase()\n> to first \"checkout\" their tip, which ends up to be a fast-forward in\n> the \"can_ff\" situation.  As a side effect, opt_ff gets contaminated\n> with the current code, but that would not affect what happens after\n> this part (i.e. call to rebase_submodules()).\n\nIndeed, you are right.  Sorry about that, I'll re-roll with this hunk\nremoved, and the modifications to t5520 pulled out as well.\n"},{"id":"430869","messageId":"175609272110e4f8d71dce3e6c4e6ca010a8f366.1626930290.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.v3.git.git.1626930290.gitgitgadget@gmail.com","subject":"[PATCH v3 1/8] t7601: test interaction of merge/rebase/fast-forward flags and options","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-22T05:04:43Z","receivedAt":"2021-07-22T05:04:55Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThe interaction of rebase and merge flags and options was not well\ntested.  Add several tests to check for correct behavior from the\nfollowing rules:\n    * --ff-only vs. --[no-]rebase\n      (and the related pull.ff=only vs. pull.rebase)\n    * --rebase[=!false] vs. --no-ff and --ff\n      (and the related pull.rebase=!false overrides pull.ff=!only)\n    * command line flags take precedence over config, except:\n      * --no-rebase heeds pull.ff=!only\n      * pull.rebase=!false vs --no-ff and --ff\n\nFor more details behind these rules and a larger table of individual\ncases, refer to https://lore.kernel.org/git/xmqqwnpqot4m.fsf@gitster.g/\nand the links found therein.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t7601-merge-pull-config.sh | 182 +++++++++++++++++++++++++++++++++++\n 1 file changed, 182 insertions(+)\n\ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex 52e8ccc933a..a44e6e69f2b 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -143,6 +143,188 @@ test_expect_success 'pull.rebase not set and --ff-only given (not-fast-forward)'\n \ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n '\n \n+test_does_rebase () {\n+\tgit reset --hard c2 &&\n+\tgit \"$@\" . c1 &&\n+\t# Check that we actually did a rebase\n+\tgit rev-list --count HEAD >actual &&\n+\tgit rev-list --merges --count HEAD >>actual &&\n+\ttest_write_lines 3 0 >expect &&\n+\ttest_cmp expect actual &&\n+\trm actual expect\n+}\n+\n+# Prefers merge over fast-forward\n+test_does_merge_when_ff_possible () {\n+\tgit reset --hard c0 &&\n+\tgit \"$@\" . c1 &&\n+\t# Check that we actually did a merge\n+\tgit rev-list --count HEAD >actual &&\n+\tgit rev-list --merges --count HEAD >>actual &&\n+\ttest_write_lines 3 1 >expect &&\n+\ttest_cmp expect actual &&\n+\trm actual expect\n+}\n+\n+# Prefers fast-forward over merge or rebase\n+test_does_fast_forward () {\n+\tgit reset --hard c0 &&\n+\tgit \"$@\" . c1 &&\n+\n+\t# Check that we did not get any merges\n+\tgit rev-list --count HEAD >actual &&\n+\tgit rev-list --merges --count HEAD >>actual &&\n+\ttest_write_lines 2 0 >expect &&\n+\ttest_cmp expect actual &&\n+\n+\t# Check that we ended up at c1\n+\tgit rev-parse HEAD >actual &&\n+\tgit rev-parse c1^{commit} >expect &&\n+\ttest_cmp actual expect &&\n+\n+\t# Remove temporary files\n+\trm actual expect\n+}\n+\n+# Doesn't fail when fast-forward not possible; does a merge\n+test_falls_back_to_full_merge () {\n+\tgit reset --hard c2 &&\n+\tgit \"$@\" . c1 &&\n+\t# Check that we actually did a merge\n+\tgit rev-list --count HEAD >actual &&\n+\tgit rev-list --merges --count HEAD >>actual &&\n+\ttest_write_lines 4 1 >expect &&\n+\ttest_cmp expect actual &&\n+\trm actual expect\n+}\n+\n+# Attempts fast forward, which is impossible, and bails\n+test_attempts_fast_forward () {\n+\tgit reset --hard c2 &&\n+\ttest_must_fail git \"$@\" . c1 2>err &&\n+\ttest_i18ngrep \"Not possible to fast-forward, aborting\" err\n+}\n+\n+#\n+# Group 1: Interaction of --ff-only with --[no-]rebase\n+# (And related interaction of pull.ff=only with pull.rebase)\n+#\n+test_expect_failure '--ff-only overrides --rebase' '\n+\ttest_attempts_fast_forward pull --rebase --ff-only\n+'\n+\n+test_expect_failure '--ff-only overrides --rebase even if first' '\n+\ttest_attempts_fast_forward pull --ff-only --rebase\n+'\n+\n+test_expect_success '--ff-only overrides --no-rebase' '\n+\ttest_attempts_fast_forward pull --ff-only --no-rebase\n+'\n+\n+test_expect_failure 'pull.ff=only overrides pull.rebase=true' '\n+\ttest_attempts_fast_forward -c pull.ff=only -c pull.rebase=true pull\n+'\n+\n+test_expect_success 'pull.ff=only overrides pull.rebase=false' '\n+\ttest_attempts_fast_forward -c pull.ff=only -c pull.rebase=false pull\n+'\n+\n+# Group 2: --rebase=[!false] overrides --no-ff and --ff\n+# (And related interaction of pull.rebase=!false and pull.ff=!only)\n+test_expect_success '--rebase overrides --no-ff' '\n+\ttest_does_rebase pull --rebase --no-ff\n+'\n+\n+test_expect_success '--rebase overrides --ff' '\n+\ttest_does_rebase pull --rebase --ff\n+'\n+\n+test_expect_success '--rebase fast-forwards when possible' '\n+\ttest_does_fast_forward pull --rebase --ff\n+'\n+\n+test_expect_success 'pull.rebase=true overrides pull.ff=false' '\n+\ttest_does_rebase -c pull.rebase=true -c pull.ff=false pull\n+'\n+\n+test_expect_success 'pull.rebase=true overrides pull.ff=true' '\n+\ttest_does_rebase -c pull.rebase=true -c pull.ff=true pull\n+'\n+\n+# Group 3: command line flags take precedence over config\n+test_expect_failure '--ff-only takes precedence over pull.rebase=true' '\n+\ttest_attempts_fast_forward -c pull.rebase=true pull --ff-only\n+'\n+\n+test_expect_success '--ff-only takes precedence over pull.rebase=false' '\n+\ttest_attempts_fast_forward -c pull.rebase=false pull --ff-only\n+'\n+\n+test_expect_failure '--no-rebase takes precedence over pull.ff=only' '\n+\ttest_falls_back_to_full_merge -c pull.ff=only pull --no-rebase\n+'\n+\n+test_expect_success '--rebase takes precedence over pull.ff=only' '\n+\ttest_does_rebase -c pull.ff=only pull --rebase\n+'\n+\n+test_expect_success '--rebase overrides pull.ff=true' '\n+\ttest_does_rebase -c pull.ff=true pull --rebase\n+'\n+\n+test_expect_success '--rebase overrides pull.ff=false' '\n+\ttest_does_rebase -c pull.ff=false pull --rebase\n+'\n+\n+test_expect_success '--rebase overrides pull.ff unset' '\n+\ttest_does_rebase pull --rebase\n+'\n+\n+# Group 4: --no-rebase heeds pull.ff=!only or explict --ff or --no-ff\n+\n+test_expect_success '--no-rebase works with --no-ff' '\n+\ttest_does_merge_when_ff_possible pull --no-rebase --no-ff\n+'\n+\n+test_expect_success '--no-rebase works with --ff' '\n+\ttest_does_fast_forward pull --no-rebase --ff\n+'\n+\n+test_expect_success '--no-rebase does ff if pull.ff unset' '\n+\ttest_does_fast_forward pull --no-rebase\n+'\n+\n+test_expect_success '--no-rebase heeds pull.ff=true' '\n+\ttest_does_fast_forward -c pull.ff=true pull --no-rebase\n+'\n+\n+test_expect_success '--no-rebase heeds pull.ff=false' '\n+\ttest_does_merge_when_ff_possible -c pull.ff=false pull --no-rebase\n+'\n+\n+# Group 5: pull.rebase=!false in combination with --no-ff or --ff\n+test_expect_success 'pull.rebase=true and --no-ff' '\n+\ttest_does_rebase -c pull.rebase=true pull --no-ff\n+'\n+\n+test_expect_success 'pull.rebase=true and --ff' '\n+\ttest_does_rebase -c pull.rebase=true pull --ff\n+'\n+\n+test_expect_success 'pull.rebase=false and --no-ff' '\n+\ttest_does_merge_when_ff_possible -c pull.rebase=false pull --no-ff\n+'\n+\n+test_expect_success 'pull.rebase=false and --ff, ff possible' '\n+\ttest_does_fast_forward -c pull.rebase=false pull --ff\n+'\n+\n+test_expect_success 'pull.rebase=false and --ff, ff not possible' '\n+\ttest_falls_back_to_full_merge -c pull.rebase=false pull --ff\n+'\n+\n+# End of groupings for conflicting merge vs. rebase flags/options\n+\n test_expect_success 'merge c1 with c2' '\n \tgit reset --hard c1 &&\n \ttest -f c0.c &&\n-- \ngitgitgadget\n\n"},{"id":"430870","messageId":"66fe7f7f9341c11bc72eaf4cfa7b1d6c658e31a8.1626930290.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.v3.git.git.1626930290.gitgitgadget@gmail.com","subject":"[PATCH v3 2/8] t7601: add tests of interactions with multiple merge heads and config","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-22T05:04:44Z","receivedAt":"2021-07-22T05:05:04Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThere were already code checking that --rebase was incompatible with\na merge of multiple heads.  However, we were sometimes throwing warnings\nabout lack of specification of rebase vs. merge when given multiple\nheads.  Since rebasing is disallowed with multiple merge heads, that\nseems like a poor warning to print; we should instead just assume\nmerging is wanted.\n\nAdd a few tests checking multiple merge head behavior.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n t/t7601-merge-pull-config.sh | 22 ++++++++++++++++++++++\n 1 file changed, 22 insertions(+)\n\ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex a44e6e69f2b..21db1e9e14b 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -325,6 +325,28 @@ test_expect_success 'pull.rebase=false and --ff, ff not possible' '\n \n # End of groupings for conflicting merge vs. rebase flags/options\n \n+test_expect_failure 'Multiple heads warns about inability to fast forward' '\n+\tgit reset --hard c1 &&\n+\ttest_must_fail git pull . c2 c3 2>err &&\n+\ttest_i18ngrep \"Pulling without specifying how to reconcile\" err\n+'\n+\n+test_expect_failure 'Multiple can never be fast forwarded' '\n+\tgit reset --hard c0 &&\n+\ttest_must_fail git -c pull.ff=only pull . c1 c2 c3 2>err &&\n+\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err &&\n+\t# In addition to calling out \"cannot fast-forward\", we very much\n+\t# want the \"multiple branches\" piece to be called out to users.\n+\ttest_i18ngrep \"Cannot fast-forward to multiple branches\" err\n+'\n+\n+test_expect_success 'Cannot rebase with multiple heads' '\n+\tgit reset --hard c0 &&\n+\ttest_must_fail git -c pull.rebase=true pull . c1 c2 c3 2>err &&\n+\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err &&\n+\ttest_i18ngrep \"Cannot rebase onto multiple branches.\" err\n+'\n+\n test_expect_success 'merge c1 with c2' '\n \tgit reset --hard c1 &&\n \ttest -f c0.c &&\n-- \ngitgitgadget\n\n"},{"id":"430871","messageId":"pull.1049.v3.git.git.1626930290.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.v2.git.git.1626831744.gitgitgadget@gmail.com","subject":"[PATCH v3 0/8] Handle pull option precedence","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-22T05:04:42Z","receivedAt":"2021-07-22T05:05:04Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"Based on a recent list of rules for flag/option precedence for git-pull[1]\nfrom Junio (particularly focusing on rebase vs. merge vs. fast-forward),\nhere's an attempt to implement and document it. Given multiple recent\nsurprises from users about some of these behaviors[2][3] and a coworker just\nyesterday expressing some puzzlement with git-pull and rebase vs. merge, it\nseems like a good time to address some of these issues.\n\nSince the handling of conflicting options was holding up two of Alex's\npatches[4][5], and his patches fix some of the tests, I also include those\ntwo patches in my series, with a few small changes to the first (so I've\nkept him as author) and more substantial changes to the second (so I've\ngiven him an Initial-patch-by attribution).\n\nChanges since v2:\n\n * Remove some unnecessary changes in patch 4, pointed out by Junio.\n\nChanges since v1:\n\n * Rebased on latest master (resolved a simple conflict with\n   dd/test-stdout-count-lines)\n * Patch 1: based on feedback from Junio, fixed some style issues, clarified\n   function names, added a few new tests, and took a stab at fixing up the\n   comments and test descriptions (but still unsure if I hit the mark on the\n   last point)\n * Patch 2: changed the test expectations for one of the multiple head tests\n   as per Junio's suggestion, and made one of the other tests expect a more\n   specific error message\n * Patches 4 & 5 were squashed and fixed: these now address a submodule bug\n   interaction with --ff-only\n * Old patch 6 (now 5): added a code comment explaining a subtle point\n * Old patch 8 (now 7): a few more documentation updates, especially making\n   --ff-only not sound merge-specific\n * Old patch 9 (now 8): Updates for new test expectation from patch 2\n\nQuick overview:\n\n * Patches 1-2: new testcases (see the commit messages for the rules)\n * Patch 3: Alex's recent patch (abort if --ff-only but can't do so)\n * Patches 4-6: fix the precedence parts Alex didn't cover\n * Patch 7: Alex's other patch, abort if rebase vs. merge not specified\n * Patch 8: Compatibility of git-pull with merge-options.txt (think\n   rebasing)\n * Patch 9: Fix multiple heads handling too\n\n[1] https://lore.kernel.org/git/xmqqwnpqot4m.fsf@gitster.g/ [2]\nhttps://lore.kernel.org/git/CAL3xRKdOyVWvcLXK7zoXtFPiHBjgL24zi5hhg+3yjowwSUPgmg@mail.gmail.com/\n[3]\nhttps://lore.kernel.org/git/c62933fb-96b2-99f5-7169-372f486f6e39@aixigo.com/\n[4]\nhttps://lore.kernel.org/git/20210711012604.947321-1-alexhenrie24@gmail.com/\n[5]\nhttps://lore.kernel.org/git/20210627000855.530985-1-alexhenrie24@gmail.com/\n\nAlex Henrie (1):\n  pull: abort if --ff-only is given and fast-forwarding is impossible\n\nElijah Newren (7):\n  t7601: test interaction of merge/rebase/fast-forward flags and options\n  t7601: add tests of interactions with multiple merge heads and config\n  pull: since --ff-only overrides, handle it first\n  pull: make --rebase and --no-rebase override pull.ff=only\n  pull: abort by default when fast-forwarding is not possible\n  pull: update docs & code for option compatibility with rebasing\n  pull: fix handling of multiple heads\n\n Documentation/git-merge.txt     |   2 +\n Documentation/git-pull.txt      |  21 +--\n Documentation/merge-options.txt |  40 ++++++\n advice.c                        |   5 +\n advice.h                        |   1 +\n builtin/merge.c                 |   2 +-\n builtin/pull.c                  |  55 +++++--\n t/t4013-diff-various.sh         |   2 +-\n t/t5520-pull.sh                 |  20 +--\n t/t5521-pull-options.sh         |   4 +-\n t/t5524-pull-msg.sh             |   4 +-\n t/t5553-set-upstream.sh         |  14 +-\n t/t5604-clone-reference.sh      |   4 +-\n t/t6402-merge-rename.sh         |  18 +--\n t/t6409-merge-subtree.sh        |   6 +-\n t/t6417-merge-ours-theirs.sh    |  10 +-\n t/t7601-merge-pull-config.sh    | 244 +++++++++++++++++++++++++++++---\n t/t7603-merge-reduce-heads.sh   |   2 +-\n 18 files changed, 371 insertions(+), 83 deletions(-)\n\n\nbase-commit: daab8a564f8bbac55f70f8bf86c070e001a9b006\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1049%2Fnewren%2Fhandle-pull-option-precedence-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1049/newren/handle-pull-option-precedence-v3\nPull-Request: https://github.com/git/git/pull/1049\n\nRange-diff vs v2:\n\n 1:  17560927211 = 1:  17560927211 t7601: test interaction of merge/rebase/fast-forward flags and options\n 2:  66fe7f7f934 = 2:  66fe7f7f934 t7601: add tests of interactions with multiple merge heads and config\n 3:  c45cd239666 = 3:  c45cd239666 pull: abort if --ff-only is given and fast-forwarding is impossible\n 4:  1a821d3b1dd ! 4:  0682b2250f4 pull: since --ff-only overrides, handle it first\n     @@ builtin/pull.c: int cmd_pull(int argc, const char **argv, const char *prefix)\n       \n       \tif (opt_rebase) {\n       \t\tint ret = 0;\n     -@@ builtin/pull.c: int cmd_pull(int argc, const char **argv, const char *prefix)\n     - \t\t    submodule_touches_in_range(the_repository, &upstream, &curr_head))\n     - \t\t\tdie(_(\"cannot rebase with locally recorded submodule modifications\"));\n     - \n     --\t\tif (can_ff) {\n     --\t\t\t/* we can fast-forward this without invoking rebase */\n     --\t\t\topt_ff = \"--ff-only\";\n     --\t\t\tret = run_merge();\n     --\t\t} else {\n     --\t\t\tret = run_rebase(&newbase, &upstream);\n     --\t\t}\n     -+\t\tret = run_rebase(&newbase, &upstream);\n     - \n     - \t\tif (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON ||\n     - \t\t\t     recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND))\n     -\n     - ## t/t5520-pull.sh ##\n     -@@ t/t5520-pull.sh: test_expect_success '--rebase (merge) fast forward' '\n     - \t# The above only validates the result.  Did we actually bypass rebase?\n     - \tgit reflog -1 >reflog.actual &&\n     - \tsed \"s/^[0-9a-f][0-9a-f]*/OBJID/\" reflog.actual >reflog.fuzzy &&\n     --\techo \"OBJID HEAD@{0}: pull --rebase . ff: Fast-forward\" >reflog.expected &&\n     -+\techo \"OBJID HEAD@{0}: pull --rebase . ff (finish): returning to refs/heads/to-rebase\" >reflog.expected &&\n     - \ttest_cmp reflog.expected reflog.fuzzy\n     - '\n     - \n     -@@ t/t5520-pull.sh: test_expect_success '--rebase (am) fast forward' '\n     - \n     - \t# The above only validates the result.  Did we actually bypass rebase?\n     - \tgit reflog -1 >reflog.actual &&\n     --\tsed \"s/^[0-9a-f][0-9a-f]*/OBJID/\" reflog.actual >reflog.fuzzy &&\n     --\techo \"OBJID HEAD@{0}: pull --rebase . ff: Fast-forward\" >reflog.expected &&\n     -+\tsed -e \"s/^[0-9a-f][0-9a-f]*/OBJID/\" -e \"s/[0-9a-f][0-9a-f]*$/OBJID/\" reflog.actual >reflog.fuzzy &&\n     -+\techo \"OBJID HEAD@{0}: rebase finished: refs/heads/to-rebase onto OBJID\" >reflog.expected &&\n     - \ttest_cmp reflog.expected reflog.fuzzy\n     - '\n     - \n 5:  9b116f3d284 = 5:  b242d001132 pull: make --rebase and --no-rebase override pull.ff=only\n 6:  f061f8b4e75 = 6:  7447c719bd7 pull: abort by default when fast-forwarding is not possible\n 7:  90d49e0fb78 = 7:  726082f2e79 pull: update docs & code for option compatibility with rebasing\n 8:  f03b15b7eb0 = 8:  768dec71558 pull: fix handling of multiple heads\n\n-- \ngitgitgadget\n"},{"id":"430872","messageId":"c45cd2396664e915a1d962e6d131505df51b118e.1626930290.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.v3.git.git.1626930290.gitgitgadget@gmail.com","subject":"[PATCH v3 3/8] pull: abort if --ff-only is given and fast-forwarding is impossible","fromName":"Alex Henrie via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-22T05:04:45Z","receivedAt":"2021-07-22T05:05:08Z","isPatch":true,"sender":{"key":"alexhenrie24@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5951993?v=4"},"body":"From: Alex Henrie <alexhenrie24@gmail.com>\n\nThe warning about pulling without specifying how to reconcile divergent\nbranches says that after setting pull.rebase to true, --ff-only can\nstill be passed on the command line to require a fast-forward. Make that\nactually work.\n\nSigned-off-by: Alex Henrie <alexhenrie24@gmail.com>\n[en: updated tests; note 3 fixes and 1 new failure]\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n advice.c                     |  5 +++++\n advice.h                     |  1 +\n builtin/merge.c              |  2 +-\n builtin/pull.c               | 11 ++++++++---\n t/t7601-merge-pull-config.sh | 10 +++++-----\n 5 files changed, 20 insertions(+), 9 deletions(-)\n\ndiff --git a/advice.c b/advice.c\nindex 0b9c89c48ab..337e8f342bc 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -286,6 +286,11 @@ void NORETURN die_conclude_merge(void)\n \tdie(_(\"Exiting because of unfinished merge.\"));\n }\n \n+void NORETURN die_ff_impossible(void)\n+{\n+\tdie(_(\"Not possible to fast-forward, aborting.\"));\n+}\n+\n void advise_on_updating_sparse_paths(struct string_list *pathspec_list)\n {\n \tstruct string_list_item *item;\ndiff --git a/advice.h b/advice.h\nindex bd26c385d00..16240438387 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -95,6 +95,7 @@ void advise_if_enabled(enum advice_type type, const char *advice, ...);\n int error_resolve_conflict(const char *me);\n void NORETURN die_resolve_conflict(const char *me);\n void NORETURN die_conclude_merge(void);\n+void NORETURN die_ff_impossible(void);\n void advise_on_updating_sparse_paths(struct string_list *pathspec_list);\n void detach_advice(const char *new_name);\n \ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex a8a843b1f54..aa920ac524f 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1620,7 +1620,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t}\n \n \tif (fast_forward == FF_ONLY)\n-\t\tdie(_(\"Not possible to fast-forward, aborting.\"));\n+\t\tdie_ff_impossible();\n \n \tif (autostash)\n \t\tcreate_autostash(the_repository,\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 3e13f810843..d9796604825 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -1046,9 +1046,14 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \n \tcan_ff = get_can_ff(&orig_head, &merge_heads.oid[0]);\n \n-\tif (rebase_unspecified && !opt_ff && !can_ff) {\n-\t\tif (opt_verbosity >= 0)\n-\t\t\tshow_advice_pull_non_ff();\n+\tif (!can_ff) {\n+\t\tif (opt_ff) {\n+\t\t\tif (!strcmp(opt_ff, \"--ff-only\"))\n+\t\t\t\tdie_ff_impossible();\n+\t\t} else {\n+\t\t\tif (rebase_unspecified && opt_verbosity >= 0)\n+\t\t\t\tshow_advice_pull_non_ff();\n+\t\t}\n \t}\n \n \tif (opt_rebase) {\ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex 21db1e9e14b..d1f621725ad 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -209,11 +209,11 @@ test_attempts_fast_forward () {\n # Group 1: Interaction of --ff-only with --[no-]rebase\n # (And related interaction of pull.ff=only with pull.rebase)\n #\n-test_expect_failure '--ff-only overrides --rebase' '\n+test_expect_success '--ff-only overrides --rebase' '\n \ttest_attempts_fast_forward pull --rebase --ff-only\n '\n \n-test_expect_failure '--ff-only overrides --rebase even if first' '\n+test_expect_success '--ff-only overrides --rebase even if first' '\n \ttest_attempts_fast_forward pull --ff-only --rebase\n '\n \n@@ -221,7 +221,7 @@ test_expect_success '--ff-only overrides --no-rebase' '\n \ttest_attempts_fast_forward pull --ff-only --no-rebase\n '\n \n-test_expect_failure 'pull.ff=only overrides pull.rebase=true' '\n+test_expect_success 'pull.ff=only overrides pull.rebase=true' '\n \ttest_attempts_fast_forward -c pull.ff=only -c pull.rebase=true pull\n '\n \n@@ -252,7 +252,7 @@ test_expect_success 'pull.rebase=true overrides pull.ff=true' '\n '\n \n # Group 3: command line flags take precedence over config\n-test_expect_failure '--ff-only takes precedence over pull.rebase=true' '\n+test_expect_success '--ff-only takes precedence over pull.rebase=true' '\n \ttest_attempts_fast_forward -c pull.rebase=true pull --ff-only\n '\n \n@@ -264,7 +264,7 @@ test_expect_failure '--no-rebase takes precedence over pull.ff=only' '\n \ttest_falls_back_to_full_merge -c pull.ff=only pull --no-rebase\n '\n \n-test_expect_success '--rebase takes precedence over pull.ff=only' '\n+test_expect_failure '--rebase takes precedence over pull.ff=only' '\n \ttest_does_rebase -c pull.ff=only pull --rebase\n '\n \n-- \ngitgitgadget\n\n"},{"id":"430873","messageId":"b242d001132880533077722342abb8675dcea90f.1626930290.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.v3.git.git.1626930290.gitgitgadget@gmail.com","subject":"[PATCH v3 5/8] pull: make --rebase and --no-rebase override pull.ff=only","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-22T05:04:47Z","receivedAt":"2021-07-22T05:05:13Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nFix the last few precedence tests failing in t7601 by now implementing\nthe logic to have --[no-]rebase override a pull.ff=only config setting.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/pull.c               | 16 +++++++++++++++-\n t/t7601-merge-pull-config.sh |  4 ++--\n 2 files changed, 17 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 1f452020375..9bf03255291 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -966,8 +966,22 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \n \tparse_repo_refspecs(argc, argv, &repo, &refspecs);\n \n-\tif (!opt_ff)\n+\tif (!opt_ff) {\n \t\topt_ff = xstrdup_or_null(config_get_ff());\n+\t\t/*\n+\t\t * A subtle point: opt_ff was set on the line above via\n+\t\t * reading from config.  opt_rebase, in contrast, is set\n+\t\t * before this point via command line options.  The setting\n+\t\t * of opt_rebase via reading from config (using\n+\t\t * config_get_rebase()) does not happen until later.  We\n+\t\t * are relying on the next if-condition happening before\n+\t\t * the config_get_rebase() call so that an explicit\n+\t\t * \"--rebase\" can override a config setting of\n+\t\t * pull.ff=only.\n+\t\t */\n+\t\tif (opt_rebase >= 0 && opt_ff && !strcmp(opt_ff, \"--ff-only\"))\n+\t\t\topt_ff = \"--ff\";\n+\t}\n \n \tif (opt_rebase < 0)\n \t\topt_rebase = config_get_rebase(&rebase_unspecified);\ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex d1f621725ad..0c0dfecd2a3 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -260,11 +260,11 @@ test_expect_success '--ff-only takes precedence over pull.rebase=false' '\n \ttest_attempts_fast_forward -c pull.rebase=false pull --ff-only\n '\n \n-test_expect_failure '--no-rebase takes precedence over pull.ff=only' '\n+test_expect_success '--no-rebase takes precedence over pull.ff=only' '\n \ttest_falls_back_to_full_merge -c pull.ff=only pull --no-rebase\n '\n \n-test_expect_failure '--rebase takes precedence over pull.ff=only' '\n+test_expect_success '--rebase takes precedence over pull.ff=only' '\n \ttest_does_rebase -c pull.ff=only pull --rebase\n '\n \n-- \ngitgitgadget\n\n"},{"id":"430874","messageId":"0682b2250f400e9a3eb1f40f48d32c2cb8c56b0a.1626930290.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.v3.git.git.1626930290.gitgitgadget@gmail.com","subject":"[PATCH v3 4/8] pull: since --ff-only overrides, handle it first","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-22T05:04:46Z","receivedAt":"2021-07-22T05:05:14Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nThere are both merge and rebase branches in the logic, and previously\nboth had to handle fast-forwarding.  Merge handled that implicitly\n(because git merge handles it directly), while in rebase it was\nexplicit.  Given that the --ff-only flag is meant to override any\n--rebase or --no-rebase, make the code reflect that by handling\n--ff-only before the merge-vs-rebase logic.\n\nIt turns out that this also fixes a bug for submodules.  Previously,\nwhen --ff-only was given, the code would run `merge --ff-only` on the\nmain module, and then run `submodule update --recursive --rebase` on the\nsubmodules.  With this change, we still run `merge --ff-only` on the\nmain module, but now run `submodule update --recursive --checkout` on\nthe submodules.  I believe this better reflects the intent of --ff-only\nto have it apply to both the main module and the submodules.\n\n(Sidenote: It is somewhat interesting that all merges pass `--checkout`\nto submodule update, even when `--no-ff` is specified, meaning that it\nwill only do fast-forward merges for submodules.  This was discussed in\ncommit a6d7eb2c7a (\"pull: optionally rebase submodules (remote submodule\nchanges only)\", 2017-06-23).  The same limitations apply now as then, so\nwe are not trying to fix this at this time.)\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/pull.c | 16 ++++++++--------\n 1 file changed, 8 insertions(+), 8 deletions(-)\n\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex d9796604825..1f452020375 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -1046,15 +1046,15 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \n \tcan_ff = get_can_ff(&orig_head, &merge_heads.oid[0]);\n \n-\tif (!can_ff) {\n-\t\tif (opt_ff) {\n-\t\t\tif (!strcmp(opt_ff, \"--ff-only\"))\n-\t\t\t\tdie_ff_impossible();\n-\t\t} else {\n-\t\t\tif (rebase_unspecified && opt_verbosity >= 0)\n-\t\t\t\tshow_advice_pull_non_ff();\n-\t\t}\n+\t/* ff-only takes precedence over rebase */\n+\tif (opt_ff && !strcmp(opt_ff, \"--ff-only\")) {\n+\t\tif (!can_ff)\n+\t\t\tdie_ff_impossible();\n+\t\topt_rebase = REBASE_FALSE;\n \t}\n+\t/* If no action specified and we can't fast forward, then warn. */\n+\tif (!opt_ff && rebase_unspecified && !can_ff)\n+\t\tshow_advice_pull_non_ff();\n \n \tif (opt_rebase) {\n \t\tint ret = 0;\n-- \ngitgitgadget\n\n"},{"id":"430875","messageId":"7447c719bd7448e99d0e6ff4728fe56371b1d6a9.1626930290.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.v3.git.git.1626930290.gitgitgadget@gmail.com","subject":"[PATCH v3 6/8] pull: abort by default when fast-forwarding is not possible","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-22T05:04:48Z","receivedAt":"2021-07-22T05:05:17Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWe have for some time shown a long warning when the user does not\nspecify how to reconcile divergent branches with git pull.  Make it an\nerror now.\n\nInitial-patch-by: Alex Henrie <alexhenrie24@gmail.com>\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-pull.txt    | 20 +++++++++------\n builtin/pull.c                | 10 +++++---\n t/t4013-diff-various.sh       |  2 +-\n t/t5520-pull.sh               | 20 +++++++--------\n t/t5521-pull-options.sh       |  4 +--\n t/t5524-pull-msg.sh           |  4 +--\n t/t5553-set-upstream.sh       | 14 +++++-----\n t/t5604-clone-reference.sh    |  4 +--\n t/t6402-merge-rename.sh       | 18 ++++++-------\n t/t6409-merge-subtree.sh      |  6 ++---\n t/t6417-merge-ours-theirs.sh  | 10 ++++----\n t/t7601-merge-pull-config.sh  | 48 +++++++++++++++++------------------\n t/t7603-merge-reduce-heads.sh |  2 +-\n 13 files changed, 84 insertions(+), 78 deletions(-)\n\ndiff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\nindex 5c3fb67c014..cad3f6bfcee 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -15,14 +15,18 @@ SYNOPSIS\n DESCRIPTION\n -----------\n \n-Incorporates changes from a remote repository into the current\n-branch.  In its default mode, `git pull` is shorthand for\n-`git fetch` followed by `git merge FETCH_HEAD`.\n-\n-More precisely, 'git pull' runs 'git fetch' with the given\n-parameters and calls 'git merge' to merge the retrieved branch\n-heads into the current branch.\n-With `--rebase`, it runs 'git rebase' instead of 'git merge'.\n+Incorporates changes from a remote repository into the current branch.\n+If the current branch is behind the remote, then by default it will\n+fast-forward the current branch to match the remote.  If the current\n+branch and the remote have diverged, the user needs to specify how to\n+reconcile the divergent branches with `--no-ff`, `--ff`, or `--rebase`\n+(or the corresponding configuration options in `pull.ff` or\n+`pull.rebase`).\n+\n+More precisely, `git pull` runs `git fetch` with the given parameters\n+and then depending on configuration options or command line flags,\n+will call either `git merge` or `git rebase` to reconcile diverging\n+branches.\n \n <repository> should be the name of a remote repository as\n passed to linkgit:git-fetch[1].  <refspec> can name an\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 9bf03255291..4514a1478ea 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -927,9 +927,9 @@ static int get_can_ff(struct object_id *orig_head, struct object_id *orig_merge_\n \n static void show_advice_pull_non_ff(void)\n {\n-\tadvise(_(\"Pulling without specifying how to reconcile divergent branches is\\n\"\n-\t\t \"discouraged. You can squelch this message by running one of the following\\n\"\n-\t\t \"commands sometime before your next pull:\\n\"\n+\tadvise(_(\"You have divergent branches and need to specify how to reconcile them.\\n\"\n+\t\t \"You can do so by running one of the following commands sometime before\\n\"\n+\t\t \"your next pull:\\n\"\n \t\t \"\\n\"\n \t\t \"  git config pull.rebase false  # merge (the default strategy)\\n\"\n \t\t \"  git config pull.rebase true   # rebase\\n\"\n@@ -1067,8 +1067,10 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \t\topt_rebase = REBASE_FALSE;\n \t}\n \t/* If no action specified and we can't fast forward, then warn. */\n-\tif (!opt_ff && rebase_unspecified && !can_ff)\n+\tif (!opt_ff && rebase_unspecified && !can_ff) {\n \t\tshow_advice_pull_non_ff();\n+\t\tdie(_(\"Need to specify how to reconcile divergent branches.\"));\n+\t}\n \n \tif (opt_rebase) {\n \t\tint ret = 0;\ndiff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh\nindex 7fadc985ccc..eb989f7f191 100755\n--- a/t/t4013-diff-various.sh\n+++ b/t/t4013-diff-various.sh\n@@ -65,7 +65,7 @@ test_expect_success setup '\n \texport GIT_AUTHOR_DATE GIT_COMMITTER_DATE &&\n \n \tgit checkout master &&\n-\tgit pull -s ours . side &&\n+\tgit pull -s ours --no-rebase . side &&\n \n \tGIT_AUTHOR_DATE=\"2006-06-26 00:05:00 +0000\" &&\n \tGIT_COMMITTER_DATE=\"2006-06-26 00:05:00 +0000\" &&\ndiff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\nindex e2c0c510222..672001a18bd 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -136,12 +136,12 @@ test_expect_success 'the default remote . should not break explicit pull' '\n \tgit reset --hard HEAD^ &&\n \techo file >expect &&\n \ttest_cmp expect file &&\n-\tgit pull . second &&\n+\tgit pull --no-rebase . second &&\n \techo modified >expect &&\n \ttest_cmp expect file &&\n \tgit reflog -1 >reflog.actual &&\n \tsed \"s/^[0-9a-f][0-9a-f]*/OBJID/\" reflog.actual >reflog.fuzzy &&\n-\techo \"OBJID HEAD@{0}: pull . second: Fast-forward\" >reflog.expected &&\n+\techo \"OBJID HEAD@{0}: pull --no-rebase . second: Fast-forward\" >reflog.expected &&\n \ttest_cmp reflog.expected reflog.fuzzy\n '\n \n@@ -226,7 +226,7 @@ test_expect_success 'fail if the index has unresolved entries' '\n \ttest_commit modified2 file &&\n \tgit ls-files -u >unmerged &&\n \ttest_must_be_empty unmerged &&\n-\ttest_must_fail git pull . second &&\n+\ttest_must_fail git pull --no-rebase . second &&\n \tgit ls-files -u >unmerged &&\n \ttest_file_not_empty unmerged &&\n \tcp file expected &&\n@@ -409,37 +409,37 @@ test_expect_success 'pull --rebase --no-autostash & rebase.autostash unset' '\n \n test_expect_success 'pull succeeds with dirty working directory and merge.autostash set' '\n \ttest_config merge.autostash true &&\n-\ttest_pull_autostash 2\n+\ttest_pull_autostash 2 --no-rebase\n '\n \n test_expect_success 'pull --autostash & merge.autostash=true' '\n \ttest_config merge.autostash true &&\n-\ttest_pull_autostash 2 --autostash\n+\ttest_pull_autostash 2 --autostash --no-rebase\n '\n \n test_expect_success 'pull --autostash & merge.autostash=false' '\n \ttest_config merge.autostash false &&\n-\ttest_pull_autostash 2 --autostash\n+\ttest_pull_autostash 2 --autostash --no-rebase\n '\n \n test_expect_success 'pull --autostash & merge.autostash unset' '\n \ttest_unconfig merge.autostash &&\n-\ttest_pull_autostash 2 --autostash\n+\ttest_pull_autostash 2 --autostash --no-rebase\n '\n \n test_expect_success 'pull --no-autostash & merge.autostash=true' '\n \ttest_config merge.autostash true &&\n-\ttest_pull_autostash_fail --no-autostash\n+\ttest_pull_autostash_fail --no-autostash --no-rebase\n '\n \n test_expect_success 'pull --no-autostash & merge.autostash=false' '\n \ttest_config merge.autostash false &&\n-\ttest_pull_autostash_fail --no-autostash\n+\ttest_pull_autostash_fail --no-autostash --no-rebase\n '\n \n test_expect_success 'pull --no-autostash & merge.autostash unset' '\n \ttest_unconfig merge.autostash &&\n-\ttest_pull_autostash_fail --no-autostash\n+\ttest_pull_autostash_fail --no-autostash --no-rebase\n '\n \n test_expect_success 'pull.rebase' '\ndiff --git a/t/t5521-pull-options.sh b/t/t5521-pull-options.sh\nindex 63a688bdbf5..7601c919fdc 100755\n--- a/t/t5521-pull-options.sh\n+++ b/t/t5521-pull-options.sh\n@@ -113,7 +113,7 @@ test_expect_success 'git pull --force' '\n \tgit pull two &&\n \ttest_commit A &&\n \tgit branch -f origin &&\n-\tgit pull --all --force\n+\tgit pull --no-rebase --all --force\n \t)\n '\n \n@@ -179,7 +179,7 @@ test_expect_success 'git pull --allow-unrelated-histories' '\n \t(\n \t\tcd dst &&\n \t\ttest_must_fail git pull ../src side &&\n-\t\tgit pull --allow-unrelated-histories ../src side\n+\t\tgit pull --no-rebase --allow-unrelated-histories ../src side\n \t)\n '\n \ndiff --git a/t/t5524-pull-msg.sh b/t/t5524-pull-msg.sh\nindex c278adaa5a2..b2be3605f5a 100755\n--- a/t/t5524-pull-msg.sh\n+++ b/t/t5524-pull-msg.sh\n@@ -28,7 +28,7 @@ test_expect_success setup '\n test_expect_success pull '\n (\n \tcd cloned &&\n-\tgit pull --log &&\n+\tgit pull --no-rebase --log &&\n \tgit log -2 &&\n \tgit cat-file commit HEAD >result &&\n \tgrep Dollar result\n@@ -41,7 +41,7 @@ test_expect_success '--log=1 limits shortlog length' '\n \tgit reset --hard HEAD^ &&\n \ttest \"$(cat afile)\" = original &&\n \ttest \"$(cat bfile)\" = added &&\n-\tgit pull --log=1 &&\n+\tgit pull --no-rebase --log=1 &&\n \tgit log -3 &&\n \tgit cat-file commit HEAD >result &&\n \tgrep Dollar result &&\ndiff --git a/t/t5553-set-upstream.sh b/t/t5553-set-upstream.sh\nindex b1d614ce18c..9c12c0f8c32 100755\n--- a/t/t5553-set-upstream.sh\n+++ b/t/t5553-set-upstream.sh\n@@ -108,27 +108,27 @@ test_expect_success 'setup commit on main and other pull' '\n \n test_expect_success 'pull --set-upstream upstream main sets branch main but not other' '\n \tclear_config main other &&\n-\tgit pull --set-upstream upstream main &&\n+\tgit pull --no-rebase --set-upstream upstream main &&\n \tcheck_config main upstream refs/heads/main &&\n \tcheck_config_missing other\n '\n \n test_expect_success 'pull --set-upstream main:other2 does not set the branch other2' '\n \tclear_config other2 &&\n-\tgit pull --set-upstream upstream main:other2 &&\n+\tgit pull --no-rebase --set-upstream upstream main:other2 &&\n \tcheck_config_missing other2\n '\n \n test_expect_success 'pull --set-upstream upstream other sets branch main' '\n \tclear_config main other &&\n-\tgit pull --set-upstream upstream other &&\n+\tgit pull --no-rebase --set-upstream upstream other &&\n \tcheck_config main upstream refs/heads/other &&\n \tcheck_config_missing other\n '\n \n test_expect_success 'pull --set-upstream upstream tag does not set the tag' '\n \tclear_config three &&\n-\tgit pull --tags --set-upstream upstream three &&\n+\tgit pull --no-rebase --tags --set-upstream upstream three &&\n \tcheck_config_missing three\n '\n \n@@ -144,16 +144,16 @@ test_expect_success 'pull --set-upstream http://nosuchdomain.example.com fails w\n \n test_expect_success 'pull --set-upstream upstream HEAD sets branch HEAD' '\n \tclear_config main other &&\n-\tgit pull --set-upstream upstream HEAD &&\n+\tgit pull --no-rebase --set-upstream upstream HEAD &&\n \tcheck_config main upstream HEAD &&\n \tgit checkout other &&\n-\tgit pull --set-upstream upstream HEAD &&\n+\tgit pull --no-rebase --set-upstream upstream HEAD &&\n \tcheck_config other upstream HEAD\n '\n \n test_expect_success 'pull --set-upstream upstream with more than one branch does nothing' '\n \tclear_config main three &&\n-\tgit pull --set-upstream upstream main three &&\n+\tgit pull --no-rebase --set-upstream upstream main three &&\n \tcheck_config_missing main &&\n \tcheck_config_missing three\n '\ndiff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh\nindex e845d621f61..24340e6d56e 100755\n--- a/t/t5604-clone-reference.sh\n+++ b/t/t5604-clone-reference.sh\n@@ -87,7 +87,7 @@ test_expect_success 'updating origin' '\n '\n \n test_expect_success 'pulling changes from origin' '\n-\tgit -C C pull origin\n+\tgit -C C pull --no-rebase origin\n '\n \n # the 2 local objects are commit and tree from the merge\n@@ -96,7 +96,7 @@ test_expect_success 'that alternate to origin gets used' '\n '\n \n test_expect_success 'pulling changes from origin' '\n-\tgit -C D pull origin\n+\tgit -C D pull --no-rebase origin\n '\n \n # the 5 local objects are expected; file3 blob, commit in A to add it\ndiff --git a/t/t6402-merge-rename.sh b/t/t6402-merge-rename.sh\nindex 3da2896e3bc..3a32b1a45cf 100755\n--- a/t/t6402-merge-rename.sh\n+++ b/t/t6402-merge-rename.sh\n@@ -103,7 +103,7 @@ test_expect_success 'setup' '\n test_expect_success 'pull renaming branch into unrenaming one' \\\n '\n \tgit show-branch &&\n-\ttest_expect_code 1 git pull . white &&\n+\ttest_expect_code 1 git pull --no-rebase . white &&\n \tgit ls-files -s &&\n \ttest_stdout_line_count = 3 git ls-files -u B &&\n \ttest_stdout_line_count = 1 git ls-files -s N &&\n@@ -119,7 +119,7 @@ test_expect_success 'pull renaming branch into another renaming one' \\\n \trm -f B &&\n \tgit reset --hard &&\n \tgit checkout red &&\n-\ttest_expect_code 1 git pull . white &&\n+\ttest_expect_code 1 git pull --no-rebase . white &&\n \ttest_stdout_line_count = 3 git ls-files -u B &&\n \ttest_stdout_line_count = 1 git ls-files -s N &&\n \tsed -ne \"/^g/{\n@@ -133,7 +133,7 @@ test_expect_success 'pull unrenaming branch into renaming one' \\\n '\n \tgit reset --hard &&\n \tgit show-branch &&\n-\ttest_expect_code 1 git pull . main &&\n+\ttest_expect_code 1 git pull --no-rebase . main &&\n \ttest_stdout_line_count = 3 git ls-files -u B &&\n \ttest_stdout_line_count = 1 git ls-files -s N &&\n \tsed -ne \"/^g/{\n@@ -147,7 +147,7 @@ test_expect_success 'pull conflicting renames' \\\n '\n \tgit reset --hard &&\n \tgit show-branch &&\n-\ttest_expect_code 1 git pull . blue &&\n+\ttest_expect_code 1 git pull --no-rebase . blue &&\n \ttest_stdout_line_count = 1 git ls-files -u A &&\n \ttest_stdout_line_count = 1 git ls-files -u B &&\n \ttest_stdout_line_count = 1 git ls-files -u C &&\n@@ -163,7 +163,7 @@ test_expect_success 'interference with untracked working tree file' '\n \tgit reset --hard &&\n \tgit show-branch &&\n \techo >A this file should not matter &&\n-\ttest_expect_code 1 git pull . white &&\n+\ttest_expect_code 1 git pull --no-rebase . white &&\n \ttest_path_is_file A\n '\n \n@@ -173,7 +173,7 @@ test_expect_success 'interference with untracked working tree file' '\n \tgit show-branch &&\n \trm -f A &&\n \techo >A this file should not matter &&\n-\ttest_expect_code 1 git pull . red &&\n+\ttest_expect_code 1 git pull --no-rebase . red &&\n \ttest_path_is_file A\n '\n \n@@ -183,7 +183,7 @@ test_expect_success 'interference with untracked working tree file' '\n \tgit checkout -f main &&\n \tgit tag -f anchor &&\n \tgit show-branch &&\n-\tgit pull . yellow &&\n+\tgit pull --no-rebase . yellow &&\n \ttest_path_is_missing M &&\n \tgit reset --hard anchor\n '\n@@ -210,7 +210,7 @@ test_expect_success 'updated working tree file should prevent the merge' '\n \techo >>M one line addition &&\n \tcat M >M.saved &&\n \tgit update-index M &&\n-\ttest_expect_code 128 git pull . yellow &&\n+\ttest_expect_code 128 git pull --no-rebase . yellow &&\n \ttest_cmp M M.saved &&\n \trm -f M.saved\n '\n@@ -222,7 +222,7 @@ test_expect_success 'interference with untracked working tree file' '\n \tgit tag -f anchor &&\n \tgit show-branch &&\n \techo >M this file should not matter &&\n-\tgit pull . main &&\n+\tgit pull --no-rebase . main &&\n \ttest_path_is_file M &&\n \t! {\n \t\tgit ls-files -s |\ndiff --git a/t/t6409-merge-subtree.sh b/t/t6409-merge-subtree.sh\nindex d406b2343cb..ba7890ec521 100755\n--- a/t/t6409-merge-subtree.sh\n+++ b/t/t6409-merge-subtree.sh\n@@ -100,7 +100,7 @@ test_expect_success 'merge update' '\n \tgit checkout -b topic_2 &&\n \tgit commit -m \"update git-gui\" &&\n \tcd ../git &&\n-\tgit pull -s subtree gui topic_2 &&\n+\tgit pull --no-rebase -s subtree gui topic_2 &&\n \tgit ls-files -s >actual &&\n \t(\n \t\techo \"100644 $o3 0\tgit-gui/git-gui.sh\" &&\n@@ -129,7 +129,7 @@ test_expect_success 'initial ambiguous subtree' '\n test_expect_success 'merge using explicit' '\n \tcd ../git &&\n \tgit reset --hard topic_2 &&\n-\tgit pull -Xsubtree=git-gui gui topic_2 &&\n+\tgit pull --no-rebase -Xsubtree=git-gui gui topic_2 &&\n \tgit ls-files -s >actual &&\n \t(\n \t\techo \"100644 $o3 0\tgit-gui/git-gui.sh\" &&\n@@ -142,7 +142,7 @@ test_expect_success 'merge using explicit' '\n test_expect_success 'merge2 using explicit' '\n \tcd ../git &&\n \tgit reset --hard topic_2 &&\n-\tgit pull -Xsubtree=git-gui2 gui topic_2 &&\n+\tgit pull --no-rebase -Xsubtree=git-gui2 gui topic_2 &&\n \tgit ls-files -s >actual &&\n \t(\n \t\techo \"100644 $o1 0\tgit-gui/git-gui.sh\" &&\ndiff --git a/t/t6417-merge-ours-theirs.sh b/t/t6417-merge-ours-theirs.sh\nindex ac9aee9a662..ec065d6a658 100755\n--- a/t/t6417-merge-ours-theirs.sh\n+++ b/t/t6417-merge-ours-theirs.sh\n@@ -69,11 +69,11 @@ test_expect_success 'binary file with -Xours/-Xtheirs' '\n '\n \n test_expect_success 'pull passes -X to underlying merge' '\n-\tgit reset --hard main && git pull -s recursive -Xours . side &&\n-\tgit reset --hard main && git pull -s recursive -X ours . side &&\n-\tgit reset --hard main && git pull -s recursive -Xtheirs . side &&\n-\tgit reset --hard main && git pull -s recursive -X theirs . side &&\n-\tgit reset --hard main && test_must_fail git pull -s recursive -X bork . side\n+\tgit reset --hard main && git pull --no-rebase -s recursive -Xours . side &&\n+\tgit reset --hard main && git pull --no-rebase -s recursive -X ours . side &&\n+\tgit reset --hard main && git pull --no-rebase -s recursive -Xtheirs . side &&\n+\tgit reset --hard main && git pull --no-rebase -s recursive -X theirs . side &&\n+\tgit reset --hard main && test_must_fail git pull --no-rebase -s recursive -X bork . side\n '\n \n test_expect_success SYMLINKS 'symlink with -Xours/-Xtheirs' '\ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex 0c0dfecd2a3..742ed3981c7 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -27,120 +27,120 @@ test_expect_success 'setup' '\n \tgit tag c3\n '\n \n-test_expect_success 'pull.rebase not set' '\n+test_expect_success 'pull.rebase not set, ff possible' '\n \tgit reset --hard c0 &&\n \tgit pull . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and pull.ff=true' '\n \tgit reset --hard c0 &&\n \ttest_config pull.ff true &&\n \tgit pull . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and pull.ff=false' '\n \tgit reset --hard c0 &&\n \ttest_config pull.ff false &&\n \tgit pull . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and pull.ff=only' '\n \tgit reset --hard c0 &&\n \ttest_config pull.ff only &&\n \tgit pull . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --rebase given' '\n \tgit reset --hard c0 &&\n \tgit pull --rebase . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --no-rebase given' '\n \tgit reset --hard c0 &&\n \tgit pull --no-rebase . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --ff given' '\n \tgit reset --hard c0 &&\n \tgit pull --ff . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --no-ff given' '\n \tgit reset --hard c0 &&\n \tgit pull --no-ff . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --ff-only given' '\n \tgit reset --hard c0 &&\n \tgit pull --ff-only . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set (not-fast-forward)' '\n \tgit reset --hard c2 &&\n-\tgit -c color.advice=always pull . c1 2>err &&\n+\ttest_must_fail git -c color.advice=always pull . c1 2>err &&\n \ttest_decode_color <err >decoded &&\n \ttest_i18ngrep \"<YELLOW>hint: \" decoded &&\n-\ttest_i18ngrep \"Pulling without specifying how to reconcile\" decoded\n+\ttest_i18ngrep \"You have divergent branches\" decoded\n '\n \n test_expect_success 'pull.rebase not set and pull.ff=true (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \ttest_config pull.ff true &&\n \tgit pull . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and pull.ff=false (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \ttest_config pull.ff false &&\n \tgit pull . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and pull.ff=only (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \ttest_config pull.ff only &&\n \ttest_must_fail git pull . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --rebase given (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \tgit pull --rebase . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --no-rebase given (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \tgit pull --no-rebase . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --ff given (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \tgit pull --ff . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --no-ff given (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \tgit pull --no-ff . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_expect_success 'pull.rebase not set and --ff-only given (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \ttest_must_fail git pull --ff-only . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"You have divergent branches\" err\n '\n \n test_does_rebase () {\n@@ -325,16 +325,16 @@ test_expect_success 'pull.rebase=false and --ff, ff not possible' '\n \n # End of groupings for conflicting merge vs. rebase flags/options\n \n-test_expect_failure 'Multiple heads warns about inability to fast forward' '\n+test_expect_success 'Multiple heads warns about inability to fast forward' '\n \tgit reset --hard c1 &&\n \ttest_must_fail git pull . c2 c3 2>err &&\n-\ttest_i18ngrep \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep \"You have divergent branches\" err\n '\n \n test_expect_failure 'Multiple can never be fast forwarded' '\n \tgit reset --hard c0 &&\n \ttest_must_fail git -c pull.ff=only pull . c1 c2 c3 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err &&\n+\ttest_i18ngrep ! \"You have divergent branches\" err &&\n \t# In addition to calling out \"cannot fast-forward\", we very much\n \t# want the \"multiple branches\" piece to be called out to users.\n \ttest_i18ngrep \"Cannot fast-forward to multiple branches\" err\n@@ -343,7 +343,7 @@ test_expect_failure 'Multiple can never be fast forwarded' '\n test_expect_success 'Cannot rebase with multiple heads' '\n \tgit reset --hard c0 &&\n \ttest_must_fail git -c pull.rebase=true pull . c1 c2 c3 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err &&\n+\ttest_i18ngrep ! \"You have divergent branches\" err &&\n \ttest_i18ngrep \"Cannot rebase onto multiple branches.\" err\n '\n \ndiff --git a/t/t7603-merge-reduce-heads.sh b/t/t7603-merge-reduce-heads.sh\nindex 98948955ae5..27cd94ad6f7 100755\n--- a/t/t7603-merge-reduce-heads.sh\n+++ b/t/t7603-merge-reduce-heads.sh\n@@ -68,7 +68,7 @@ test_expect_success 'merge c1 with c2, c3, c4, c5' '\n \n test_expect_success 'pull c2, c3, c4, c5 into c1' '\n \tgit reset --hard c1 &&\n-\tgit pull . c2 c3 c4 c5 &&\n+\tgit pull --no-rebase . c2 c3 c4 c5 &&\n \ttest \"$(git rev-parse c1)\" != \"$(git rev-parse HEAD)\" &&\n \ttest \"$(git rev-parse c1)\" = \"$(git rev-parse HEAD^1)\" &&\n \ttest \"$(git rev-parse c2)\" = \"$(git rev-parse HEAD^2)\" &&\n-- \ngitgitgadget\n\n"},{"id":"430876","messageId":"768dec715582640ca6a48b3e158d1a61e422d0cc.1626930290.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.v3.git.git.1626930290.gitgitgadget@gmail.com","subject":"[PATCH v3 8/8] pull: fix handling of multiple heads","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-22T05:04:50Z","receivedAt":"2021-07-22T05:05:18Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\nWith multiple heads, we should not allow rebasing or fast-forwarding.\nMake sure any fast-forward request calls out specifically the fact that\nmultiple branches are in play.  Also, since we cannot fast-forward to\nmultiple branches, fix our computation of can_ff.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n builtin/pull.c               | 18 ++++++++++++++----\n t/t7601-merge-pull-config.sh |  2 +-\n 2 files changed, 15 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 2f1d1f4037d..b311ea6b9df 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -913,12 +913,18 @@ static int run_rebase(const struct object_id *newbase,\n \treturn ret;\n }\n \n-static int get_can_ff(struct object_id *orig_head, struct object_id *orig_merge_head)\n+static int get_can_ff(struct object_id *orig_head,\n+\t\t      struct oid_array *merge_heads)\n {\n \tint ret;\n \tstruct commit_list *list = NULL;\n \tstruct commit *merge_head, *head;\n+\tstruct object_id *orig_merge_head;\n \n+\tif (merge_heads->nr > 1)\n+\t\treturn 0;\n+\n+\torig_merge_head = &merge_heads->oid[0];\n \thead = lookup_commit_reference(the_repository, orig_head);\n \tcommit_list_insert(head, &list);\n \tmerge_head = lookup_commit_reference(the_repository, orig_merge_head);\n@@ -1057,10 +1063,14 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \t\t\tdie(_(\"Cannot merge multiple branches into empty head.\"));\n \t\treturn pull_into_void(merge_heads.oid, &curr_head);\n \t}\n-\tif (opt_rebase && merge_heads.nr > 1)\n-\t\tdie(_(\"Cannot rebase onto multiple branches.\"));\n+\tif (merge_heads.nr > 1) {\n+\t\tif (opt_rebase)\n+\t\t\tdie(_(\"Cannot rebase onto multiple branches.\"));\n+\t\tif (opt_ff && !strcmp(opt_ff, \"--ff-only\"))\n+\t\t\tdie(_(\"Cannot fast-forward to multiple branches.\"));\n+\t}\n \n-\tcan_ff = get_can_ff(&orig_head, &merge_heads.oid[0]);\n+\tcan_ff = get_can_ff(&orig_head, &merge_heads);\n \n \t/* ff-only takes precedence over rebase */\n \tif (opt_ff && !strcmp(opt_ff, \"--ff-only\")) {\ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex 742ed3981c7..1f652f433ee 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -331,7 +331,7 @@ test_expect_success 'Multiple heads warns about inability to fast forward' '\n \ttest_i18ngrep \"You have divergent branches\" err\n '\n \n-test_expect_failure 'Multiple can never be fast forwarded' '\n+test_expect_success 'Multiple can never be fast forwarded' '\n \tgit reset --hard c0 &&\n \ttest_must_fail git -c pull.ff=only pull . c1 c2 c3 2>err &&\n \ttest_i18ngrep ! \"You have divergent branches\" err &&\n-- \ngitgitgadget\n"},{"id":"430877","messageId":"726082f2e79f04dd0b2935873fafabe259baac71.1626930290.git.gitgitgadget@gmail.com","threadId":"56121","inReplyTo":"pull.1049.v3.git.git.1626930290.gitgitgadget@gmail.com","subject":"[PATCH v3 7/8] pull: update docs & code for option compatibility with rebasing","fromName":"Elijah Newren via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2021-07-22T05:04:49Z","receivedAt":"2021-07-22T05:05:19Z","isPatch":true,"sender":{"key":"newren@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5455730?v=4"},"body":"From: Elijah Newren <newren@gmail.com>\n\ngit-pull.txt includes merge-options.txt, which is written assuming\nmerges will happen.  git-pull has allowed rebases for many years; update\nthe documentation to reflect that.\n\nWhile at it, pass any `--signoff` flag through to the rebase backend too\nso that we don't have to document it as merge-specific.  Rebase has\nsupported the --signoff flag for years now as well.\n\nSigned-off-by: Elijah Newren <newren@gmail.com>\n---\n Documentation/git-merge.txt     |  2 ++\n Documentation/git-pull.txt      |  9 ++++----\n Documentation/merge-options.txt | 40 +++++++++++++++++++++++++++++++++\n builtin/pull.c                  |  2 ++\n 4 files changed, 48 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt\nindex 3819fadac1f..e4f3352eb58 100644\n--- a/Documentation/git-merge.txt\n+++ b/Documentation/git-merge.txt\n@@ -61,6 +61,8 @@ merge has resulted in conflicts.\n \n OPTIONS\n -------\n+:git-merge: 1\n+\n include::merge-options.txt[]\n \n -m <msg>::\ndiff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\nindex cad3f6bfcee..077961dfa1f 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -19,13 +19,12 @@ Incorporates changes from a remote repository into the current branch.\n If the current branch is behind the remote, then by default it will\n fast-forward the current branch to match the remote.  If the current\n branch and the remote have diverged, the user needs to specify how to\n-reconcile the divergent branches with `--no-ff`, `--ff`, or `--rebase`\n-(or the corresponding configuration options in `pull.ff` or\n-`pull.rebase`).\n+reconcile the divergent branches with `--rebase` or `--no-rebase` (or\n+the corresponding configuration option in `pull.rebase`).\n \n More precisely, `git pull` runs `git fetch` with the given parameters\n and then depending on configuration options or command line flags,\n-will call either `git merge` or `git rebase` to reconcile diverging\n+will call either `git rebase` or `git merge` to reconcile diverging\n branches.\n \n <repository> should be the name of a remote repository as\n@@ -136,7 +135,7 @@ published that history already.  Do *not* use this option\n unless you have read linkgit:git-rebase[1] carefully.\n \n --no-rebase::\n-\tOverride earlier --rebase.\n+\tThis is shorthand for --rebase=false.\n \n Options related to fetching\n ~~~~~~~~~~~~~~~~~~~~~~~~~~~\ndiff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt\nindex eb0aabd396f..907327bf759 100644\n--- a/Documentation/merge-options.txt\n+++ b/Documentation/merge-options.txt\n@@ -2,6 +2,9 @@\n --no-commit::\n \tPerform the merge and commit the result. This option can\n \tbe used to override --no-commit.\n+ifdef::git-pull[]\n+\tOnly useful when merging.\n+endif::git-pull[]\n +\n With --no-commit perform the merge and stop just before creating\n a merge commit, to give the user a chance to inspect and further\n@@ -39,6 +42,7 @@ set to `no` at the beginning of them.\n \tto `MERGE_MSG` before being passed on to the commit machinery in the\n \tcase of a merge conflict.\n \n+ifdef::git-merge[]\n --ff::\n --no-ff::\n --ff-only::\n@@ -47,6 +51,22 @@ set to `no` at the beginning of them.\n \tdefault unless merging an annotated (and possibly signed) tag\n \tthat is not stored in its natural place in the `refs/tags/`\n \thierarchy, in which case `--no-ff` is assumed.\n+endif::git-merge[]\n+ifdef::git-pull[]\n+--ff-only::\n+\tOnly update to the new history if there is no divergent local\n+\thistory.  This is the default when no method for reconciling\n+\tdivergent histories is provided (via the --rebase=* flags).\n+\n+--ff::\n+--no-ff::\n+\tWhen merging rather than rebasing, specifies how a merge is\n+\thandled when the merged-in history is already a descendant of\n+\tthe current history.  If merging is requested, `--ff` is the\n+\tdefault unless merging an annotated (and possibly signed) tag\n+\tthat is not stored in its natural place in the `refs/tags/`\n+\thierarchy, in which case `--no-ff` is assumed.\n+endif::git-pull[]\n +\n With `--ff`, when possible resolve the merge as a fast-forward (only\n update the branch pointer to match the merged branch; do not create a\n@@ -55,9 +75,11 @@ descendant of the current history), create a merge commit.\n +\n With `--no-ff`, create a merge commit in all cases, even when the merge\n could instead be resolved as a fast-forward.\n+ifdef::git-merge[]\n +\n With `--ff-only`, resolve the merge as a fast-forward when possible.\n When not possible, refuse to merge and exit with a non-zero status.\n+endif::git-merge[]\n \n -S[<keyid>]::\n --gpg-sign[=<keyid>]::\n@@ -73,6 +95,9 @@ When not possible, refuse to merge and exit with a non-zero status.\n \tIn addition to branch names, populate the log message with\n \tone-line descriptions from at most <n> actual commits that are being\n \tmerged. See also linkgit:git-fmt-merge-msg[1].\n+ifdef::git-pull[]\n+\tOnly useful when merging.\n+endif::git-pull[]\n +\n With --no-log do not list one-line descriptions from the\n actual commits being merged.\n@@ -102,10 +127,17 @@ With --no-squash perform the merge and commit the result. This\n option can be used to override --squash.\n +\n With --squash, --commit is not allowed, and will fail.\n+ifdef::git-pull[]\n++\n+Only useful when merging.\n+endif::git-pull[]\n \n --no-verify::\n \tThis option bypasses the pre-merge and commit-msg hooks.\n \tSee also linkgit:githooks[5].\n+ifdef::git-pull[]\n+\tOnly useful when merging.\n+endif::git-pull[]\n \n -s <strategy>::\n --strategy=<strategy>::\n@@ -127,6 +159,10 @@ With --squash, --commit is not allowed, and will fail.\n \tdefault trust model, this means the signing key has been signed by\n \ta trusted key.  If the tip commit of the side branch is not signed\n \twith a valid key, the merge is aborted.\n+ifdef::git-pull[]\n++\n+Only useful when merging.\n+endif::git-pull[]\n \n --summary::\n --no-summary::\n@@ -166,3 +202,7 @@ endif::git-pull[]\n \tprojects that started their lives independently. As that is\n \ta very rare occasion, no configuration variable to enable\n \tthis by default exists and will not be added.\n+ifdef::git-pull[]\n++\n+Only useful when merging.\n+endif::git-pull[]\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 4514a1478ea..2f1d1f4037d 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -893,6 +893,8 @@ static int run_rebase(const struct object_id *newbase,\n \tstrvec_pushv(&args, opt_strategy_opts.v);\n \tif (opt_gpg_sign)\n \t\tstrvec_push(&args, opt_gpg_sign);\n+\tif (opt_signoff)\n+\t\tstrvec_push(&args, opt_signoff);\n \tif (opt_autostash == 0)\n \t\tstrvec_push(&args, \"--no-autostash\");\n \telse if (opt_autostash == 1)\n-- \ngitgitgadget\n\n"},{"id":"430879","messageId":"60f919a6939ec_31ad20835@natae.notmuch","threadId":"56121","inReplyTo":"pull.1049.v3.git.git.1626930290.gitgitgadget@gmail.com","subject":"RE: [PATCH v3 0/8] Handle pull option precedence","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-22T07:09:26Z","receivedAt":"2021-07-22T07:09:32Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Elijah Newren via GitGitGadget wrote:\n> Based on a recent list of rules for flag/option precedence for git-pull[1]\n> from Junio (particularly focusing on rebase vs. merge vs. fast-forward),\n> here's an attempt to implement and document it. Given multiple recent\n> surprises from users about some of these behaviors[2][3] and a coworker just\n> yesterday expressing some puzzlement with git-pull and rebase vs. merge, it\n> seems like a good time to address some of these issues.\n> \n> Since the handling of conflicting options was holding up two of Alex's\n> patches[4][5], and his patches fix some of the tests, I also include those\n> two patches in my series, with a few small changes to the first (so I've\n> kept him as author) and more substantial changes to the second (so I've\n> given him an Initial-patch-by attribution).\n> \n> Changes since v2:\n> \n>  * Remove some unnecessary changes in patch 4, pointed out by Junio.\n\nAll my previous objections to v2 still stand:\n\nhttps://lore.kernel.org/git/60f80feeb4461_1305dc208b8@natae.notmuch/\n\n-- \nFelipe Contreras\n"}]}