{"thread":{"id":"54849","subject":"[PATCH v2 00/13] pull: pull mode part 2","startedAt":"2020-12-18T21:11:10Z","lastAt":"2020-12-18T21:11:56Z","messageCount":14,"participants":["Felipe Contreras"],"isPatch":true,"patchVersion":2,"patchTotal":13},"messages":[{"id":"412585","messageId":"20201218211026.1937168-1-felipe.contreras@gmail.com","threadId":"54849","inReplyTo":null,"subject":"[PATCH v2 00/13] pull: pull mode part 2","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2020-12-18T21:10:13Z","receivedAt":"2020-12-18T21:11:10Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"This patch series depends on fc/fc/pull-merge-rebase.\n\nIt's basically a collection of all the pull.mode patches rebased on top\nof what Junio merged to jch.\n\nExcept that while re-reading the old threads I noticed a suggestion by \nTheodore Ts'o that went unnoticed [1]. It would be very useful to make\nthe pull.mode configuration per-repository, instead of per-branch.\n\nThis way we can have two different configurations for:\n\n  git pull origin *\n  git pull john *\n\nThis also could allow us in the future to have a \"merge-inverted\" mode\nso users could finally merge correctly an update from the upstream\nbranch: master to origin/master, not origin/master to master.\n\nThis in addition allows us to consider yet another possible future\ndefault:\n\n  git pull                # default mode: merge-inverted\n  git pull origin master  # default mode: merge\n\nAlso, based on Junio's feedback [2] regarding the logic of opt_ff I\ndecided to remove it completely, since it's clear they don't\nimplicitely mean a merge.\n\nAnd finally; I noticed some quirkiness with semantics of --rebase and\npull.rebase, which I handled in the last patch.\n\nChanges since v1:\n\n * Added more cleanups\n * Added --merge option\n * Added REBASE_DEFAULT\n * Added warning with --ff options\n * Added pull.mode\n * Added pull.mode=fast-forward (previously ff-only)\n * Improved --rebase and pull.rebase interaction\n\nChanges since previous patch series:\n\n * Add remote.<name>.pullmode\n * Remove branch.<name>.pullmode\n * pull.mode now overrides pull.rebase\n\n[1] https://lore.kernel.org/git/20130312212027.GE14792@thunk.org\n[2] https://lore.kernel.org/git/20201214202647.3340193-1-gitster@pobox.com\n\nFelipe Contreras (13):\n  doc: pull: explain what is a fast-forward\n  pull: improve default warning\n  pull: cleanup autostash check\n  pull: trivial cleanup\n  pull: trivial whitespace style fix\n  pull: introduce --merge option\n  rebase: add REBASE_DEFAULT\n  pull: move configurations fetches\n  pull: show warning with --ff options\n  pull: add pull.mode\n  pull: add pull.mode=fast-forward\n  pull: reorganize mode conditionals\n  pull: improve --rebase and pull.rebase interaction\n\n Documentation/config/pull.txt   |   6 ++\n Documentation/config/remote.txt |   6 ++\n Documentation/git-pull.txt      |  49 ++++++++--\n builtin/pull.c                  | 165 +++++++++++++++++++++++---------\n rebase.c                        |  12 +++\n rebase.h                        |  13 ++-\n t/t5520-pull.sh                 | 108 +++++++++++++++++++++\n t/t7601-merge-pull-config.sh    |  34 +++++--\n 8 files changed, 330 insertions(+), 63 deletions(-)\n\nRange-diff:\n 1:  0925821483 !  1:  eb72fa24fa doc: pull: explain what is a fast-forward\n    @@ Documentation/git-pull.txt: Assume the following history exists and the current\n     +synchronize the local, and remote brances.\n     +\n     +In these situations `git pull` will warn you about your possible\n    -+options, which are either merge, or rebase. However, by default it will\n    -+continue doing a merge.\n    ++options, which are either merge (`--no-rebase`), or rebase (`--rebase`).\n    ++However, by default it will continue doing a merge.\n     +\n     +A merge will create a new commit with two parent commits (`G` and `C`)\n     +and a log message describing the changes, which you can edit.\n    @@ Documentation/git-pull.txt: and a log message from the user describing the chang\n      \n      In Git 1.7.0 or later, to cancel a conflicting merge, use\n      `git reset --merge`.  *Warning*: In older versions of Git, running 'git pull'\n    +@@ Documentation/git-pull.txt: version.\n    + \n    + SEE ALSO\n    + --------\n    +-linkgit:git-fetch[1], linkgit:git-merge[1], linkgit:git-config[1]\n    ++linkgit:git-fetch[1], linkgit:git-merge[1], linkgit:git-rebase[1],\n    ++linkgit:git-config[1]\n    + \n    + GIT\n    + ---\n 2:  46c14cf851 =  2:  37d1dcecfd pull: improve default warning\n 3:  aeb17014f5 =  3:  e3d29270ac pull: cleanup autostash check\n -:  ---------- >  4:  214bbbfcff pull: trivial cleanup\n -:  ---------- >  5:  69c1073064 pull: trivial whitespace style fix\n -:  ---------- >  6:  7561b4e7a8 pull: introduce --merge option\n -:  ---------- >  7:  17bf94fb5d rebase: add REBASE_DEFAULT\n -:  ---------- >  8:  8bd30852dd pull: move configurations fetches\n -:  ---------- >  9:  107fd4e0db pull: show warning with --ff options\n -:  ---------- > 10:  18d4caec8f pull: add pull.mode\n -:  ---------- > 11:  38feb507ce pull: add pull.mode=fast-forward\n -:  ---------- > 12:  80cd35372a pull: reorganize mode conditionals\n -:  ---------- > 13:  dc2819720a pull: improve --rebase and pull.rebase interaction\n-- \n2.30.0.rc0\n\n"},{"id":"412586","messageId":"20201218211026.1937168-2-felipe.contreras@gmail.com","threadId":"54849","inReplyTo":"20201218211026.1937168-1-felipe.contreras@gmail.com","subject":"[PATCH v2 01/13] doc: pull: explain what is a fast-forward","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2020-12-18T21:10:14Z","receivedAt":"2020-12-18T21:11:27Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"We want users to know what is a fast-forward in order to understand the\ndefault warning.\n\nLet's expand the explanation in order to cover both the simple, and the\ncomplex cases with as much detail as possible.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n Documentation/git-pull.txt | 41 ++++++++++++++++++++++++++++++++------\n 1 file changed, 35 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\nindex 5c3fb67c01..142df1c4a1 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -41,16 +41,41 @@ Assume the following history exists and the current branch is\n ------------\n \t  A---B---C master on origin\n \t /\n-    D---E---F---G master\n+    D---E master\n \t^\n \torigin/master in your repository\n ------------\n \n Then \"`git pull`\" will fetch and replay the changes from the remote\n `master` branch since it diverged from the local `master` (i.e., `E`)\n-until its current commit (`C`) on top of `master` and record the\n-result in a new commit along with the names of the two parent commits\n-and a log message from the user describing the changes.\n+until its current commit (`C`) on top of `master`.\n+\n+After the remote changes have been synchronized, the local `master` will\n+be fast-forwarded to the same commit as the remote one, therefore\n+creating a linear history.\n+\n+------------\n+    D---E---A---B---C master, origin/master\n+------------\n+\n+However, a non-fast-forward case looks very different:\n+\n+------------\n+\t  A---B---C origin/master\n+\t /\n+    D---E---F---G master\n+------------\n+\n+If there are additional changes in the local `master`, it's\n+not possible to fast-forward, so a decision must be made how to\n+synchronize the local, and remote brances.\n+\n+In these situations `git pull` will warn you about your possible\n+options, which are either merge (`--no-rebase`), or rebase (`--rebase`).\n+However, by default it will continue doing a merge.\n+\n+A merge will create a new commit with two parent commits (`G` and `C`)\n+and a log message describing the changes, which you can edit.\n \n ------------\n \t  A---B---C origin/master\n@@ -58,8 +83,11 @@ and a log message from the user describing the changes.\n     D---E---F---G---H master\n ------------\n \n+Once the merge commit is created (`H`), your local `master` branch has\n+incorporated the changes of the remote `master` branch.\n+\n See linkgit:git-merge[1] for details, including how conflicts\n-are presented and handled.\n+are presented and handled, and also linkgit:git-rebase[1].\n \n In Git 1.7.0 or later, to cancel a conflicting merge, use\n `git reset --merge`.  *Warning*: In older versions of Git, running 'git pull'\n@@ -248,7 +276,8 @@ version.\n \n SEE ALSO\n --------\n-linkgit:git-fetch[1], linkgit:git-merge[1], linkgit:git-config[1]\n+linkgit:git-fetch[1], linkgit:git-merge[1], linkgit:git-rebase[1],\n+linkgit:git-config[1]\n \n GIT\n ---\n-- \n2.30.0.rc0\n\n"},{"id":"412587","messageId":"20201218211026.1937168-3-felipe.contreras@gmail.com","threadId":"54849","inReplyTo":"20201218211026.1937168-1-felipe.contreras@gmail.com","subject":"[PATCH v2 02/13] pull: improve default warning","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2020-12-18T21:10:15Z","receivedAt":"2020-12-18T21:11:27Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"According to feedback from GitHub trainers [1], most newcomers don't\nunderstand what a rebase is. So in the default warning we want to\nprovide our users with a command that does the most sensible thing,\nfixes the divergence, gets rid of the warning, with the minimum mental\neffort, and happens to be the default:\n\n  git pull --no-rebase (later --merge)\n\nIn addition, we don't want to start by recommending a permanent\nconfiguration, but a temporary solution so they start training their\nfingers and maybe learn how to do a rebase. So we start with the commands.\n\nAlso, we need to be clear about what we mean by \"specifying\"; merge, or\nrebase.\n\nMoreover, thanks to the previous patch now \"git pull --help\" explains\nwhat a fast-forward is, let's mention that reference.\n\nAnd finally, use --global in the configuration commands like we did with\npush.default.\n\n[1] https://lore.kernel.org/git/20130909201751.GA14437@sigill.intra.peff.net/\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n builtin/pull.c | 22 ++++++++++++----------\n 1 file changed, 12 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex e8927fc2ff..a766d9762c 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -927,18 +927,20 @@ 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(_(\"Pulling without specifying how to reconcile divergent branches is discouraged;\\n\"\n+\t\t \"you need to specify if you want a merge, or a rebase.\\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-\t\t \"  git config pull.ff only       # fast-forward only\\n\"\n+\t\t \"  git pull --no-rebase # the default (merge)\\n\"\n+\t\t \"  git pull --rebase\\n\"\n \t\t \"\\n\"\n-\t\t \"You can replace \\\"git config\\\" with \\\"git config --global\\\" to set a default\\n\"\n-\t\t \"preference for all repositories. You can also pass --rebase, --no-rebase,\\n\"\n-\t\t \"or --ff-only on the command line to override the configured default per\\n\"\n-\t\t \"invocation.\\n\"));\n+\t\t \"You can squelch this message by running one of the following commands:\\n\"\n+\t\t \"\\n\"\n+\t\t \"  git config --global pull.rebase false  # merge\\n\"\n+\t\t \"  git config --global pull.rebase true   # rebase\\n\"\n+\t\t \"  git config --global pull.ff only       # fast-forward only\\n\"\n+\t\t \"\\n\"\n+\t\t \"If unsure, run \\\"git pull --no-rebase\\\".\\n\"\n+\t\t \"Read \\\"git pull --help\\\" for more information.\"));\n }\n \n int cmd_pull(int argc, const char **argv, const char *prefix)\n-- \n2.30.0.rc0\n\n"},{"id":"412588","messageId":"20201218211026.1937168-5-felipe.contreras@gmail.com","threadId":"54849","inReplyTo":"20201218211026.1937168-1-felipe.contreras@gmail.com","subject":"[PATCH v2 04/13] pull: trivial cleanup","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2020-12-18T21:10:17Z","receivedAt":"2020-12-18T21:11:27Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"There's no need to store ran_ff. Now it's obvious from the conditionals.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n builtin/pull.c | 6 ++----\n 1 file changed, 2 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 42cd6c38d8..21089e5a29 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -1055,7 +1055,6 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \n \tif (opt_rebase) {\n \t\tint ret = 0;\n-\t\tint ran_ff = 0;\n \n \t\tstruct object_id newbase;\n \t\tstruct object_id upstream;\n@@ -1070,11 +1069,10 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\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\tran_ff = 1;\n \t\t\tret = run_merge();\n-\t\t}\n-\t\tif (!ran_ff)\n+\t\t} else {\n \t\t\tret = run_rebase(&newbase, &upstream);\n+\t\t}\n \n \t\tif (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON ||\n \t\t\t     recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND))\n-- \n2.30.0.rc0\n\n"},{"id":"412589","messageId":"20201218211026.1937168-4-felipe.contreras@gmail.com","threadId":"54849","inReplyTo":"20201218211026.1937168-1-felipe.contreras@gmail.com","subject":"[PATCH v2 03/13] pull: cleanup autostash check","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2020-12-18T21:10:16Z","receivedAt":"2020-12-18T21:11:27Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Currently \"git pull --rebase\" takes a shortcut in the case a\nfast-forward merge is possible; run_merge() is called with --ff-only.\n\nHowever, \"git merge\" didn't have an --autostash option, so, when \"git\npull --rebase --autostash\" was called *and* the fast-forward merge\nshortcut was taken, then the pull failed.\n\nThis was fixed in commit f15e7cf5cc (pull: ff --rebase --autostash\nworks in dirty repo, 2017-06-01) by simply skipping the fast-forward\nmerge shortcut.\n\nLater on \"git merge\" learned the --autostash option [a03b55530a\n(merge: teach --autostash option, 2020-04-07)], and so did \"git pull\"\n[d9f15d37f1 (pull: pass --autostash to merge, 2020-04-07)].\n\nTherefore it's not necessary to skip the fast-forward merge shortcut\nanymore when called with --rebase --autostash.\n\nLet's always take the fast-forward merge shortcut by essentially\nreverting f15e7cf5cc.\n\nReviewed-by: Elijah Newren <newren@gmail.com>\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n builtin/pull.c | 16 +++++++---------\n 1 file changed, 7 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex a766d9762c..42cd6c38d8 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -949,7 +949,6 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \tstruct oid_array merge_heads = OID_ARRAY_INIT;\n \tstruct object_id orig_head, curr_head;\n \tstruct object_id rebase_fork_point;\n-\tint autostash;\n \tint rebase_unspecified = 0;\n \tint can_ff;\n \n@@ -984,8 +983,8 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \tif (get_oid(\"HEAD\", &orig_head))\n \t\toidclr(&orig_head);\n \n-\tautostash = config_autostash;\n \tif (opt_rebase) {\n+\t\tint autostash = config_autostash;\n \t\tif (opt_autostash != -1)\n \t\t\tautostash = opt_autostash;\n \n@@ -1067,13 +1066,12 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \t\t     recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) &&\n \t\t    submodule_touches_in_range(the_repository, &upstream, &curr_head))\n \t\t\tdie(_(\"cannot rebase with locally recorded submodule modifications\"));\n-\t\tif (!autostash) {\n-\t\t\tif (can_ff) {\n-\t\t\t\t/* we can fast-forward this without invoking rebase */\n-\t\t\t\topt_ff = \"--ff-only\";\n-\t\t\t\tran_ff = 1;\n-\t\t\t\tret = run_merge();\n-\t\t\t}\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\tran_ff = 1;\n+\t\t\tret = run_merge();\n \t\t}\n \t\tif (!ran_ff)\n \t\t\tret = run_rebase(&newbase, &upstream);\n-- \n2.30.0.rc0\n\n"},{"id":"412591","messageId":"20201218211026.1937168-6-felipe.contreras@gmail.com","threadId":"54849","inReplyTo":"20201218211026.1937168-1-felipe.contreras@gmail.com","subject":"[PATCH v2 05/13] pull: trivial whitespace style fix","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2020-12-18T21:10:18Z","receivedAt":"2020-12-18T21:11:27Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Two spaces unaligned to anything is not part of the coding-style. A\nsingle tab is.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n builtin/pull.c | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 21089e5a29..48e25a5061 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -126,9 +126,9 @@ static struct option pull_options[] = {\n \t/* Options passed to git-merge or git-rebase */\n \tOPT_GROUP(N_(\"Options related to merging\")),\n \tOPT_CALLBACK_F('r', \"rebase\", &opt_rebase,\n-\t  \"(false|true|merges|preserve|interactive)\",\n-\t  N_(\"incorporate changes by rebasing rather than merging\"),\n-\t  PARSE_OPT_OPTARG, parse_opt_rebase),\n+\t\t\"(false|true|merges|preserve|interactive)\",\n+\t\tN_(\"incorporate changes by rebasing rather than merging\"),\n+\t\tPARSE_OPT_OPTARG, parse_opt_rebase),\n \tOPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,\n \t\tN_(\"do not show a diffstat at the end of the merge\"),\n \t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG),\n-- \n2.30.0.rc0\n\n"},{"id":"412590","messageId":"20201218211026.1937168-7-felipe.contreras@gmail.com","threadId":"54849","inReplyTo":"20201218211026.1937168-1-felipe.contreras@gmail.com","subject":"[PATCH v2 06/13] pull: introduce --merge option","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2020-12-18T21:10:19Z","receivedAt":"2020-12-18T21:11:28Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Previously --no-rebase (which still works for backwards compatibility).\n\nNow we can update the default warning, and the git-pull(1) man page to\nuse --merge instead of the non-intuitive --no-rebase.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n Documentation/git-pull.txt   | 11 +++++++----\n builtin/pull.c               |  6 ++++--\n t/t7601-merge-pull-config.sh |  8 ++++----\n 3 files changed, 15 insertions(+), 10 deletions(-)\n\ndiff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\nindex 142df1c4a1..195496e63d 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -71,8 +71,8 @@ not possible to fast-forward, so a decision must be made how to\n synchronize the local, and remote brances.\n \n In these situations `git pull` will warn you about your possible\n-options, which are either merge (`--no-rebase`), or rebase (`--rebase`).\n-However, by default it will continue doing a merge.\n+options, which are either `--merge`, or `--rebase`. However, by default\n+it will continue doing a merge.\n \n A merge will create a new commit with two parent commits (`G` and `C`)\n and a log message describing the changes, which you can edit.\n@@ -159,8 +159,11 @@ It rewrites history, which does not bode well when you\n 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+-m::\n+--merge::\n+\tForce a merge.\n++\n+Previously this was --no-rebase, but that usage has been deprecated.\n \n Options related to fetching\n ~~~~~~~~~~~~~~~~~~~~~~~~~~~\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 48e25a5061..1336b59b21 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -129,6 +129,8 @@ static struct option pull_options[] = {\n \t\t\"(false|true|merges|preserve|interactive)\",\n \t\tN_(\"incorporate changes by rebasing rather than merging\"),\n \t\tPARSE_OPT_OPTARG, parse_opt_rebase),\n+\tOPT_SET_INT('m', \"merge\", &opt_rebase,\n+\t\tN_(\"incorporate changes by merging\"), REBASE_FALSE),\n \tOPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,\n \t\tN_(\"do not show a diffstat at the end of the merge\"),\n \t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG),\n@@ -930,7 +932,7 @@ static void show_advice_pull_non_ff(void)\n \tadvise(_(\"Pulling without specifying how to reconcile divergent branches is discouraged;\\n\"\n \t\t \"you need to specify if you want a merge, or a rebase.\\n\"\n \t\t \"\\n\"\n-\t\t \"  git pull --no-rebase # the default (merge)\\n\"\n+\t\t \"  git pull --merge # the default\\n\"\n \t\t \"  git pull --rebase\\n\"\n \t\t \"\\n\"\n \t\t \"You can squelch this message by running one of the following commands:\\n\"\n@@ -939,7 +941,7 @@ static void show_advice_pull_non_ff(void)\n \t\t \"  git config --global pull.rebase true   # rebase\\n\"\n \t\t \"  git config --global pull.ff only       # fast-forward only\\n\"\n \t\t \"\\n\"\n-\t\t \"If unsure, run \\\"git pull --no-rebase\\\".\\n\"\n+\t\t \"If unsure, run \\\"git pull --merge\\\".\\n\"\n \t\t \"Read \\\"git pull --help\\\" for more information.\"));\n }\n \ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex 52e8ccc933..6d03e0b9fe 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -60,9 +60,9 @@ test_expect_success 'pull.rebase not set and --rebase given' '\n \ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n '\n \n-test_expect_success 'pull.rebase not set and --no-rebase given' '\n+test_expect_success 'pull.rebase not set and --merge given' '\n \tgit reset --hard c0 &&\n-\tgit pull --no-rebase . c1 2>err &&\n+\tgit pull --merge . c1 2>err &&\n \ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n '\n \n@@ -119,9 +119,9 @@ test_expect_success 'pull.rebase not set and --rebase given (not-fast-forward)'\n \ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n '\n \n-test_expect_success 'pull.rebase not set and --no-rebase given (not-fast-forward)' '\n+test_expect_success 'pull.rebase not set and --merge given (not-fast-forward)' '\n \tgit reset --hard c2 &&\n-\tgit pull --no-rebase . c1 2>err &&\n+\tgit pull --merge . c1 2>err &&\n \ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n '\n \n-- \n2.30.0.rc0\n\n"},{"id":"412592","messageId":"20201218211026.1937168-8-felipe.contreras@gmail.com","threadId":"54849","inReplyTo":"20201218211026.1937168-1-felipe.contreras@gmail.com","subject":"[PATCH v2 07/13] rebase: add REBASE_DEFAULT","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2020-12-18T21:10:20Z","receivedAt":"2020-12-18T21:11:28Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"By introducing a default we can distinguish when the user has forced an\noption.\n\nTherefore there's no need pass around an extra variable variable (it's\nthe same as opt_rebase == REBASE_DEFAULT), nor is there any need to\ninitialize opt_rebase to an invalid value.\n\nAdditionally this will allow us to override the default with a\nconfiguration, and subsequently the configuration with arguments.\n\nCc: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n builtin/pull.c | 27 ++++++++++++---------------\n rebase.h       |  3 ++-\n 2 files changed, 14 insertions(+), 16 deletions(-)\n\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 1336b59b21..c0a90fa741 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -74,7 +74,7 @@ static char *opt_progress;\n static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT;\n \n /* Options passed to git-merge or git-rebase */\n-static enum rebase_type opt_rebase = -1;\n+static enum rebase_type opt_rebase;\n static char *opt_diffstat;\n static char *opt_log;\n static char *opt_signoff;\n@@ -326,7 +326,7 @@ static const char *config_get_ff(void)\n  * looks for the value of \"pull.rebase\". If both configuration keys do not\n  * exist, returns REBASE_FALSE.\n  */\n-static enum rebase_type config_get_rebase(int *rebase_unspecified)\n+static enum rebase_type config_get_rebase(void)\n {\n \tstruct branch *curr_branch = branch_get(\"HEAD\");\n \tconst char *value;\n@@ -346,9 +346,7 @@ static enum rebase_type config_get_rebase(int *rebase_unspecified)\n \tif (!git_config_get_value(\"pull.rebase\", &value))\n \t\treturn parse_config_rebase(\"pull.rebase\", value, 1);\n \n-\t*rebase_unspecified = 1;\n-\n-\treturn REBASE_FALSE;\n+\treturn REBASE_DEFAULT;\n }\n \n /**\n@@ -443,7 +441,7 @@ static void NORETURN die_no_merge_candidates(const char *repo, const char **refs\n \tconst char *remote = curr_branch ? curr_branch->remote_name : NULL;\n \n \tif (*refspecs) {\n-\t\tif (opt_rebase)\n+\t\tif (opt_rebase >= REBASE_TRUE)\n \t\t\tfprintf_ln(stderr, _(\"There is no candidate for rebasing against among the refs that you just fetched.\"));\n \t\telse\n \t\t\tfprintf_ln(stderr, _(\"There are no candidates for merging among the refs that you just fetched.\"));\n@@ -456,7 +454,7 @@ static void NORETURN die_no_merge_candidates(const char *repo, const char **refs\n \t\t\trepo);\n \t} else if (!curr_branch) {\n \t\tfprintf_ln(stderr, _(\"You are not currently on a branch.\"));\n-\t\tif (opt_rebase)\n+\t\tif (opt_rebase >= REBASE_TRUE)\n \t\t\tfprintf_ln(stderr, _(\"Please specify which branch you want to rebase against.\"));\n \t\telse\n \t\t\tfprintf_ln(stderr, _(\"Please specify which branch you want to merge with.\"));\n@@ -471,7 +469,7 @@ static void NORETURN die_no_merge_candidates(const char *repo, const char **refs\n \t\t\tremote_name = _(\"<remote>\");\n \n \t\tfprintf_ln(stderr, _(\"There is no tracking information for the current branch.\"));\n-\t\tif (opt_rebase)\n+\t\tif (opt_rebase >= REBASE_TRUE)\n \t\t\tfprintf_ln(stderr, _(\"Please specify which branch you want to rebase against.\"));\n \t\telse\n \t\t\tfprintf_ln(stderr, _(\"Please specify which branch you want to merge with.\"));\n@@ -951,7 +949,6 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \tstruct oid_array merge_heads = OID_ARRAY_INIT;\n \tstruct object_id orig_head, curr_head;\n \tstruct object_id rebase_fork_point;\n-\tint rebase_unspecified = 0;\n \tint can_ff;\n \n \tif (!getenv(\"GIT_REFLOG_ACTION\"))\n@@ -973,8 +970,8 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \tif (!opt_ff)\n \t\topt_ff = xstrdup_or_null(config_get_ff());\n \n-\tif (opt_rebase < 0)\n-\t\topt_rebase = config_get_rebase(&rebase_unspecified);\n+\tif (!opt_rebase)\n+\t\topt_rebase = config_get_rebase();\n \n \tif (read_cache_unmerged())\n \t\tdie_resolve_conflict(\"pull\");\n@@ -985,7 +982,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \tif (get_oid(\"HEAD\", &orig_head))\n \t\toidclr(&orig_head);\n \n-\tif (opt_rebase) {\n+\tif (opt_rebase >= REBASE_TRUE) {\n \t\tint autostash = config_autostash;\n \t\tif (opt_autostash != -1)\n \t\t\tautostash = opt_autostash;\n@@ -1045,17 +1042,17 @@ 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+\tif (opt_rebase >= REBASE_TRUE && merge_heads.nr > 1)\n \t\tdie(_(\"Cannot rebase onto multiple branches.\"));\n \n \tcan_ff = get_can_ff(&orig_head, &merge_heads.oid[0]);\n \n-\tif (rebase_unspecified && !opt_ff && !can_ff) {\n+\tif (!opt_rebase && !opt_ff && !can_ff) {\n \t\tif (opt_verbosity >= 0)\n \t\t\tshow_advice_pull_non_ff();\n \t}\n \n-\tif (opt_rebase) {\n+\tif (opt_rebase >= REBASE_TRUE) {\n \t\tint ret = 0;\n \n \t\tstruct object_id newbase;\ndiff --git a/rebase.h b/rebase.h\nindex cc723d4748..34d4acfd74 100644\n--- a/rebase.h\n+++ b/rebase.h\n@@ -3,7 +3,8 @@\n \n enum rebase_type {\n \tREBASE_INVALID = -1,\n-\tREBASE_FALSE = 0,\n+\tREBASE_DEFAULT = 0,\n+\tREBASE_FALSE,\n \tREBASE_TRUE,\n \tREBASE_PRESERVE,\n \tREBASE_MERGES,\n-- \n2.30.0.rc0\n\n"},{"id":"412593","messageId":"20201218211026.1937168-10-felipe.contreras@gmail.com","threadId":"54849","inReplyTo":"20201218211026.1937168-1-felipe.contreras@gmail.com","subject":"[PATCH v2 09/13] pull: show warning with --ff options","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2020-12-18T21:10:22Z","receivedAt":"2020-12-18T21:11:51Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"We want the user to specify either --merge or --rebase, if she doesn't\nwe throw a warning.\n\nUsing --ff, --no-ff, or --ff-only does not make the merge explicit.\n\nFor example, if the user has the following configuration:\n\n  git config pull.rebase true\n  git pull --no-ff\n\nA merge is not implied.\n\nWe should be consistent and either imply a merge; in which case a\nprevious \"pull.rebase=true\" configuration is overridden, or don't; in\nwhich case the warning should be thrown.\n\nCc: Junio C Hamano <gitster@pobox.com>\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n builtin/pull.c               |  2 +-\n t/t7601-merge-pull-config.sh | 12 ++++++------\n 2 files changed, 7 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 2fd5e44e03..f1a03ccd14 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -1044,7 +1044,7 @@ 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 (!opt_rebase && !opt_ff && !can_ff) {\n+\tif (!opt_rebase && !can_ff) {\n \t\tif (opt_verbosity >= 0)\n \t\t\tshow_advice_pull_non_ff();\n \t}\ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex 6d03e0b9fe..7c4607191a 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -96,21 +96,21 @@ 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 \"Pulling without specifying how to reconcile\" 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 \"Pulling without specifying how to reconcile\" 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 \"Pulling without specifying how to reconcile\" err\n '\n \n test_expect_success 'pull.rebase not set and --rebase given (not-fast-forward)' '\n@@ -128,19 +128,19 @@ test_expect_success 'pull.rebase not set and --merge given (not-fast-forward)' '\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 \"Pulling without specifying how to reconcile\" 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 \"Pulling without specifying how to reconcile\" 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 \"Pulling without specifying how to reconcile\" err\n '\n \n test_expect_success 'merge c1 with c2' '\n-- \n2.30.0.rc0\n\n"},{"id":"412594","messageId":"20201218211026.1937168-9-felipe.contreras@gmail.com","threadId":"54849","inReplyTo":"20201218211026.1937168-1-felipe.contreras@gmail.com","subject":"[PATCH v2 08/13] pull: move configurations fetches","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2020-12-18T21:10:21Z","receivedAt":"2020-12-18T21:11:51Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Now that we have FETCH_DEFAULT we can fetch the configuration before\nparsing the argument options.\n\nThe options will override the configuration, and if they don't;\nopt_rebase will remain being FETCH_DEFAULT.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n builtin/pull.c | 9 +++------\n 1 file changed, 3 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex c0a90fa741..2fd5e44e03 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -951,6 +951,9 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \tstruct object_id rebase_fork_point;\n \tint can_ff;\n \n+\topt_ff = xstrdup_or_null(config_get_ff());\n+\topt_rebase = config_get_rebase();\n+\n \tif (!getenv(\"GIT_REFLOG_ACTION\"))\n \t\tset_reflog_message(argc, argv);\n \n@@ -967,12 +970,6 @@ 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-\t\topt_ff = xstrdup_or_null(config_get_ff());\n-\n-\tif (!opt_rebase)\n-\t\topt_rebase = config_get_rebase();\n-\n \tif (read_cache_unmerged())\n \t\tdie_resolve_conflict(\"pull\");\n \n-- \n2.30.0.rc0\n\n"},{"id":"412595","messageId":"20201218211026.1937168-11-felipe.contreras@gmail.com","threadId":"54849","inReplyTo":"20201218211026.1937168-1-felipe.contreras@gmail.com","subject":"[PATCH v2 10/13] pull: add pull.mode","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2020-12-18T21:10:23Z","receivedAt":"2020-12-18T21:11:53Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"The evolution of pull options has somewhat served most users, however,\nthey have been found lacking for a very needed trio: merge / rebase /\nfast-forward-only.\n\nAnother thing that is missing is the possibility to specify pull options\non a per-repository basis, as Theodore Ts'o suggested long time ago\n[1].\n\nThis patch adds a pull.mode option with two possible values (for now);\nmerge and rebase. If set, it overrides what the user has specified in\npull.rebase, and it's updated with either --merge, or --rebase.\n\nIn addition to pull.mode, a 'remote.<name>.pullmode' configuration is\nintroduced, so the user can specify the desired options based on the\nsource of the pull, rather than the destination.\n\nAnd of course; new modes like fast-forward-only can be introduced later\non.\n\n[1] https://lore.kernel.org/git/20130312212027.GE14792@thunk.org\n\nCc: Theodore Ts'o <tytso@mit.edu>\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n Documentation/config/pull.txt   |  6 +++\n Documentation/config/remote.txt |  6 +++\n Documentation/git-pull.txt      |  3 +-\n builtin/pull.c                  | 81 +++++++++++++++++++++++++++++++--\n rebase.c                        | 10 ++++\n rebase.h                        |  9 ++++\n t/t5520-pull.sh                 | 54 ++++++++++++++++++++++\n t/t7601-merge-pull-config.sh    | 14 ++++++\n 8 files changed, 178 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/config/pull.txt b/Documentation/config/pull.txt\nindex 5404830609..f4385cde33 100644\n--- a/Documentation/config/pull.txt\n+++ b/Documentation/config/pull.txt\n@@ -29,6 +29,12 @@ mode.\n it unless you understand the implications (see linkgit:git-rebase[1]\n for details).\n \n+pull.mode::\n+\tWhen \"git pull\" is run, this determines if it would either merge or\n+\trebase the fetched branch. The possible values are 'merge',\n+\tand 'rebase'. See \"branch.<name>.pullmode\" for setting this on a\n+\tper-branch basis.\n+\n pull.octopus::\n \tThe default merge strategy to use when pulling multiple branches\n \tat once.\ndiff --git a/Documentation/config/remote.txt b/Documentation/config/remote.txt\nindex a8e6437a90..a732c92cf5 100644\n--- a/Documentation/config/remote.txt\n+++ b/Documentation/config/remote.txt\n@@ -84,3 +84,9 @@ remote.<name>.promisor::\n remote.<name>.partialclonefilter::\n \tThe filter that will be applied when fetching from this\n \tpromisor remote.\n+\n+remote.<name>.pullmode::\n+\tWhen \"git pull\" is run, this determines if it would either merge or\n+\trebase the branches from this remote. The possible values are 'merge', and\n+\t'rebase'. See \"pull.mode\" for doing this in a non\n+\trepository-specific manner.\ndiff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\nindex 195496e63d..65f8b16a7d 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -72,7 +72,8 @@ synchronize the local, and remote brances.\n \n In these situations `git pull` will warn you about your possible\n options, which are either `--merge`, or `--rebase`. However, by default\n-it will continue doing a merge.\n+it will continue doing a merge (you can change that with the `pull.mode`\n+configuration).\n \n A merge will create a new commit with two parent commits (`G` and `C`)\n and a log message describing the changes, which you can edit.\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex f1a03ccd14..bfadd585c7 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -27,6 +27,8 @@\n #include \"commit-reach.h\"\n #include \"sequencer.h\"\n \n+static enum pull_mode_type mode;\n+\n /**\n  * Parses the value of --rebase. If value is a false value, returns\n  * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is\n@@ -49,6 +51,14 @@ static enum rebase_type parse_config_rebase(const char *key, const char *value,\n \treturn REBASE_INVALID;\n }\n \n+static enum pull_mode_type parse_config_pull_mode(const char *key, const char *value)\n+{\n+\tenum pull_mode_type v = pull_mode_parse_value(value);\n+\tif (v == PULL_MODE_INVALID)\n+\t\tdie(_(\"Invalid value for %s: %s\"), key, value);\n+\treturn v;\n+}\n+\n /**\n  * Callback for --rebase, which parses arg with parse_config_rebase().\n  */\n@@ -60,9 +70,21 @@ static int parse_opt_rebase(const struct option *opt, const char *arg, int unset\n \t\t*value = parse_config_rebase(\"--rebase\", arg, 0);\n \telse\n \t\t*value = unset ? REBASE_FALSE : REBASE_TRUE;\n+\n+\tif (*value > 0)\n+\t\tmode = *value >= REBASE_TRUE ? PULL_MODE_REBASE : PULL_MODE_MERGE;\n+\n \treturn *value == REBASE_INVALID ? -1 : 0;\n }\n \n+static int parse_opt_merge(const struct option *opt, const char *arg, int unset)\n+{\n+\tenum rebase_type *value = opt->value;\n+\tmode = PULL_MODE_MERGE;\n+\t*value = REBASE_FALSE;\n+\treturn 0;\n+}\n+\n static const char * const pull_usage[] = {\n \tN_(\"git pull [<options>] [<repository> [<refspec>...]]\"),\n \tNULL\n@@ -129,8 +151,9 @@ static struct option pull_options[] = {\n \t\t\"(false|true|merges|preserve|interactive)\",\n \t\tN_(\"incorporate changes by rebasing rather than merging\"),\n \t\tPARSE_OPT_OPTARG, parse_opt_rebase),\n-\tOPT_SET_INT('m', \"merge\", &opt_rebase,\n-\t\tN_(\"incorporate changes by merging\"), REBASE_FALSE),\n+\tOPT_CALLBACK_F('m', \"merge\", &opt_rebase, NULL,\n+\t\tN_(\"incorporate changes by merging\"),\n+\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_merge),\n \tOPT_PASSTHRU('n', NULL, &opt_diffstat, NULL,\n \t\tN_(\"do not show a diffstat at the end of the merge\"),\n \t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG),\n@@ -349,6 +372,31 @@ static enum rebase_type config_get_rebase(void)\n \treturn REBASE_DEFAULT;\n }\n \n+static enum pull_mode_type config_get_pull_mode(const char *repo)\n+{\n+\tconst char *value;\n+\tstruct remote *remote;\n+\n+\tremote = remote_get(repo);\n+\n+\tif (remote) {\n+\t\tchar *key = xstrfmt(\"remote.%s.pullmode\", remote->name);\n+\n+\t\tif (!git_config_get_value(key, &value)) {\n+\t\t\tenum pull_mode_type ret = parse_config_pull_mode(key, value);\n+\t\t\tfree(key);\n+\t\t\treturn ret;\n+\t\t}\n+\n+\t\tfree(key);\n+\t}\n+\n+\tif (!git_config_get_value(\"pull.mode\", &value))\n+\t\treturn parse_config_pull_mode(\"pull.mode\", value);\n+\n+\treturn PULL_MODE_DEFAULT;\n+}\n+\n /**\n  * Read config variables.\n  */\n@@ -935,8 +983,8 @@ static void show_advice_pull_non_ff(void)\n \t\t \"\\n\"\n \t\t \"You can squelch this message by running one of the following commands:\\n\"\n \t\t \"\\n\"\n-\t\t \"  git config --global pull.rebase false  # merge\\n\"\n-\t\t \"  git config --global pull.rebase true   # rebase\\n\"\n+\t\t \"  git config --global pull.mode merge\\n\"\n+\t\t \"  git config --global pull.mode rebase\\n\"\n \t\t \"  git config --global pull.ff only       # fast-forward only\\n\"\n \t\t \"\\n\"\n \t\t \"If unsure, run \\\"git pull --merge\\\".\\n\"\n@@ -970,6 +1018,31 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \n \tparse_repo_refspecs(argc, argv, &repo, &refspecs);\n \n+\t/*\n+\t * If the user has not specified --merge or --rebase, fetch pull.mode to override\n+\t * pull.rename.\n+\t */\n+\tif (!mode) {\n+\t\tmode = config_get_pull_mode(repo);\n+\n+\t\tswitch (mode) {\n+\t\tcase PULL_MODE_MERGE:\n+\t\t\topt_rebase = REBASE_FALSE;\n+\t\t\tbreak;\n+\t\tcase PULL_MODE_REBASE:\n+\t\t\t/* Do not oeverride other rebase modes */\n+\t\t\tif (opt_rebase < REBASE_TRUE)\n+\t\t\t\topt_rebase = REBASE_TRUE;\n+\t\t\tbreak;\n+\t\tcase PULL_MODE_DEFAULT:\n+\t\t\tif (opt_rebase > 0)\n+\t\t\t\tmode = opt_rebase >= REBASE_TRUE ? PULL_MODE_REBASE : PULL_MODE_MERGE;\n+\t\t\tbreak;\n+\t\tdefault:\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n \tif (read_cache_unmerged())\n \t\tdie_resolve_conflict(\"pull\");\n \ndiff --git a/rebase.c b/rebase.c\nindex f8137d859b..bdfca49886 100644\n--- a/rebase.c\n+++ b/rebase.c\n@@ -33,3 +33,13 @@ enum rebase_type rebase_parse_value(const char *value)\n \n \treturn REBASE_INVALID;\n }\n+\n+enum pull_mode_type pull_mode_parse_value(const char *value)\n+{\n+\tif (!strcmp(value, \"merge\") || !strcmp(value, \"m\"))\n+\t\treturn PULL_MODE_MERGE;\n+\telse if (!strcmp(value, \"rebase\") || !strcmp(value, \"r\"))\n+\t\treturn PULL_MODE_REBASE;\n+\n+\treturn PULL_MODE_INVALID;\n+}\ndiff --git a/rebase.h b/rebase.h\nindex 34d4acfd74..5ab8f4ddd5 100644\n--- a/rebase.h\n+++ b/rebase.h\n@@ -13,4 +13,13 @@ enum rebase_type {\n \n enum rebase_type rebase_parse_value(const char *value);\n \n+enum pull_mode_type {\n+\tPULL_MODE_INVALID = -1,\n+\tPULL_MODE_DEFAULT = 0,\n+\tPULL_MODE_MERGE,\n+\tPULL_MODE_REBASE\n+};\n+\n+enum pull_mode_type pull_mode_parse_value(const char *value);\n+\n #endif /* REBASE */\ndiff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\nindex 9fae07cdfa..59799ac4d5 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -449,6 +449,16 @@ test_expect_success 'pull.rebase' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'pull.mode rebase' '\n+\tgit reset --hard before-rebase &&\n+\ttest_config pull.mode rebase &&\n+\tgit pull . copy &&\n+\ttest_cmp_rev HEAD^ copy &&\n+\techo new >expect &&\n+\tgit show HEAD:file2 >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'pull --autostash & pull.rebase=true' '\n \ttest_config pull.rebase true &&\n \ttest_pull_autostash 1 --autostash\n@@ -480,6 +490,18 @@ test_expect_success 'branch.to-rebase.rebase should override pull.rebase' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'remote..pullmode' '\n+\tgit reset --hard before-rebase &&\n+\tgit remote add test_remote . &&\n+\ttest_when_finished \"git remote remove test_remote\" &&\n+\ttest_config remote.test_remote.pullmode rebase &&\n+\tgit pull test_remote copy &&\n+\ttest_cmp_rev HEAD^ copy &&\n+\techo new >expect &&\n+\tgit show HEAD:file2 >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'pull --rebase warns on --verify-signatures' '\n \tgit reset --hard before-rebase &&\n \tgit pull --rebase --verify-signatures . copy 2>err &&\n@@ -523,6 +545,17 @@ test_expect_success 'pull.rebase=false create a new merge commit' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'pull.mode=merge create a new merge commit' '\n+\tgit reset --hard before-preserve-rebase &&\n+\ttest_config pull.mode merge &&\n+\tgit pull . copy &&\n+\ttest_cmp_rev HEAD^1 before-preserve-rebase &&\n+\ttest_cmp_rev HEAD^2 copy &&\n+\techo file3 >expect &&\n+\tgit show HEAD:file3.t >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'pull.rebase=true flattens keep-merge' '\n \tgit reset --hard before-preserve-rebase &&\n \ttest_config pull.rebase true &&\n@@ -552,6 +585,16 @@ test_expect_success REBASE_P \\\n \ttest_cmp_rev HEAD^2 keep-merge\n '\n \n+test_expect_success REBASE_P \\\n+\t'pull.rebase=preserve rebases and merges keep-merge with pull.mode' '\n+\tgit reset --hard before-preserve-rebase &&\n+\ttest_config pull.mode rebase &&\n+\ttest_config pull.rebase preserve &&\n+\tgit pull . copy &&\n+\ttest_cmp_rev HEAD^^ copy &&\n+\ttest_cmp_rev HEAD^2 keep-merge\n+'\n+\n test_expect_success 'pull.rebase=interactive' '\n \twrite_script \"$TRASH_DIRECTORY/fake-editor\" <<-\\EOF &&\n \techo I was here >fake.out &&\n@@ -593,6 +636,17 @@ test_expect_success '--rebase=false create a new merge commit' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success '--rebase=false create a new merge commit with pull.mode' '\n+\tgit reset --hard before-preserve-rebase &&\n+\ttest_config pull.mode rebase &&\n+\tgit pull --rebase=false . copy &&\n+\ttest_cmp_rev HEAD^1 before-preserve-rebase &&\n+\ttest_cmp_rev HEAD^2 copy &&\n+\techo file3 >expect &&\n+\tgit show HEAD:file3.t >actual &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success '--rebase=true rebases and flattens keep-merge' '\n \tgit reset --hard before-preserve-rebase &&\n \ttest_config pull.rebase preserve &&\ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex 7c4607191a..47fd2e2d05 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -33,6 +33,13 @@ test_expect_success 'pull.rebase not set' '\n \ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n '\n \n+test_expect_success 'pull.mode set' '\n+\tgit reset --hard c0 &&\n+\ttest_config pull.mode merge &&\n+\tgit pull . c1 2>err &&\n+\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" 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@@ -92,6 +99,13 @@ test_expect_success 'pull.rebase not set (not-fast-forward)' '\n \ttest_i18ngrep \"Pulling without specifying how to reconcile\" decoded\n '\n \n+test_expect_success 'pull.mode set' '\n+\tgit reset --hard c2 &&\n+\ttest_config pull.mode merge &&\n+\tgit pull . c1 2>err &&\n+\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\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-- \n2.30.0.rc0\n\n"},{"id":"412596","messageId":"20201218211026.1937168-13-felipe.contreras@gmail.com","threadId":"54849","inReplyTo":"20201218211026.1937168-1-felipe.contreras@gmail.com","subject":"[PATCH v2 12/13] pull: reorganize mode conditionals","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2020-12-18T21:10:25Z","receivedAt":"2020-12-18T21:11:54Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Now that everything is in place we can shuffle around the conditionals\nso it's clearer what we are trying to do.\n\nNo functional changes.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n builtin/pull.c | 10 +++++-----\n 1 file changed, 5 insertions(+), 5 deletions(-)\n\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex ef23d8a52f..ad8afabe9b 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -1118,12 +1118,12 @@ 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 (mode == PULL_MODE_FAST_FORWARD && !can_ff)\n-\t\tdie(_(\"The pull was not fast-forward, please either merge or rebase.\\n\"));\n-\n-\tif (!opt_rebase && !can_ff) {\n-\t\tif (opt_verbosity >= 0)\n+\tif (!can_ff) {\n+\t\tif (!mode && opt_verbosity >= 0)\n \t\t\tshow_advice_pull_non_ff();\n+\n+\t\tif (mode == PULL_MODE_FAST_FORWARD)\n+\t\t\tdie(_(\"The pull was not fast-forward, please either merge or rebase.\\n\"));\n \t}\n \n \tif (opt_rebase >= REBASE_TRUE) {\n-- \n2.30.0.rc0\n\n"},{"id":"412597","messageId":"20201218211026.1937168-14-felipe.contreras@gmail.com","threadId":"54849","inReplyTo":"20201218211026.1937168-1-felipe.contreras@gmail.com","subject":"[PATCH v2 13/13] pull: improve --rebase and pull.rebase interaction","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2020-12-18T21:10:26Z","receivedAt":"2020-12-18T21:11:55Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Currently --rebase without argument overrides pull.rebase:\n\n  git config pull.rebase merges\n  git pull --rebase\n\nUp until now this hasn't been a big issue, since user has not been\nforced to specify a merge, or a rebase. But with the introduction of\n--merge and pull.mode, the user could in theory have the following\nconfiguration:\n\n  git config pull.mode merge\n  git config pull.rebase merges\n\nIn such case, the user would expect:\n\n  git pull --rebase\n\nTo be the same as:\n\n  git pull --rebase=merges\n\nIf the user wants to override the configuration, she can do:\n\n  git pull --rebase=true\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n builtin/pull.c  | 10 ++++++++--\n t/t5520-pull.sh | 10 ++++++++++\n 2 files changed, 18 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex ad8afabe9b..4e8e9d85a2 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -68,8 +68,14 @@ static int parse_opt_rebase(const struct option *opt, const char *arg, int unset\n \n \tif (arg)\n \t\t*value = parse_config_rebase(\"--rebase\", arg, 0);\n-\telse\n-\t\t*value = unset ? REBASE_FALSE : REBASE_TRUE;\n+\telse {\n+\t\tif (!unset) {\n+\t\t\t/* --rebase shouldn't override pull.rebase=merges (and others) */\n+\t\t\tif (*value < REBASE_TRUE)\n+\t\t\t\t*value = REBASE_TRUE;\n+\t\t} else\n+\t\t\t*value = REBASE_FALSE;\n+\t}\n \n \tif (*value > 0)\n \t\tmode = *value >= REBASE_TRUE ? PULL_MODE_REBASE : PULL_MODE_MERGE;\ndiff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\nindex 45d818065f..d0f5722eab 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -595,6 +595,16 @@ test_expect_success REBASE_P \\\n \ttest_cmp_rev HEAD^2 keep-merge\n '\n \n+test_expect_success REBASE_P \\\n+\t'pull.rebase=preserve interacts correctly with pull.mode and --rebase' '\n+\tgit reset --hard before-preserve-rebase &&\n+\ttest_config pull.mode merge &&\n+\ttest_config pull.rebase preserve &&\n+\tgit pull --rebase . copy &&\n+\ttest_cmp_rev HEAD^^ copy &&\n+\ttest_cmp_rev HEAD^2 keep-merge\n+'\n+\n test_expect_success 'pull.rebase=interactive' '\n \twrite_script \"$TRASH_DIRECTORY/fake-editor\" <<-\\EOF &&\n \techo I was here >fake.out &&\n-- \n2.30.0.rc0\n\n"},{"id":"412598","messageId":"20201218211026.1937168-12-felipe.contreras@gmail.com","threadId":"54849","inReplyTo":"20201218211026.1937168-1-felipe.contreras@gmail.com","subject":"[PATCH v2 11/13] pull: add pull.mode=fast-forward","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2020-12-18T21:10:24Z","receivedAt":"2020-12-18T21:11:56Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"It is very typical for Git newcomers to inadvertently create merges and\nworse; pushing them. This is one of the reasons many experienced users\nprefer to avoid 'git pull', and recommend newcomers to avoid it as well.\n\nTo escape these problems--and keep 'git pull' useful--it has been\nsuggested that 'git pull' barfs by default if the merge is\nnon-fast-forward, which unfortunately would break backwards\ncompatibility.\n\nThis patch leaves everything in place to enable this new mode, but it\nonly gets enabled if the user specifically configures it:\n\n  pull.mode = fast-forward\n\nLater on this mode can be enabled by default.\n\nFor *some* of the long discussions you can read:\n\nhttps://lore.kernel.org/git/20130522115042.GA20649@inner.h.apk.li\nhttps://lore.kernel.org/git/1377988690-23460-1-git-send-email-felipe.contreras@gmail.com\nhttps://lore.kernel.org/4ay6w9i74cygt6ii1b0db7wg.1398433713382@email.android.com\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n Documentation/config/pull.txt   |  4 +--\n Documentation/config/remote.txt |  4 +--\n builtin/pull.c                  |  6 ++++-\n rebase.c                        |  2 ++\n rebase.h                        |  3 ++-\n t/t5520-pull.sh                 | 44 +++++++++++++++++++++++++++++++++\n 6 files changed, 57 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/config/pull.txt b/Documentation/config/pull.txt\nindex f4385cde33..2b5e813259 100644\n--- a/Documentation/config/pull.txt\n+++ b/Documentation/config/pull.txt\n@@ -32,8 +32,8 @@ for details).\n pull.mode::\n \tWhen \"git pull\" is run, this determines if it would either merge or\n \trebase the fetched branch. The possible values are 'merge',\n-\tand 'rebase'. See \"branch.<name>.pullmode\" for setting this on a\n-\tper-branch basis.\n+\t'rebase', and 'fast-forward'. See \"branch.<name>.pullmode\" for setting\n+\tthis on a per-branch basis.\n \n pull.octopus::\n \tThe default merge strategy to use when pulling multiple branches\ndiff --git a/Documentation/config/remote.txt b/Documentation/config/remote.txt\nindex a732c92cf5..e7db1d46c1 100644\n--- a/Documentation/config/remote.txt\n+++ b/Documentation/config/remote.txt\n@@ -87,6 +87,6 @@ remote.<name>.partialclonefilter::\n \n remote.<name>.pullmode::\n \tWhen \"git pull\" is run, this determines if it would either merge or\n-\trebase the branches from this remote. The possible values are 'merge', and\n-\t'rebase'. See \"pull.mode\" for doing this in a non\n+\trebase the branches from this remote. The possible values are 'merge',\n+\t'rebase', and 'fast-forward'. See \"pull.mode\" for doing this in a non\n \trepository-specific manner.\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex bfadd585c7..ef23d8a52f 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -985,7 +985,7 @@ static void show_advice_pull_non_ff(void)\n \t\t \"\\n\"\n \t\t \"  git config --global pull.mode merge\\n\"\n \t\t \"  git config --global pull.mode rebase\\n\"\n-\t\t \"  git config --global pull.ff only       # fast-forward only\\n\"\n+\t\t \"  git config --global pull.mode fast-forward\\n\"\n \t\t \"\\n\"\n \t\t \"If unsure, run \\\"git pull --merge\\\".\\n\"\n \t\t \"Read \\\"git pull --help\\\" for more information.\"));\n@@ -1027,6 +1027,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\n \n \t\tswitch (mode) {\n \t\tcase PULL_MODE_MERGE:\n+\t\tcase PULL_MODE_FAST_FORWARD:\n \t\t\topt_rebase = REBASE_FALSE;\n \t\t\tbreak;\n \t\tcase PULL_MODE_REBASE:\n@@ -1117,6 +1118,9 @@ 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 (mode == PULL_MODE_FAST_FORWARD && !can_ff)\n+\t\tdie(_(\"The pull was not fast-forward, please either merge or rebase.\\n\"));\n+\n \tif (!opt_rebase && !can_ff) {\n \t\tif (opt_verbosity >= 0)\n \t\t\tshow_advice_pull_non_ff();\ndiff --git a/rebase.c b/rebase.c\nindex bdfca49886..9fe99b5b16 100644\n--- a/rebase.c\n+++ b/rebase.c\n@@ -40,6 +40,8 @@ enum pull_mode_type pull_mode_parse_value(const char *value)\n \t\treturn PULL_MODE_MERGE;\n \telse if (!strcmp(value, \"rebase\") || !strcmp(value, \"r\"))\n \t\treturn PULL_MODE_REBASE;\n+\telse if (!strcmp(value, \"fast-forward\") || !strcmp(value, \"f\"))\n+\t\treturn PULL_MODE_FAST_FORWARD;\n \n \treturn PULL_MODE_INVALID;\n }\ndiff --git a/rebase.h b/rebase.h\nindex 5ab8f4ddd5..e66a73feb4 100644\n--- a/rebase.h\n+++ b/rebase.h\n@@ -17,7 +17,8 @@ enum pull_mode_type {\n \tPULL_MODE_INVALID = -1,\n \tPULL_MODE_DEFAULT = 0,\n \tPULL_MODE_MERGE,\n-\tPULL_MODE_REBASE\n+\tPULL_MODE_REBASE,\n+\tPULL_MODE_FAST_FORWARD\n };\n \n enum pull_mode_type pull_mode_parse_value(const char *value);\ndiff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\nindex 59799ac4d5..45d818065f 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -873,4 +873,48 @@ test_expect_success 'git pull --rebase against local branch' '\n \ttest_cmp expect file2\n '\n \n+setup_other () {\n+\ttest_when_finished \"git checkout master && git branch -D other test\" &&\n+\tgit checkout -b other $1 &&\n+\t>new &&\n+\tgit add new &&\n+\tgit commit -m new &&\n+\tgit checkout -b test -t other &&\n+\tgit reset --hard master\n+}\n+\n+setup_ff () {\n+\tsetup_other master\n+}\n+\n+setup_non_ff () {\n+\tsetup_other master^\n+}\n+\n+test_expect_success 'fast-forward (pull.mode=fast-forward)' '\n+\tsetup_ff &&\n+\tgit -c pull.mode=fast-forward pull\n+'\n+\n+test_expect_success 'non-fast-forward (pull.mode=fast-forward)' '\n+\tsetup_non_ff &&\n+\ttest_must_fail git -c pull.mode=fast-forward pull\n+'\n+\n+test_expect_success 'non-fast-forward with merge (pull.mode=fast-forward)' '\n+\tsetup_non_ff &&\n+\tgit -c pull.mode=fast-forward pull --merge\n+'\n+\n+test_expect_success 'non-fast-forward with rebase (pull.mode=fast-forward)' '\n+\tsetup_non_ff &&\n+\tgit -c pull.mode=fast-forward pull --rebase\n+'\n+\n+test_expect_success 'non-fast-forward error message (pull.mode=fast-forward)' '\n+\tsetup_non_ff &&\n+\ttest_must_fail git -c pull.mode=fast-forward pull 2> error &&\n+\ttest_i18ngrep \"The pull was not fast-forward\" error\n+'\n+\n test_done\n-- \n2.30.0.rc0\n\n"}]}