{"thread":{"id":"56049","subject":"[RFC PATCH 00/35] git update: fix broken git pull","startedAt":"2021-07-05T12:32:13Z","lastAt":"2021-07-06T22:26:24Z","messageCount":53,"participants":["Felipe Contreras","Ævar Arnfjörð Bjarmason","Randall S. Becker","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":35},"messages":[{"id":"429200","messageId":"20210705123209.1808663-1-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":null,"subject":"[RFC PATCH 00/35] git update: fix broken git pull","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:34Z","receivedAt":"2021-07-05T12:32:13Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Throughout the years many big threads have been started [1] [2] [3] [4]\n[5] [6] because `git pull` is broken (for everyone that isn't a\nmaintainer), and nothing has been fixed.\n\nThe two main problems are:\n\n  1. The order of the parents is the opposite of what it should be\n  2. By default it should fail unless it's a fast-forward\n\nHowever, every attempt to fix these issues gets entangled in an\ninescapable web of interrelated problems.\n\nThat's because `git pull` is in fact *two* commands pretending to be\none.\n\nI have spent several days re-reading old threads looking for anything\nrelated to `git pull` and fast-forward throughout the entire history of\nthe git mailing list, going as far back as 2008.\n\nThis process has been illuminating.\n\nThe amount of people that have pondered about, and even suggested, a\n`git update` command is striking:\n\n * Linus Torvalds: [7]\n * Junio C. Hamano: [8]\n * Richard Hansen: [9]\n * John Keeping: [10]\n\nAnd of course me [11], who not only suggested it, but implemented it\nas well [12].\n\nIt's not just the name, but the behavior too:\n\n  `git fetch` + `git merge --ff-only`\n\nThis way `git pull` can be used by maintainers to integrate pull\nrequests, and `git update` for everyone else (normal developers) to\nupdate their local branch to its upstream.\n\nPlus it has a --merge mode that creates a merge commit, but with the\nparents in the right order (master to upstream, not upstream to master).\n\nBoth problems are fixed by this approach.\n\n\nAlso, a thorny issue since a long time ago has been how to properly\nadvise users what to do in case a fast-forward is not possible.\n\nI propose a new command: `git fast-forward`. When a fast-forward is not\npossible `git fast-forward` fails, and shows an advice (that can be\neasily turned off).\n\nTherefore in fact `git update` is really `git fetch` +\n`git fast-forward`, and it's the latter that throws the advice. The\nadvice also suggests `git help fast-forward` which has an explanation\nabout what a fast-forward is, what are the options to synchronize\nwith the remote branch (merge and rebase), and what do they look like.\n\nIn addition there's a ton of fixes for `git pull` which have been sent\nbefore, including the `pull.mode` configuration, the `--merge` option,\nthe per-branch `pullMode` configuration, `pull.mode=fast-forward`, and\nan improved advice message to prepare users for a future default change:\n\n  The pull was not a fast-forward, in the future you will have to choose\n  between a merge or a rebase.\n\n  To quell this message you have two main options:\n\n  1. Adopt the new behavior:\n\n    git config --global pull.mode fast-forward\n\n  2. Maintain the current behavior:\n\n    git config --global pull.mode merge\n\n  For now we will fall back to the traditional behavior: merge.\n  For more information check \"git help fast-forward\".\n\nIt doesn't just improve the situation for normal developers, but it also\nopens the possibility to fix a wart in `git pull --rebase`:\n\nRight now:\n\n git pull --merge github john (merge \"github/john\" into \"master)\n git pull --rebase github john (rebase \"master\" onto \"github/john\")\n\nSince now `git update --rebase` can be used to rebase the current branch\nonto its upstream, that leaves `git pull --rebase` open to rebase\ngithub/john onto master, not the other way around like it currently is.\n\n\nJunio has suggested there's no consensus over fast-forward by default,\nbut that's not true. Here is a non-exhaustive list of people that have\nagreed the default should be fast-forward only.\n\n * Linus Torvalds\n * Junio C Hamano\n * Jeff King\n * Jonathan Nieder\n * Richard Hansen\n * Philip Oakley\n * Elijah Newren\n * John Keeping\n * Ramkumar Ramachandra\n * Alex Henrie\n * W. Trevor King\n * Greg Troxel\n * Peter Kjellerstedt\n * Konstantin Tokarev\n * Robert Dailey\n * Vít Ondruch\n * Raymond E. Pasco\n * Jacob Keller\n * Felipe Contreras\n\nThere's only one person against the change: Matthieu Moy. His argument\nback then [13] was: why ask users to merge or rebase if newcomers will\nnot pick a rebase? To that my response is: for the same reason we ask\nthem to do `git commit --all`: so that they eventually learn about the\nstaging area. Just like they can do `git commit --all`, they can do\n`git pull --merge`. They can figure out what that means later.\n\nThe people in favor of reordering the parents:\n\n * Andreas Krey\n * John Szakmeister\n * Jeremy Rosen\n * John Keeping\n\nAnd the people in favor of my old `git update`:\n\n * Damien Robert\n * Ping Yin\n * Philippe Vaucher\n\n\nThis patch series doesn't implement all of the features my old\n`git update` had, but it implements most of them, most\nimportantly--unlike `git pull`--it's not broken.\n\nRight now only `git update` without arguments works, and only if the\nupstream branch is configured. I have a pretty good idea of what the it\nshould do with different arguments (as I had already implemented that),\nbut for now that's open to discussion.\n\nIf you want to give it a try:\n\nhttps://github.com/felipec/git/tree/fc/update\n\n[1] https://lore.kernel.org/git/200910201947.50423.trast@student.ethz.ch/\n[2] https://lore.kernel.org/git/20130522115042.GA20649@inner.h.apk.li/\n[3] https://lore.kernel.org/git/1377988690-23460-1-git-send-email-felipe.contreras@gmail.com/\n[4] https://lore.kernel.org/git/4ay6w9i74cygt6ii1b0db7wg.1398433713382@email.android.com/\n[5] https://lore.kernel.org/git/5363BB9F.40102@xiplink.com/\n[6] https://lore.kernel.org/git/20201204061623.1170745-1-felipe.contreras@gmail.com/\n[7] https://lore.kernel.org/git/CA+55aFzxsNxgKD1uGZQCiib+=+wCMSa0=B+Ye3Zi-u6kpz8Vrg@mail.gmail.com/\n[8] https://lore.kernel.org/git/xmqqppjyhnom.fsf@gitster.dls.corp.google.com/\n[9] https://lore.kernel.org/git/5228A14B.3000804@bbn.com/\n[10] https://lore.kernel.org/git/20130628174252.GF2232@serenity.lan/\n[11] https://lore.kernel.org/git/5366db742d494_18f9e4b308aa@nysa.notmuch/\n[12] https://github.com/felipec/git/commit/61e05c9798b3c74bab8b2d47ede4c12e8e305345\n[13] https://lore.kernel.org/git/vpqbo3za8r9.fsf@anie.imag.fr/\n\nFelipe Contreras (35):\n  merge: improve fatal fast-forward message\n  merge: split cmd_merge()\n  fast-forward: add new builtin\n  doc: fast-forward: explain what it is\n  fast-forward: add advice for novices\n  fast-forward: make the advise configurable\n  fast-forward: add help about merge vs. rebase\n  update: new built-in\n  update: add options and usage skeleton\n  update: add --ff option\n  update: add --merge mode\n  commit: support for multiple MERGE_MODE\n  merge: add --reverse-parents option\n  update: reverse the order of parents\n  update: fake a reverse order of parents in message\n  update: add --rebase mode\n  update: add mode configuation\n  update: add per-branch mode configuration\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 configuration fetches\n  pull: show warning with --ff options\n  pull: add pull.mode\n  pull: add per-branch mode configuration\n  pull: add pull.mode=fast-forward\n  pull: reorganize mode conditionals\n  pull: add diverging advice on fast-forward mode\n  pull: improve --rebase and pull.rebase interaction\n  pull: improve default warning\n  pull: advice of future changes\n  FUTURE: pull: enable ff-only mode by default\n  !fixup FUTURE: pull: enable ff-only mode by default\n\n .gitignore                             |   2 +\n Documentation/config.txt               |   4 +\n Documentation/config/advice.txt        |   2 +\n Documentation/config/branch.txt        |  10 ++\n Documentation/config/pull.txt          |   6 +\n Documentation/config/update.txt        |   5 +\n Documentation/git-fast-forward.txt     | 104 +++++++++++++++++\n Documentation/git-pull.txt             |  10 +-\n Documentation/git-update.txt           |  47 ++++++++\n Documentation/merge-options.txt        |   4 +\n Makefile                               |   2 +\n advice.c                               |  15 +++\n advice.h                               |   2 +\n builtin.h                              |   2 +\n builtin/commit.c                       |   7 +-\n builtin/merge.c                        |  59 +++++++---\n builtin/pull.c                         | 156 +++++++++++++++++--------\n builtin/update.c                       | 153 ++++++++++++++++++++++++\n contrib/completion/git-completion.bash |  22 ++++\n fmt-merge-msg.c                        |  21 +++-\n fmt-merge-msg.h                        |   3 +-\n git.c                                  |   2 +\n rebase.c                               |  12 ++\n rebase.h                               |  13 ++-\n t/t4013-diff-various.sh                |   2 +-\n t/t5520-pull.sh                        | 123 +++++++++++++++++--\n t/t5521-pull-options.sh                |   4 +-\n t/t5524-pull-msg.sh                    |   4 +-\n t/t5553-set-upstream.sh                |  14 +--\n t/t5563-update.sh                      |  87 ++++++++++++++\n t/t5604-clone-reference.sh             |   4 +-\n t/t6402-merge-rename.sh                |  16 +--\n t/t6409-merge-subtree.sh               |   6 +-\n t/t6417-merge-ours-theirs.sh           |  10 +-\n t/t7600-merge.sh                       |  47 ++++++++\n t/t7601-merge-pull-config.sh           | 116 ------------------\n t/t7603-merge-reduce-heads.sh          |   2 +-\n 37 files changed, 870 insertions(+), 228 deletions(-)\n create mode 100644 Documentation/config/update.txt\n create mode 100644 Documentation/git-fast-forward.txt\n create mode 100644 Documentation/git-update.txt\n create mode 100644 builtin/update.c\n create mode 100755 t/t5563-update.sh\n\n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429201","messageId":"20210705123209.1808663-2-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 01/35] merge: improve fatal fast-forward message","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:35Z","receivedAt":"2021-07-05T12:32:16Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n builtin/merge.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex a8a843b1f5..05e631229d 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(_(\"unable to fast-forward\"));\n \n \tif (autostash)\n \t\tcreate_autostash(the_repository,\n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429202","messageId":"20210705123209.1808663-3-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 02/35] merge: split cmd_merge()","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:36Z","receivedAt":"2021-07-05T12:32:17Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"We want to re-use most of cmd_merge() for a new command.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n builtin/merge.c | 26 ++++++++++++++------------\n 1 file changed, 14 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 05e631229d..9c944f5f0f 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1259,7 +1259,8 @@ static int merging_a_throwaway_tag(struct commit *commit)\n \treturn is_throwaway_tag;\n }\n \n-int cmd_merge(int argc, const char **argv, const char *prefix)\n+static int merge_common(int argc, const char **argv, const char *prefix,\n+\tconst struct option *options, const char * const usage[])\n {\n \tstruct object_id result_tree, stash, head_oid;\n \tstruct commit *head_commit;\n@@ -1273,7 +1274,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \tint orig_argc = argc;\n \n \tif (argc == 2 && !strcmp(argv[1], \"-h\"))\n-\t\tusage_with_options(builtin_merge_usage, builtin_merge_options);\n+\t\tusage_with_options(usage, options);\n \n \t/*\n \t * Check if we are _not_ on a detached HEAD, i.e. if there is a\n@@ -1299,8 +1300,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \n \tif (branch_mergeoptions)\n \t\tparse_branch_merge_options(branch_mergeoptions);\n-\targc = parse_options(argc, argv, prefix, builtin_merge_options,\n-\t\t\tbuiltin_merge_usage, 0);\n+\targc = parse_options(argc, argv, prefix, options, usage, 0);\n \tif (shortlog_len < 0)\n \t\tshortlog_len = (merge_log_config > 0) ? merge_log_config : 0;\n \n@@ -1314,7 +1314,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \n \t\tif (orig_argc != 2)\n \t\t\tusage_msg_opt(_(\"--abort expects no arguments\"),\n-\t\t\t      builtin_merge_usage, builtin_merge_options);\n+\t\t\t\tusage, options);\n \n \t\tif (!file_exists(git_path_merge_head(the_repository)))\n \t\t\tdie(_(\"There is no merge to abort (MERGE_HEAD missing).\"));\n@@ -1336,8 +1336,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \tif (quit_current_merge) {\n \t\tif (orig_argc != 2)\n \t\t\tusage_msg_opt(_(\"--quit expects no arguments\"),\n-\t\t\t\t      builtin_merge_usage,\n-\t\t\t\t      builtin_merge_options);\n+\t\t\t\tusage, options);\n \n \t\tremove_merge_branch_state(the_repository);\n \t\tgoto done;\n@@ -1349,7 +1348,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \n \t\tif (orig_argc != 2)\n \t\t\tusage_msg_opt(_(\"--continue expects no arguments\"),\n-\t\t\t      builtin_merge_usage, builtin_merge_options);\n+\t\t\t\tusage, options);\n \n \t\tif (!file_exists(git_path_merge_head(the_repository)))\n \t\t\tdie(_(\"There is no merge in progress (MERGE_HEAD missing).\"));\n@@ -1416,8 +1415,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t}\n \n \tif (!argc)\n-\t\tusage_with_options(builtin_merge_usage,\n-\t\t\tbuiltin_merge_options);\n+\t\tusage_with_options(usage, options);\n \n \tif (!head_commit) {\n \t\t/*\n@@ -1458,8 +1456,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t\t\t      argc, argv, &merge_msg);\n \n \tif (!head_commit || !argc)\n-\t\tusage_with_options(builtin_merge_usage,\n-\t\t\tbuiltin_merge_options);\n+\t\tusage_with_options(usage, options);\n \n \tif (verify_signatures) {\n \t\tfor (p = remoteheads; p; p = p->next) {\n@@ -1738,3 +1735,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \tfree(branch_to_free);\n \treturn ret;\n }\n+\n+int cmd_merge(int argc, const char **argv, const char *prefix)\n+{\n+\treturn merge_common(argc, argv, prefix, builtin_merge_options, builtin_merge_usage);\n+}\n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429203","messageId":"20210705123209.1808663-4-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 03/35] fast-forward: add new builtin","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:37Z","receivedAt":"2021-07-05T12:32:18Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"This is one of the most common git operations, it makes sense it has its\nown built-in.\n\nThis is basically the same as `git merge --ff-only` (for now).\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n .gitignore                             |  1 +\n Documentation/git-fast-forward.txt     | 26 ++++++++++++++++++++++++++\n Makefile                               |  1 +\n builtin.h                              |  1 +\n builtin/merge.c                        | 15 +++++++++++++++\n contrib/completion/git-completion.bash | 10 ++++++++++\n git.c                                  |  1 +\n t/t7600-merge.sh                       | 21 +++++++++++++++++++++\n 8 files changed, 76 insertions(+)\n create mode 100644 Documentation/git-fast-forward.txt\n\ndiff --git a/.gitignore b/.gitignore\nindex 311841f9be..45703399b0 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -62,6 +62,7 @@\n /git-describe\n /git-env--helper\n /git-fast-export\n+/git-fast-forward\n /git-fast-import\n /git-fetch\n /git-fetch-pack\ndiff --git a/Documentation/git-fast-forward.txt b/Documentation/git-fast-forward.txt\nnew file mode 100644\nindex 0000000000..d457022629\n--- /dev/null\n+++ b/Documentation/git-fast-forward.txt\n@@ -0,0 +1,26 @@\n+git-fast-forward(1)\n+===================\n+\n+NAME\n+----\n+git-fast-forward - Advance the branch pointer\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git fast-forward' [<commit>]\n+\n+DESCRIPTION\n+-----------\n+Incorporates changes into the current branch. By default the upstream branch is\n+used, but a different commit can be specified in the arguments.\n+\n+THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOUR MAY CHANGE.\n+\n+SEE ALSO\n+--------\n+linkgit:git-merge[1]\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex c3565fc0f8..cf42162a07 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -772,6 +772,7 @@ BUILT_INS += $(patsubst builtin/%.o,git-%$X,$(BUILTIN_OBJS))\n \n BUILT_INS += git-cherry$X\n BUILT_INS += git-cherry-pick$X\n+BUILT_INS += git-fast-forward$X\n BUILT_INS += git-format-patch$X\n BUILT_INS += git-fsck-objects$X\n BUILT_INS += git-init$X\ndiff --git a/builtin.h b/builtin.h\nindex 16ecd5586f..601e438c9b 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -151,6 +151,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix);\n int cmd_difftool(int argc, const char **argv, const char *prefix);\n int cmd_env__helper(int argc, const char **argv, const char *prefix);\n int cmd_fast_export(int argc, const char **argv, const char *prefix);\n+int cmd_fast_forward(int argc, const char **argv, const char *prefix);\n int cmd_fast_import(int argc, const char **argv, const char *prefix);\n int cmd_fetch(int argc, const char **argv, const char *prefix);\n int cmd_fetch_pack(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 9c944f5f0f..e396943d37 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -61,6 +61,11 @@ static const char * const builtin_merge_usage[] = {\n \tNULL\n };\n \n+static const char * const builtin_ff_usage[] = {\n+\tN_(\"git fast-forward [<commit>]\"),\n+\tNULL\n+};\n+\n static int show_diffstat = 1, shortlog_len = -1, squash;\n static int option_commit = -1;\n static int option_edit = -1;\n@@ -304,6 +309,10 @@ static struct option builtin_merge_options[] = {\n \tOPT_END()\n };\n \n+static struct option builtin_ff_options[] = {\n+\tOPT_END()\n+};\n+\n static int save_state(struct object_id *stash)\n {\n \tint len;\n@@ -1740,3 +1749,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n {\n \treturn merge_common(argc, argv, prefix, builtin_merge_options, builtin_merge_usage);\n }\n+\n+int cmd_fast_forward(int argc, const char **argv, const char *prefix)\n+{\n+\tfast_forward = FF_ONLY;\n+\treturn merge_common(argc, argv, prefix, builtin_ff_options, builtin_ff_usage);\n+}\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex b50c5d0ea3..cfaee3aaeb 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1786,6 +1786,16 @@ _git_difftool ()\n \t__git_complete_revlist_file\n }\n \n+_git_fast_forward ()\n+{\n+\tcase \"$cur\" in\n+\t--*)\n+\t\t__gitcomp_builtin fast-forward\n+\t\treturn\n+\tesac\n+\t__git_complete_refs\n+}\n+\n __git_fetch_recurse_submodules=\"yes on-demand no\"\n \n _git_fetch ()\ndiff --git a/git.c b/git.c\nindex 18bed9a996..6ab1fb9251 100644\n--- a/git.c\n+++ b/git.c\n@@ -524,6 +524,7 @@ static struct cmd_struct commands[] = {\n \t{ \"difftool\", cmd_difftool, RUN_SETUP_GENTLY },\n \t{ \"env--helper\", cmd_env__helper },\n \t{ \"fast-export\", cmd_fast_export, RUN_SETUP },\n+\t{ \"fast-forward\", cmd_fast_forward, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"fast-import\", cmd_fast_import, RUN_SETUP | NO_PARSEOPT },\n \t{ \"fetch\", cmd_fetch, RUN_SETUP },\n \t{ \"fetch-pack\", cmd_fetch_pack, RUN_SETUP | NO_PARSEOPT },\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex 1cbc9715a8..10f8956665 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -205,6 +205,16 @@ test_expect_success 'merge c0 with c1 with --ff-only' '\n \n test_debug 'git log --graph --decorate --oneline --all'\n \n+test_expect_success 'fast-forward c0 with c1' '\n+\tgit reset --hard c0 &&\n+\tgit fast-forward c1 &&\n+\tgit fast-forward HEAD c0 c1 &&\n+\tverify_merge file result.1 &&\n+\tverify_head \"$c1\"\n+'\n+\n+test_debug 'git log --graph --decorate --oneline --all'\n+\n test_expect_success 'merge from unborn branch' '\n \tgit checkout -f main &&\n \ttest_might_fail git branch -D kid &&\n@@ -322,6 +332,17 @@ test_expect_success 'merges with --ff-only' '\n \tverify_head $c3\n '\n \n+test_expect_success 'fast-forward' '\n+\tgit reset --hard c1 &&\n+\ttest_tick &&\n+\ttest_must_fail git fast-forward c2 &&\n+\ttest_must_fail git fast-forward c3 &&\n+\ttest_must_fail git fast-forward c2 c3 &&\n+\tgit reset --hard c0 &&\n+\tgit merge c3 &&\n+\tverify_head $c3\n+'\n+\n test_expect_success 'merges with merge.ff=only' '\n \tgit reset --hard c1 &&\n \ttest_tick &&\n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429204","messageId":"20210705123209.1808663-5-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 04/35] doc: fast-forward: explain what it is","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:38Z","receivedAt":"2021-07-05T12:32:20Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n Documentation/git-fast-forward.txt | 32 +++++++++++++++++++++++++++++-\n 1 file changed, 31 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-fast-forward.txt b/Documentation/git-fast-forward.txt\nindex d457022629..b7322c437e 100644\n--- a/Documentation/git-fast-forward.txt\n+++ b/Documentation/git-fast-forward.txt\n@@ -15,11 +15,41 @@ DESCRIPTION\n Incorporates changes into the current branch. By default the upstream branch is\n used, but a different commit can be specified in the arguments.\n \n+Assume the following history exists and the current branch is\n+`master`:\n+\n+------------\n+    D---C---B---A origin/master\n+\t^\n+\t|\n+      master\n+------------\n+\n+Then `git fast-forward` will advance the local `master` to `origin/master`:\n+\n+------------\n+    D---C---B---A master, origin/master\n+------------\n+\n+This operation is not always possible; if you made changes and the branches\n+have diverged:\n+\n+------------\n+    D---C---B---A origin/master\n+\t \\\n+\t  X---Y master\n+------------\n+\n+then the fast-forward command will fail.\n+\n+In those cases you need to either `git merge`, or `git rebase` in order to\n+synchronize the two branches.\n+\n THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOUR MAY CHANGE.\n \n SEE ALSO\n --------\n-linkgit:git-merge[1]\n+linkgit:git-merge[1], linkgit:git-rebase[1]\n \n GIT\n ---\n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429205","messageId":"20210705123209.1808663-6-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 05/35] fast-forward: add advice for novices","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:39Z","receivedAt":"2021-07-05T12:32:21Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"It doesn't hurt showing it on `git merge --ff-only` too.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n advice.c        | 11 +++++++++++\n advice.h        |  1 +\n builtin/merge.c |  4 +++-\n 3 files changed, 15 insertions(+), 1 deletion(-)\n\ndiff --git a/advice.c b/advice.c\nindex 0b9c89c48a..8f068c8be9 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -326,3 +326,14 @@ void detach_advice(const char *new_name)\n \n \tfprintf(stderr, fmt, new_name);\n }\n+\n+void diverging_advice(void)\n+{\n+\tadvise(_(\"Diverging branches can't be fast-forwarded, you need to either:\\n\"\n+\t\t\"\\n\"\n+\t\t\"\\tgit merge\\n\"\n+\t\t\"\\n\"\n+\t\t\"or:\\n\"\n+\t\t\"\\n\"\n+\t\t\"\\tgit rebase\\n\"));\n+}\ndiff --git a/advice.h b/advice.h\nindex bd26c385d0..6ce967c962 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -97,5 +97,6 @@ void NORETURN die_resolve_conflict(const char *me);\n void NORETURN die_conclude_merge(void);\n void advise_on_updating_sparse_paths(struct string_list *pathspec_list);\n void detach_advice(const char *new_name);\n+void diverging_advice(void);\n \n #endif /* ADVICE_H */\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex e396943d37..1836f98f82 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1625,8 +1625,10 @@ static int merge_common(int argc, const char **argv, const char *prefix,\n \t\t}\n \t}\n \n-\tif (fast_forward == FF_ONLY)\n+\tif (fast_forward == FF_ONLY) {\n+\t\tdiverging_advice();\n \t\tdie(_(\"unable to fast-forward\"));\n+\t}\n \n \tif (autostash)\n \t\tcreate_autostash(the_repository,\n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429206","messageId":"20210705123209.1808663-7-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 06/35] fast-forward: make the advise configurable","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:40Z","receivedAt":"2021-07-05T12:32:23Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"With advice.diverging.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n Documentation/config/advice.txt | 2 ++\n advice.c                        | 4 +++-\n advice.h                        | 1 +\n 3 files changed, 6 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/config/advice.txt b/Documentation/config/advice.txt\nindex 8b2849ff7b..dae85310dc 100644\n--- a/Documentation/config/advice.txt\n+++ b/Documentation/config/advice.txt\n@@ -123,4 +123,6 @@ advice.*::\n \t\tAdvice shown when either linkgit:git-add[1] or linkgit:git-rm[1]\n \t\tis asked to update index entries outside the current sparse\n \t\tcheckout.\n+\tdiverging::\n+\t\tAdvice shown when a fast-forward is not possible.\n --\ndiff --git a/advice.c b/advice.c\nindex 8f068c8be9..60de7fbc4e 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -110,6 +110,7 @@ static struct {\n \t[ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] \t= { \"checkoutAmbiguousRemoteBranchName\", 1 },\n \t[ADVICE_COMMIT_BEFORE_MERGE]\t\t\t= { \"commitBeforeMerge\", 1 },\n \t[ADVICE_DETACHED_HEAD]\t\t\t\t= { \"detachedHead\", 1 },\n+\t[ADVICE_DIVERGING]\t\t\t\t= { \"diverging\", 1 },\n \t[ADVICE_FETCH_SHOW_FORCED_UPDATES]\t\t= { \"fetchShowForcedUpdates\", 1 },\n \t[ADVICE_GRAFT_FILE_DEPRECATED]\t\t\t= { \"graftFileDeprecated\", 1 },\n \t[ADVICE_IGNORED_HOOK]\t\t\t\t= { \"ignoredHook\", 1 },\n@@ -329,7 +330,8 @@ void detach_advice(const char *new_name)\n \n void diverging_advice(void)\n {\n-\tadvise(_(\"Diverging branches can't be fast-forwarded, you need to either:\\n\"\n+\tadvise_if_enabled(ADVICE_DIVERGING,\n+\t\t_(\"Diverging branches can't be fast-forwarded, you need to either:\\n\"\n \t\t\"\\n\"\n \t\t\"\\tgit merge\\n\"\n \t\t\"\\n\"\ndiff --git a/advice.h b/advice.h\nindex 6ce967c962..695f5a62bb 100644\n--- a/advice.h\n+++ b/advice.h\n@@ -49,6 +49,7 @@ extern int advice_add_empty_pathspec;\n \tADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME,\n \tADVICE_COMMIT_BEFORE_MERGE,\n \tADVICE_DETACHED_HEAD,\n+\tADVICE_DIVERGING,\n \tADVICE_FETCH_SHOW_FORCED_UPDATES,\n \tADVICE_GRAFT_FILE_DEPRECATED,\n \tADVICE_IGNORED_HOOK,\n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429207","messageId":"20210705123209.1808663-8-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 07/35] fast-forward: add help about merge vs. rebase","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:41Z","receivedAt":"2021-07-05T12:32:26Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n Documentation/git-fast-forward.txt | 48 ++++++++++++++++++++++++++++++\n advice.c                           |  4 ++-\n 2 files changed, 51 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-fast-forward.txt b/Documentation/git-fast-forward.txt\nindex b7322c437e..a73834e0f9 100644\n--- a/Documentation/git-fast-forward.txt\n+++ b/Documentation/git-fast-forward.txt\n@@ -47,6 +47,54 @@ synchronize the two branches.\n \n THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOUR MAY CHANGE.\n \n+MERGE OR REBASE\n+---------------\n+\n+The decision to whether merge or rebase depends on the situation, and the\n+project. Traditionally git has prefered merge over rebase, but that creates a\n+new commit, and that's frowned up on some projects, so you can't just simply\n+choose to merge blindly.\n+\n+------------\n+    D---C---B---A origin/master\n+\t \\\n+\t  X---Y master\n+------------\n+\n+The nature of distributed version control systems make this divergence\n+unavoidable. You must decide how to synchronize this divergence.\n+\n+If you choose to merge, the two heads (master and origin/master) will be joined\n+together in a new commit:\n+\n+------------\n+\t  origin/master\n+\t\t|\n+\t\tv\n+    D---C---B---A---M master\n+\t \\\t   /\n+\t  X---Y---+\n+------------\n+\n+This new commit is called a merge commit and has two parents (A and Y).\n+\n+Rebasing on the other hand rewrites the history:\n+\n+------------\n+\t  origin/master\n+\t\t|\n+\t\tv\n+    D---C---B---A---X'---Y' master\n+------------\n+\n+The commits that diverged (X and Y) are rewritten as if they were created on top\n+of the new base (A). This creates a linear history, which is cleaner, but some\n+people prefer to preserve the original hsitory.\n+\n+In both cases it's likely you would have to resolve conflicts, the difference is\n+that in a merge you would have to do it all at once in one commit, while with a\n+rebase you would have to do it on every rewritten commit.\n+\n SEE ALSO\n --------\n linkgit:git-merge[1], linkgit:git-rebase[1]\ndiff --git a/advice.c b/advice.c\nindex 60de7fbc4e..7f422b05d3 100644\n--- a/advice.c\n+++ b/advice.c\n@@ -337,5 +337,7 @@ void diverging_advice(void)\n \t\t\"\\n\"\n \t\t\"or:\\n\"\n \t\t\"\\n\"\n-\t\t\"\\tgit rebase\\n\"));\n+\t\t\"\\tgit rebase\\n\"\n+\t\t\"\\n\"\n+\t\t\"For more information check \\\"git help fast-forward\\\".\\n\"));\n }\n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429208","messageId":"20210705123209.1808663-9-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 08/35] update: new built-in","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:42Z","receivedAt":"2021-07-05T12:32:27Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"This is basically `git fetch` + `git fast-forward`.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n .gitignore                             |  1 +\n Documentation/config.txt               |  2 ++\n Documentation/git-update.txt           | 32 +++++++++++++++++\n Makefile                               |  1 +\n builtin.h                              |  1 +\n builtin/update.c                       | 48 ++++++++++++++++++++++++++\n contrib/completion/git-completion.bash | 12 +++++++\n git.c                                  |  1 +\n t/t5563-update.sh                      | 45 ++++++++++++++++++++++++\n 9 files changed, 143 insertions(+)\n create mode 100644 Documentation/git-update.txt\n create mode 100644 builtin/update.c\n create mode 100755 t/t5563-update.sh\n\ndiff --git a/.gitignore b/.gitignore\nindex 45703399b0..2a3bc43ef2 100644\n--- a/.gitignore\n+++ b/.gitignore\n@@ -171,6 +171,7 @@\n /git-tag\n /git-unpack-file\n /git-unpack-objects\n+/git-update\n /git-update-index\n /git-update-ref\n /git-update-server-info\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex bf82766a6a..fc4b49c0d4 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -440,6 +440,8 @@ include::config/rerere.txt[]\n \n include::config/reset.txt[]\n \n+include::config/update.txt[]\n+\n include::config/sendemail.txt[]\n \n include::config/sequencer.txt[]\ndiff --git a/Documentation/git-update.txt b/Documentation/git-update.txt\nnew file mode 100644\nindex 0000000000..54c49c5d12\n--- /dev/null\n+++ b/Documentation/git-update.txt\n@@ -0,0 +1,32 @@\n+git-update(1)\n+=============\n+\n+NAME\n+----\n+git-update - Update the current branch to the latest remote\n+\n+SYNOPSIS\n+--------\n+[verse]\n+'git update'\n+\n+DESCRIPTION\n+-----------\n+\n+Incorporates changes from a remote repository into the current branch.\n+\n+`git update` runs `git fetch` and then tries to advance the current branch to\n+the remote branch with `git fast-forward`. If you don't have any extra changes\n+the update operation is straight-forward, but if you do a further `git merge` or\n+`git rebase` will be needed.\n+\n+THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOUR MAY CHANGE.\n+\n+SEE ALSO\n+--------\n+linkgit:git-fetch[1], linkgit:git-fast-forward[1],\n+linkgit:git-merge[1], linkgit:git-rebase[1]\n+\n+GIT\n+---\n+Part of the linkgit:git[1] suite\ndiff --git a/Makefile b/Makefile\nindex cf42162a07..6450574feb 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -1161,6 +1161,7 @@ BUILTIN_OBJS += builtin/symbolic-ref.o\n BUILTIN_OBJS += builtin/tag.o\n BUILTIN_OBJS += builtin/unpack-file.o\n BUILTIN_OBJS += builtin/unpack-objects.o\n+BUILTIN_OBJS += builtin/update.o\n BUILTIN_OBJS += builtin/update-index.o\n BUILTIN_OBJS += builtin/update-ref.o\n BUILTIN_OBJS += builtin/update-server-info.o\ndiff --git a/builtin.h b/builtin.h\nindex 601e438c9b..7d18897682 100644\n--- a/builtin.h\n+++ b/builtin.h\n@@ -229,6 +229,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix);\n int cmd_tar_tree(int argc, const char **argv, const char *prefix);\n int cmd_unpack_file(int argc, const char **argv, const char *prefix);\n int cmd_unpack_objects(int argc, const char **argv, const char *prefix);\n+int cmd_update(int argc, const char **argv, const char *prefix);\n int cmd_update_index(int argc, const char **argv, const char *prefix);\n int cmd_update_ref(int argc, const char **argv, const char *prefix);\n int cmd_update_server_info(int argc, const char **argv, const char *prefix);\ndiff --git a/builtin/update.c b/builtin/update.c\nnew file mode 100644\nindex 0000000000..51e45b453d\n--- /dev/null\n+++ b/builtin/update.c\n@@ -0,0 +1,48 @@\n+/*\n+ * Copyright (C) 2021 Felipe Contreras\n+ */\n+\n+#include \"builtin.h\"\n+#include \"run-command.h\"\n+#include \"dir.h\"\n+\n+static int run_fetch(void)\n+{\n+\tstruct strvec args = STRVEC_INIT;\n+\tint ret;\n+\n+\tstrvec_pushl(&args, \"fetch\", \"--update-head-ok\", NULL);\n+\n+\tret = run_command_v_opt(args.v, RUN_GIT_CMD);\n+\tstrvec_clear(&args);\n+\treturn ret;\n+}\n+\n+static int run_fast_forward(void)\n+{\n+\tstruct strvec args = STRVEC_INIT;\n+\tint ret;\n+\n+\tstrvec_pushl(&args, \"fast-forward\", \"FETCH_HEAD\", NULL);\n+\n+\tret = run_command_v_opt(args.v, RUN_GIT_CMD);\n+\tstrvec_clear(&args);\n+\treturn ret;\n+}\n+\n+int cmd_update(int argc, const char **argv, const char *prefix)\n+{\n+\tif (!getenv(\"GIT_REFLOG_ACTION\"))\n+\t\tsetenv(\"GIT_REFLOG_ACTION\", \"update\", 0);\n+\n+\tif (repo_read_index_unmerged(the_repository))\n+\t\tdie_resolve_conflict(\"update\");\n+\n+\tif (file_exists(git_path_merge_head(the_repository)))\n+\t\tdie_conclude_merge();\n+\n+\tif (run_fetch())\n+\t\treturn 1;\n+\n+\treturn run_fast_forward();\n+}\ndiff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash\nindex cfaee3aaeb..c5214d9856 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -3257,6 +3257,18 @@ _git_tag ()\n \tesac\n }\n \n+_git_update ()\n+{\n+\tcase \"$cur\" in\n+\t--*)\n+\t\t__gitcomp_builtin update\n+\n+\t\treturn\n+\t\t;;\n+\tesac\n+\t__git_complete_remote_or_refspec\n+}\n+\n _git_whatchanged ()\n {\n \t_git_log\ndiff --git a/git.c b/git.c\nindex 6ab1fb9251..0156ea81a4 100644\n--- a/git.c\n+++ b/git.c\n@@ -610,6 +610,7 @@ static struct cmd_struct commands[] = {\n \t{ \"tag\", cmd_tag, RUN_SETUP | DELAY_PAGER_CONFIG },\n \t{ \"unpack-file\", cmd_unpack_file, RUN_SETUP | NO_PARSEOPT },\n \t{ \"unpack-objects\", cmd_unpack_objects, RUN_SETUP | NO_PARSEOPT },\n+\t{ \"update\", cmd_update, RUN_SETUP | NEED_WORK_TREE },\n \t{ \"update-index\", cmd_update_index, RUN_SETUP },\n \t{ \"update-ref\", cmd_update_ref, RUN_SETUP },\n \t{ \"update-server-info\", cmd_update_server_info, RUN_SETUP },\ndiff --git a/t/t5563-update.sh b/t/t5563-update.sh\nnew file mode 100755\nindex 0000000000..951df41ac4\n--- /dev/null\n+++ b/t/t5563-update.sh\n@@ -0,0 +1,45 @@\n+#!/bin/sh\n+\n+test_description='update'\n+\n+GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=master\n+export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\techo one > file &&\n+\tgit add file &&\n+\tgit commit -a -m one &&\n+\techo two > file &&\n+\tgit commit -a -m two\n+'\n+\n+test_expect_success 'basic update' '\n+\ttest_when_finished \"rm -rf test\" &&\n+\t(\n+\tgit clone . test &&\n+\tcd test &&\n+\tgit reset --hard @^ &&\n+\tgit update &&\n+\ttest_cmp_rev master origin/master\n+\t)\n+'\n+\n+test_expect_success 'non-fast-forward update' '\n+\ttest_when_finished \"rm -rf test\" &&\n+\t(\n+\tgit clone . test &&\n+\tcd test &&\n+\tgit checkout -b other master^ &&\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+\ttest_must_fail git update &&\n+\ttest_cmp_rev @ master\n+\t)\n+'\n+\n+test_done\n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429209","messageId":"20210705123209.1808663-10-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 09/35] update: add options and usage skeleton","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:43Z","receivedAt":"2021-07-05T12:32:28Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n builtin/update.c | 12 ++++++++++++\n 1 file changed, 12 insertions(+)\n\ndiff --git a/builtin/update.c b/builtin/update.c\nindex 51e45b453d..1a69896aa8 100644\n--- a/builtin/update.c\n+++ b/builtin/update.c\n@@ -3,9 +3,19 @@\n  */\n \n #include \"builtin.h\"\n+#include \"parse-options.h\"\n #include \"run-command.h\"\n #include \"dir.h\"\n \n+static const char * const update_usage[] = {\n+\tN_(\"git update\"),\n+\tNULL\n+};\n+\n+static struct option update_options[] = {\n+\tOPT_END()\n+};\n+\n static int run_fetch(void)\n {\n \tstruct strvec args = STRVEC_INIT;\n@@ -35,6 +45,8 @@ int cmd_update(int argc, const char **argv, const char *prefix)\n \tif (!getenv(\"GIT_REFLOG_ACTION\"))\n \t\tsetenv(\"GIT_REFLOG_ACTION\", \"update\", 0);\n \n+\targc = parse_options(argc, argv, prefix, update_options, update_usage, 0);\n+\n \tif (repo_read_index_unmerged(the_repository))\n \t\tdie_resolve_conflict(\"update\");\n \n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429210","messageId":"20210705123209.1808663-11-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 10/35] update: add --ff option","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:44Z","receivedAt":"2021-07-05T12:32:32Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"This is the default, it doesn't change anything.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n Documentation/git-update.txt |  9 ++++++++-\n builtin/update.c             | 17 +++++++++++++++--\n 2 files changed, 23 insertions(+), 3 deletions(-)\n\ndiff --git a/Documentation/git-update.txt b/Documentation/git-update.txt\nindex 54c49c5d12..df778b7ef4 100644\n--- a/Documentation/git-update.txt\n+++ b/Documentation/git-update.txt\n@@ -8,7 +8,7 @@ git-update - Update the current branch to the latest remote\n SYNOPSIS\n --------\n [verse]\n-'git update'\n+'git update' [<options>]\n \n DESCRIPTION\n -----------\n@@ -22,6 +22,13 @@ the update operation is straight-forward, but if you do a further `git merge` or\n \n THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOUR MAY CHANGE.\n \n+OPTIONS\n+-------\n+\n+-f::\n+--ff::\n+\tForces a fast-forward.\n+\n SEE ALSO\n --------\n linkgit:git-fetch[1], linkgit:git-fast-forward[1],\ndiff --git a/builtin/update.c b/builtin/update.c\nindex 1a69896aa8..34681fe21a 100644\n--- a/builtin/update.c\n+++ b/builtin/update.c\n@@ -7,12 +7,22 @@\n #include \"run-command.h\"\n #include \"dir.h\"\n \n+enum update_mode_type {\n+\tUPDATE_MODE_FAST_FORWARD = 0\n+};\n+\n+static enum update_mode_type mode = UPDATE_MODE_FAST_FORWARD;\n+\n static const char * const update_usage[] = {\n-\tN_(\"git update\"),\n+\tN_(\"git update [<options>]\"),\n \tNULL\n };\n \n static struct option update_options[] = {\n+\tOPT_SET_INT_F('f', \"ff\", &mode,\n+\t\tN_(\"incorporate changes by fast-forwarding\"),\n+\t\tUPDATE_MODE_FAST_FORWARD, PARSE_OPT_NONEG),\n+\n \tOPT_END()\n };\n \n@@ -56,5 +66,8 @@ int cmd_update(int argc, const char **argv, const char *prefix)\n \tif (run_fetch())\n \t\treturn 1;\n \n-\treturn run_fast_forward();\n+\tif (mode == UPDATE_MODE_FAST_FORWARD)\n+\t\treturn run_fast_forward();\n+\n+\treturn 1;\n }\n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429211","messageId":"20210705123209.1808663-12-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 11/35] update: add --merge mode","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:45Z","receivedAt":"2021-07-05T12:32:34Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n Documentation/git-update.txt |  4 ++++\n builtin/update.c             | 20 +++++++++++++++++++-\n t/t5563-update.sh            | 15 +++++++++++++++\n 3 files changed, 38 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-update.txt b/Documentation/git-update.txt\nindex df778b7ef4..075653c6fd 100644\n--- a/Documentation/git-update.txt\n+++ b/Documentation/git-update.txt\n@@ -29,6 +29,10 @@ OPTIONS\n --ff::\n \tForces a fast-forward.\n \n+-m::\n+--merge::\n+\tForces a merge.\n+\n SEE ALSO\n --------\n linkgit:git-fetch[1], linkgit:git-fast-forward[1],\ndiff --git a/builtin/update.c b/builtin/update.c\nindex 34681fe21a..5946b383f5 100644\n--- a/builtin/update.c\n+++ b/builtin/update.c\n@@ -8,7 +8,8 @@\n #include \"dir.h\"\n \n enum update_mode_type {\n-\tUPDATE_MODE_FAST_FORWARD = 0\n+\tUPDATE_MODE_FAST_FORWARD = 0,\n+\tUPDATE_MODE_MERGE\n };\n \n static enum update_mode_type mode = UPDATE_MODE_FAST_FORWARD;\n@@ -22,6 +23,9 @@ static struct option update_options[] = {\n \tOPT_SET_INT_F('f', \"ff\", &mode,\n \t\tN_(\"incorporate changes by fast-forwarding\"),\n \t\tUPDATE_MODE_FAST_FORWARD, PARSE_OPT_NONEG),\n+\tOPT_SET_INT_F('m', \"merge\", &mode,\n+\t\tN_(\"incorporate changes by merging\"),\n+\t\tUPDATE_MODE_MERGE, PARSE_OPT_NONEG),\n \n \tOPT_END()\n };\n@@ -50,6 +54,18 @@ static int run_fast_forward(void)\n \treturn ret;\n }\n \n+static int run_merge(void)\n+{\n+\tint ret;\n+\tstruct strvec args = STRVEC_INIT;\n+\n+\tstrvec_pushl(&args, \"merge\", \"FETCH_HEAD\", NULL);\n+\n+\tret = run_command_v_opt(args.v, RUN_GIT_CMD);\n+\tstrvec_clear(&args);\n+\treturn ret;\n+}\n+\n int cmd_update(int argc, const char **argv, const char *prefix)\n {\n \tif (!getenv(\"GIT_REFLOG_ACTION\"))\n@@ -68,6 +84,8 @@ int cmd_update(int argc, const char **argv, const char *prefix)\n \n \tif (mode == UPDATE_MODE_FAST_FORWARD)\n \t\treturn run_fast_forward();\n+\tif (mode == UPDATE_MODE_MERGE)\n+\t\treturn run_merge();\n \n \treturn 1;\n }\ndiff --git a/t/t5563-update.sh b/t/t5563-update.sh\nindex 951df41ac4..833a5285da 100755\n--- a/t/t5563-update.sh\n+++ b/t/t5563-update.sh\n@@ -42,4 +42,19 @@ test_expect_success 'non-fast-forward update' '\n \t)\n '\n \n+test_expect_success 'git update non-fast-forward with merge' '\n+\ttest_when_finished \"rm -rf test\" &&\n+\t(\n+\tgit clone . test &&\n+\tcd test &&\n+\tgit checkout -b other master^ &&\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+\tgit update --merge\n+\t)\n+'\n+\n test_done\n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429212","messageId":"20210705123209.1808663-13-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 12/35] commit: support for multiple MERGE_MODE","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:46Z","receivedAt":"2021-07-05T12:32:35Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n builtin/commit.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex 190d215d43..f9dd155566 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1742,7 +1742,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\tif (!stat(git_path_merge_mode(the_repository), &statbuf)) {\n \t\t\tif (strbuf_read_file(&sb, git_path_merge_mode(the_repository), 0) < 0)\n \t\t\t\tdie_errno(_(\"could not read MERGE_MODE\"));\n-\t\t\tif (!strcmp(sb.buf, \"no-ff\"))\n+\t\t\tif (strstr(sb.buf, \"no-ff\"))\n \t\t\t\tallow_fast_forward = 0;\n \t\t}\n \t\tif (allow_fast_forward)\n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429213","messageId":"20210705123209.1808663-14-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 13/35] merge: add --reverse-parents option","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:47Z","receivedAt":"2021-07-05T12:32:36Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"It would be useful to make `git update` reverse parents as desired.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n Documentation/merge-options.txt |  4 ++++\n builtin/commit.c                |  5 +++++\n builtin/merge.c                 | 11 ++++++++++-\n t/t7600-merge.sh                | 26 ++++++++++++++++++++++++++\n 4 files changed, 45 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt\nindex eb0aabd396..8917ed97b2 100644\n--- a/Documentation/merge-options.txt\n+++ b/Documentation/merge-options.txt\n@@ -149,6 +149,10 @@ ifndef::git-pull[]\n \tNote that not all merge strategies may support progress\n \treporting.\n \n+--reverse-parents::\n+--no-reverse-parents::\n+\tReverse the order of parents in the merge commit.\n+\n endif::git-pull[]\n \n --autostash::\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex f9dd155566..931738eeaf 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1724,6 +1724,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\tFILE *fp;\n \t\tint allow_fast_forward = 1;\n \t\tstruct commit_list **pptr = &parents;\n+\t\tint reverse_parents = 0;\n \n \t\tif (!reflog_msg)\n \t\t\treflog_msg = \"commit (merge)\";\n@@ -1744,9 +1745,13 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\t\t\tdie_errno(_(\"could not read MERGE_MODE\"));\n \t\t\tif (strstr(sb.buf, \"no-ff\"))\n \t\t\t\tallow_fast_forward = 0;\n+\t\t\tif (strstr(sb.buf, \"reverse\"))\n+\t\t\t\treverse_parents = 1;\n \t\t}\n \t\tif (allow_fast_forward)\n \t\t\treduce_heads_replace(&parents);\n+\t\tif (reverse_parents)\n+\t\t\tparents = reverse_commit_list(parents);\n \t} else {\n \t\tif (!reflog_msg)\n \t\t\treflog_msg = is_from_cherry_pick(whence)\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 1836f98f82..b9c6c43d8f 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -91,6 +91,7 @@ static int signoff;\n static const char *sign_commit;\n static int autostash;\n static int no_verify;\n+static int reverse_parents;\n \n static struct strategy all_strategy[] = {\n \t{ \"recursive\",  DEFAULT_TWOHEAD | NO_TRIVIAL },\n@@ -306,6 +307,8 @@ static struct option builtin_merge_options[] = {\n \tOPT_BOOL(0, \"overwrite-ignore\", &overwrite_ignore, N_(\"update ignored files (default)\")),\n \tOPT_BOOL(0, \"signoff\", &signoff, N_(\"add a Signed-off-by trailer\")),\n \tOPT_BOOL(0, \"no-verify\", &no_verify, N_(\"bypass pre-merge-commit and commit-msg hooks\")),\n+\tOPT_BOOL(0, \"reverse-parents\", &reverse_parents,\n+\t\tN_(\"reverse the order of parents\")),\n \tOPT_END()\n };\n \n@@ -913,6 +916,8 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads)\n \tpptr = commit_list_append(head, pptr);\n \tpptr = commit_list_append(remoteheads->item, pptr);\n \tprepare_to_commit(remoteheads);\n+\tif (reverse_parents)\n+\t\tparents = reverse_commit_list(parents);\n \tif (commit_tree(merge_msg.buf, merge_msg.len, &result_tree, parents,\n \t\t\t&result_commit, NULL, sign_commit))\n \t\tdie(_(\"failed to write commit object\"));\n@@ -937,6 +942,8 @@ static int finish_automerge(struct commit *head,\n \tparents = remoteheads;\n \tif (!head_subsumed || fast_forward == FF_NO)\n \t\tcommit_list_insert(head, &parents);\n+\tif (reverse_parents)\n+\t\tparents = reverse_commit_list(parents);\n \tprepare_to_commit(remoteheads);\n \tif (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents,\n \t\t\t&result_commit, NULL, sign_commit))\n@@ -1049,7 +1056,9 @@ static void write_merge_heads(struct commit_list *remoteheads)\n \n \tstrbuf_reset(&buf);\n \tif (fast_forward == FF_NO)\n-\t\tstrbuf_addstr(&buf, \"no-ff\");\n+\t\tstrbuf_addstr(&buf, \"no-ff \");\n+\tif (reverse_parents)\n+\t\tstrbuf_addstr(&buf, \"reverse \");\n \twrite_file_buf(git_path_merge_mode(the_repository), buf.buf, buf.len);\n \tstrbuf_release(&buf);\n }\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex 10f8956665..660d13dd51 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -563,6 +563,16 @@ test_expect_success 'merge c0 with c1 (no-ff)' '\n \n test_debug 'git log --graph --decorate --oneline --all'\n \n+test_expect_success 'merge c0 with c1 (--reverse-parents)' '\n+\tgit reset --hard c0 &&\n+\ttest_tick &&\n+\tgit merge --no-ff --reverse-parents c1 &&\n+\tverify_merge file result.1 &&\n+\tverify_parents $c1 $c0\n+'\n+\n+test_debug 'git log --graph --decorate --oneline --all'\n+\n test_expect_success 'merge c0 with c1 (merge.ff=false)' '\n \tgit reset --hard c0 &&\n \ttest_config merge.ff \"false\" &&\n@@ -940,6 +950,22 @@ test_expect_success 'merge annotated/signed tag w/ tracking' '\n \t)\n '\n \n+test_expect_success 'merge --reverse-parents --no-commit && commit' '\n+\tgit reset --hard c0 &&\n+\tgit merge --no-ff --reverse-parents --no-commit c1 &&\n+\tEDITOR=: git commit &&\n+\tverify_parents $c1 $c0\n+'\n+\n+test_debug 'git log --graph --decorate --oneline --all'\n+\n+test_expect_success 'amending reverse merge commit' '\n+\tEDITOR=: git commit --amend &&\n+\tverify_parents $c1 $c0\n+'\n+\n+test_debug 'git log --graph --decorate --oneline --all'\n+\n test_expect_success GPG 'merge --ff-only tag' '\n \tgit reset --hard c0 &&\n \tgit commit --allow-empty -m \"A newer commit\" &&\n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429214","messageId":"20210705123209.1808663-15-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 14/35] update: reverse the order of parents","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:48Z","receivedAt":"2021-07-05T12:32:40Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"The order of the parents in git pull is wrong, many big threads [1] [2]\nhave been started because of that.\n\nThere's no need for git update to repeat that mistake.\n\n[1] https://lore.kernel.org/git/20130522115042.GA20649@inner.h.apk.li/\n[2] https://lore.kernel.org/git/4ay6w9i74cygt6ii1b0db7wg.1398433713382@email.android.com/\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n builtin/update.c  | 2 +-\n t/t5563-update.sh | 4 +++-\n 2 files changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/update.c b/builtin/update.c\nindex 5946b383f5..94e83532a8 100644\n--- a/builtin/update.c\n+++ b/builtin/update.c\n@@ -59,7 +59,7 @@ static int run_merge(void)\n \tint ret;\n \tstruct strvec args = STRVEC_INIT;\n \n-\tstrvec_pushl(&args, \"merge\", \"FETCH_HEAD\", NULL);\n+\tstrvec_pushl(&args, \"merge\", \"--reverse-parents\", \"FETCH_HEAD\", NULL);\n \n \tret = run_command_v_opt(args.v, RUN_GIT_CMD);\n \tstrvec_clear(&args);\ndiff --git a/t/t5563-update.sh b/t/t5563-update.sh\nindex 833a5285da..72d6a357a1 100755\n--- a/t/t5563-update.sh\n+++ b/t/t5563-update.sh\n@@ -53,7 +53,9 @@ test_expect_success 'git update non-fast-forward with merge' '\n \tgit commit -m new &&\n \tgit checkout -b test -t other &&\n \tgit reset --hard master &&\n-\tgit update --merge\n+\tgit update --merge &&\n+\ttest_cmp_rev @^2 master &&\n+\ttest_cmp_rev @^1 other\n \t)\n '\n \n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429215","messageId":"20210705123209.1808663-16-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 15/35] update: fake a reverse order of parents in message","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:49Z","receivedAt":"2021-07-05T12:32:41Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n builtin/merge.c   |  1 +\n fmt-merge-msg.c   | 21 +++++++++++++++++++--\n fmt-merge-msg.h   |  3 ++-\n t/t5563-update.sh | 10 +++++++++-\n 4 files changed, 31 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex b9c6c43d8f..cb476958ad 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1136,6 +1136,7 @@ static void prepare_merge_message(struct strbuf *merge_names, struct strbuf *mer\n \topts.add_title = !have_message;\n \topts.shortlog_len = shortlog_len;\n \topts.credit_people = (0 < option_edit);\n+\topts.reverse_parents = reverse_parents;\n \n \tfmt_merge_msg(merge_names, merge_msg, &opts);\n \tif (merge_msg->len)\ndiff --git a/fmt-merge-msg.c b/fmt-merge-msg.c\nindex 0f66818e0f..1f840711cc 100644\n--- a/fmt-merge-msg.c\n+++ b/fmt-merge-msg.c\n@@ -432,6 +432,19 @@ static int dest_suppressed(const char *dest_branch)\n \treturn 0;\n }\n \n+static void fmt_update_msg_title(struct strbuf *out, const char *current_branch)\n+{\n+\tstruct src_data *src_data;\n+\tstrbuf_addf(out, \"Merge branch '%s'\", current_branch);\n+\tsrc_data = srcs.items[0].util;\n+\tif (src_data->branch.nr) {\n+\t\tconst char *branch_name = src_data->branch.items[0].string;\n+\t\tif (!dest_suppressed(branch_name))\n+\t\t\tstrbuf_addf(out, \" into %s\", branch_name);\n+\t}\n+\tstrbuf_addch(out, '\\n');\n+}\n+\n static void fmt_merge_msg_title(struct strbuf *out,\n \t\t\t\tconst char *current_branch)\n {\n@@ -665,8 +678,12 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,\n \t\t\tdie(\"error in line %d: %.*s\", i, len, p);\n \t}\n \n-\tif (opts->add_title && srcs.nr)\n-\t\tfmt_merge_msg_title(out, current_branch);\n+\tif (opts->add_title && srcs.nr) {\n+\t\tif (opts->reverse_parents)\n+\t\t\tfmt_update_msg_title(out, current_branch);\n+\t\telse\n+\t\t\tfmt_merge_msg_title(out, current_branch);\n+\t}\n \n \tif (origins.nr)\n \t\tfmt_merge_msg_sigs(out);\ndiff --git a/fmt-merge-msg.h b/fmt-merge-msg.h\nindex f2ab0e0085..fbdb1c905e 100644\n--- a/fmt-merge-msg.h\n+++ b/fmt-merge-msg.h\n@@ -7,7 +7,8 @@\n \n struct fmt_merge_msg_opts {\n \tunsigned add_title:1,\n-\t\tcredit_people:1;\n+\t\tcredit_people:1,\n+\t\treverse_parents:1;\n \tint shortlog_len;\n };\n \ndiff --git a/t/t5563-update.sh b/t/t5563-update.sh\nindex 72d6a357a1..aabbf5a965 100755\n--- a/t/t5563-update.sh\n+++ b/t/t5563-update.sh\n@@ -7,6 +7,13 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n . ./test-lib.sh\n \n+check_msg () {\n+\ttest \"$2\" != \"master\" && into=\" into '$2'\"\n+\techo \"Merge branch '$1'${into}\" > expected\n+\tgit log -1 --format=%s > actual &&\n+\ttest_cmp expected actual\n+}\n+\n test_expect_success 'setup' '\n \techo one > file &&\n \tgit add file &&\n@@ -55,7 +62,8 @@ test_expect_success 'git update non-fast-forward with merge' '\n \tgit reset --hard master &&\n \tgit update --merge &&\n \ttest_cmp_rev @^2 master &&\n-\ttest_cmp_rev @^1 other\n+\ttest_cmp_rev @^1 other &&\n+\tcheck_msg test other\n \t)\n '\n \n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429216","messageId":"20210705123209.1808663-17-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 16/35] update: add --rebase mode","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:50Z","receivedAt":"2021-07-05T12:32:42Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n Documentation/git-update.txt |  4 ++++\n builtin/update.c             | 20 +++++++++++++++++++-\n t/t5563-update.sh            | 17 +++++++++++++++++\n 3 files changed, 40 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-update.txt b/Documentation/git-update.txt\nindex 075653c6fd..0fefda1379 100644\n--- a/Documentation/git-update.txt\n+++ b/Documentation/git-update.txt\n@@ -33,6 +33,10 @@ OPTIONS\n --merge::\n \tForces a merge.\n \n+-r::\n+--rebase::\n+\tForces a rebase.\n+\n SEE ALSO\n --------\n linkgit:git-fetch[1], linkgit:git-fast-forward[1],\ndiff --git a/builtin/update.c b/builtin/update.c\nindex 94e83532a8..989104421e 100644\n--- a/builtin/update.c\n+++ b/builtin/update.c\n@@ -9,7 +9,8 @@\n \n enum update_mode_type {\n \tUPDATE_MODE_FAST_FORWARD = 0,\n-\tUPDATE_MODE_MERGE\n+\tUPDATE_MODE_MERGE,\n+\tUPDATE_MODE_REBASE\n };\n \n static enum update_mode_type mode = UPDATE_MODE_FAST_FORWARD;\n@@ -26,6 +27,9 @@ static struct option update_options[] = {\n \tOPT_SET_INT_F('m', \"merge\", &mode,\n \t\tN_(\"incorporate changes by merging\"),\n \t\tUPDATE_MODE_MERGE, PARSE_OPT_NONEG),\n+\tOPT_SET_INT_F('r', \"rebase\", &mode,\n+\t\tN_(\"incorporate changes by rebasing\"),\n+\t\tUPDATE_MODE_REBASE, PARSE_OPT_NONEG),\n \n \tOPT_END()\n };\n@@ -66,6 +70,18 @@ static int run_merge(void)\n \treturn ret;\n }\n \n+static int run_rebase(void)\n+{\n+\tint ret;\n+\tstruct strvec args = STRVEC_INIT;\n+\n+\tstrvec_pushl(&args, \"rebase\", \"FETCH_HEAD\", NULL);\n+\n+\tret = run_command_v_opt(args.v, RUN_GIT_CMD);\n+\tstrvec_clear(&args);\n+\treturn ret;\n+}\n+\n int cmd_update(int argc, const char **argv, const char *prefix)\n {\n \tif (!getenv(\"GIT_REFLOG_ACTION\"))\n@@ -86,6 +102,8 @@ int cmd_update(int argc, const char **argv, const char *prefix)\n \t\treturn run_fast_forward();\n \tif (mode == UPDATE_MODE_MERGE)\n \t\treturn run_merge();\n+\tif (mode == UPDATE_MODE_REBASE)\n+\t\treturn run_rebase();\n \n \treturn 1;\n }\ndiff --git a/t/t5563-update.sh b/t/t5563-update.sh\nindex aabbf5a965..73e67f9783 100755\n--- a/t/t5563-update.sh\n+++ b/t/t5563-update.sh\n@@ -67,4 +67,21 @@ test_expect_success 'git update non-fast-forward with merge' '\n \t)\n '\n \n+test_expect_success 'git update non-fast-forward with rebase' '\n+\ttest_when_finished \"rm -rf test\" &&\n+\t(\n+\tgit clone . test &&\n+\tcd test &&\n+\tgit checkout -b other master^ &&\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+\tgit update --rebase &&\n+\ttest_cmp_rev @^ other &&\n+\ttest_must_fail git rev-parse --verify -q @^2\n+\t)\n+'\n+\n test_done\n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429217","messageId":"20210705123209.1808663-18-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 17/35] update: add mode configuation","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:51Z","receivedAt":"2021-07-05T12:32:43Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n Documentation/config.txt        |  2 ++\n Documentation/config/update.txt |  4 ++++\n builtin/update.c                | 26 ++++++++++++++++++++++++++\n 3 files changed, 32 insertions(+)\n create mode 100644 Documentation/config/update.txt\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex fc4b49c0d4..998339e6fd 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -466,6 +466,8 @@ include::config/trace2.txt[]\n \n include::config/transfer.txt[]\n \n+include::config/update.txt[]\n+\n include::config/uploadarchive.txt[]\n \n include::config/uploadpack.txt[]\ndiff --git a/Documentation/config/update.txt b/Documentation/config/update.txt\nnew file mode 100644\nindex 0000000000..42a7fe1614\n--- /dev/null\n+++ b/Documentation/config/update.txt\n@@ -0,0 +1,4 @@\n+update.mode::\n+\tWhen `git update` is run, this determines the mode of operation,\n+\tpossible values are 'fast-forward', 'merge', and 'rebase'. The default\n+\tis 'fast-forward'.\ndiff --git a/builtin/update.c b/builtin/update.c\nindex 989104421e..4a1b82a41d 100644\n--- a/builtin/update.c\n+++ b/builtin/update.c\n@@ -5,9 +5,11 @@\n #include \"builtin.h\"\n #include \"parse-options.h\"\n #include \"run-command.h\"\n+#include \"config.h\"\n #include \"dir.h\"\n \n enum update_mode_type {\n+\tUPDATE_MODE_INVALID = -1,\n \tUPDATE_MODE_FAST_FORWARD = 0,\n \tUPDATE_MODE_MERGE,\n \tUPDATE_MODE_REBASE\n@@ -34,6 +36,28 @@ static struct option update_options[] = {\n \tOPT_END()\n };\n \n+static enum update_mode_type update_mode_parse_value(const char *value)\n+{\n+\tif (!strcmp(value, \"fast-forward\"))\n+\t\treturn UPDATE_MODE_FAST_FORWARD;\n+\tif (!strcmp(value, \"merge\"))\n+\t\treturn UPDATE_MODE_MERGE;\n+\tif (!strcmp(value, \"rebase\"))\n+\t\treturn UPDATE_MODE_REBASE;\n+\n+\treturn UPDATE_MODE_INVALID;\n+}\n+\n+static int git_update_config(const char *var, const char *value, void *cb)\n+{\n+\tif (!strcmp(var, \"update.mode\")) {\n+\t\tmode = update_mode_parse_value(value);\n+\t\treturn 0;\n+\t}\n+\n+\treturn git_default_config(var, value, cb);\n+}\n+\n static int run_fetch(void)\n {\n \tstruct strvec args = STRVEC_INIT;\n@@ -87,6 +111,8 @@ int cmd_update(int argc, const char **argv, const char *prefix)\n \tif (!getenv(\"GIT_REFLOG_ACTION\"))\n \t\tsetenv(\"GIT_REFLOG_ACTION\", \"update\", 0);\n \n+\tgit_config(git_update_config, NULL);\n+\n \targc = parse_options(argc, argv, prefix, update_options, update_usage, 0);\n \n \tif (repo_read_index_unmerged(the_repository))\n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429218","messageId":"20210705123209.1808663-19-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 18/35] update: add per-branch mode configuration","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:52Z","receivedAt":"2021-07-05T12:32:44Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n Documentation/config/branch.txt |  5 +++++\n Documentation/config/update.txt |  3 ++-\n builtin/update.c                | 18 ++++++++++++++++++\n 3 files changed, 25 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt\nindex cc5f3249fc..ba355f23e0 100644\n--- a/Documentation/config/branch.txt\n+++ b/Documentation/config/branch.txt\n@@ -96,6 +96,11 @@ mode.\n it unless you understand the implications (see linkgit:git-rebase[1]\n for details).\n \n+branch.<name>.updateMode::\n+\tWhen `git update` is run, this determines the mode of operation,\n+\tpossible values are 'fast-forward', 'merge', and 'rebase'.\n+\tSee `update.mode` for doing this in a non branch-specific manner.\n+\n branch.<name>.description::\n \tBranch description, can be edited with\n \t`git branch --edit-description`. Branch description is\ndiff --git a/Documentation/config/update.txt b/Documentation/config/update.txt\nindex 42a7fe1614..e6db45b6c3 100644\n--- a/Documentation/config/update.txt\n+++ b/Documentation/config/update.txt\n@@ -1,4 +1,5 @@\n update.mode::\n \tWhen `git update` is run, this determines the mode of operation,\n \tpossible values are 'fast-forward', 'merge', and 'rebase'. The default\n-\tis 'fast-forward'.\n+\tis 'fast-forward'. See \"branch.<name>.updateMode\" for setting this on a\n+\tper-branch basis.\ndiff --git a/builtin/update.c b/builtin/update.c\nindex 4a1b82a41d..50905f5806 100644\n--- a/builtin/update.c\n+++ b/builtin/update.c\n@@ -7,6 +7,7 @@\n #include \"run-command.h\"\n #include \"config.h\"\n #include \"dir.h\"\n+#include \"remote.h\"\n \n enum update_mode_type {\n \tUPDATE_MODE_INVALID = -1,\n@@ -58,6 +59,22 @@ static int git_update_config(const char *var, const char *value, void *cb)\n \treturn git_default_config(var, value, cb);\n }\n \n+static void get_branch_config(void)\n+{\n+\tstruct branch *cur_branch = branch_get(\"HEAD\");\n+\tchar *key;\n+\tconst char *value;\n+\n+\tif (!cur_branch)\n+\t\treturn;\n+\n+\tkey = xstrfmt(\"branch.%s.updatemode\", cur_branch->name);\n+\tif (!git_config_get_value(key, &value))\n+\t\tmode = update_mode_parse_value(value);\n+\n+\tfree(key);\n+}\n+\n static int run_fetch(void)\n {\n \tstruct strvec args = STRVEC_INIT;\n@@ -112,6 +129,7 @@ int cmd_update(int argc, const char **argv, const char *prefix)\n \t\tsetenv(\"GIT_REFLOG_ACTION\", \"update\", 0);\n \n \tgit_config(git_update_config, NULL);\n+\tget_branch_config();\n \n \targc = parse_options(argc, argv, prefix, update_options, update_usage, 0);\n \n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429219","messageId":"20210705123209.1808663-20-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 19/35] pull: cleanup autostash check","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:53Z","receivedAt":"2021-07-05T12:32:45Z","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\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 e8927fc2ff..a22293b7db 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -947,7 +947,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@@ -982,8 +981,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@@ -1065,13 +1064,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.32.0.36.g70aac2b1aa\n\n"},{"id":"429220","messageId":"20210705123209.1808663-21-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 20/35] pull: trivial cleanup","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:54Z","receivedAt":"2021-07-05T12:32:47Z","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 a22293b7db..80e2f55cbc 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -1053,7 +1053,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@@ -1068,11 +1067,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.32.0.36.g70aac2b1aa\n\n"},{"id":"429221","messageId":"20210705123209.1808663-22-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 21/35] pull: trivial whitespace style fix","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:55Z","receivedAt":"2021-07-05T12:32:50Z","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 80e2f55cbc..3e13f81084 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.32.0.36.g70aac2b1aa\n\n"},{"id":"429222","messageId":"20210705123209.1808663-23-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 22/35] pull: introduce --merge option","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:56Z","receivedAt":"2021-07-05T12:32:51Z","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   | 7 +++++--\n builtin/pull.c               | 4 +++-\n t/t7601-merge-pull-config.sh | 8 ++++----\n 3 files changed, 12 insertions(+), 7 deletions(-)\n\ndiff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\nindex 5c3fb67c01..d23128fa72 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -131,8 +131,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 3e13f81084..0d76b54186 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@@ -936,7 +938,7 @@ static void show_advice_pull_non_ff(void)\n \t\t \"  git config pull.ff only       # fast-forward only\\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 \"preference for all repositories. You can also pass --rebase, --merge,\\n\"\n \t\t \"or --ff-only on the command line to override the configured default per\\n\"\n \t\t \"invocation.\\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.32.0.36.g70aac2b1aa\n\n"},{"id":"429223","messageId":"20210705123209.1808663-24-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 23/35] rebase: add REBASE_DEFAULT","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:57Z","receivedAt":"2021-07-05T12:32:53Z","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 0d76b54186..94fc8b0739 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@@ -949,7 +947,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@@ -971,8 +968,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@@ -983,7 +980,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@@ -1043,17 +1040,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.32.0.36.g70aac2b1aa\n\n"},{"id":"429224","messageId":"20210705123209.1808663-25-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 24/35] pull: move configuration fetches","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:58Z","receivedAt":"2021-07-05T12:32:56Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Now that we have REBASE_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 REBASE_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 94fc8b0739..61af8ea2cb 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -949,6 +949,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@@ -965,12 +968,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.32.0.36.g70aac2b1aa\n\n"},{"id":"429225","messageId":"20210705123209.1808663-26-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 25/35] pull: show warning with --ff options","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:31:59Z","receivedAt":"2021-07-05T12:32:56Z","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\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 61af8ea2cb..2c2f0822d5 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -1042,7 +1042,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.32.0.36.g70aac2b1aa\n\n"},{"id":"429226","messageId":"20210705123209.1808663-27-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 26/35] pull: add pull.mode","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:32:00Z","receivedAt":"2021-07-05T12:32:57Z","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\nTo avoid some of the problems of git pull Linus Torvalds suggested a\nconfiguration pull.merge [1], however, instead of having pull.merge and\npull.rebase, we can have pull.mode which works for both.\n\nAdditionally this would allow us to have a saner per-branch\nconfiguration: branch.<name>.pullMode.\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\n[1] https://lore.kernel.org/git/CA+55aFz2Uvq4vmyjJPao5tS-uuVvKm6mbP7Uz8sdq1VMxMGJCw@mail.gmail.com/\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n Documentation/config/pull.txt |  5 +++\n builtin/pull.c                | 68 ++++++++++++++++++++++++++++++++---\n rebase.c                      | 10 ++++++\n rebase.h                      |  9 +++++\n t/t5520-pull.sh               | 42 ++++++++++++++++++++++\n t/t7601-merge-pull-config.sh  | 14 ++++++++\n 6 files changed, 143 insertions(+), 5 deletions(-)\n\ndiff --git a/Documentation/config/pull.txt b/Documentation/config/pull.txt\nindex 5404830609..646431a02d 100644\n--- a/Documentation/config/pull.txt\n+++ b/Documentation/config/pull.txt\n@@ -29,6 +29,11 @@ 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'.\n+\n pull.octopus::\n \tThe default merge strategy to use when pulling multiple branches\n \tat once.\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 2c2f0822d5..124575c32c 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,16 @@ 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+\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@@ -931,9 +964,9 @@ static void show_advice_pull_non_ff(void)\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 \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 config pull.mode merge  # the default strategy\\n\"\n+\t\t \"  git config pull.mode rebase\\n\"\n+\t\t \"  git config pull.ff only     # fast-forward only\\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, --merge,\\n\"\n@@ -968,6 +1001,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 e2c0c51022..663c15fcd7 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -452,6 +452,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@@ -526,6 +536,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@@ -555,6 +576,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@@ -596,6 +627,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.32.0.36.g70aac2b1aa\n\n"},{"id":"429227","messageId":"20210705123209.1808663-28-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 27/35] pull: add per-branch mode configuration","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:32:01Z","receivedAt":"2021-07-05T12:33:01Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Many people have argued that `git pull` should have a per-branch\nconfiguration for its mode of operation (e.g. Linus Torvalds [1] and\nTheodore Ts'o [2]).\n\nbranch.<name>.pullMode achieves that.\n\n[1] https://lore.kernel.org/git/CA+55aFz2Uvq4vmyjJPao5tS-uuVvKm6mbP7Uz8sdq1VMxMGJCw@mail.gmail.com/\n[2] https://lore.kernel.org/git/20130312212027.GE14792@thunk.org/\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n Documentation/config/branch.txt |  5 +++++\n Documentation/config/pull.txt   |  3 ++-\n builtin/pull.c                  | 13 +++++++++++++\n t/t5520-pull.sh                 | 11 +++++++++++\n 4 files changed, 31 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/config/branch.txt b/Documentation/config/branch.txt\nindex ba355f23e0..5f412caf62 100644\n--- a/Documentation/config/branch.txt\n+++ b/Documentation/config/branch.txt\n@@ -101,6 +101,11 @@ branch.<name>.updateMode::\n \tpossible values are 'fast-forward', 'merge', and 'rebase'.\n \tSee `update.mode` for doing this in a non branch-specific manner.\n \n+branch.<name>.pullMode::\n+\tWhen `git pull` is run, this determines the mode of operation,\n+\tpossible values are 'merge' and 'rebase'. See `pull.mode` for doing this\n+\tin a non branch-specific manner.\n+\n branch.<name>.description::\n \tBranch description, can be edited with\n \t`git branch --edit-description`. Branch description is\ndiff --git a/Documentation/config/pull.txt b/Documentation/config/pull.txt\nindex 646431a02d..3fb9bfdfea 100644\n--- a/Documentation/config/pull.txt\n+++ b/Documentation/config/pull.txt\n@@ -32,7 +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'.\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\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 124575c32c..bb3c0b55f9 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -374,8 +374,21 @@ static enum rebase_type config_get_rebase(void)\n \n static enum pull_mode_type config_get_pull_mode(const char *repo)\n {\n+\tstruct branch *curr_branch = branch_get(\"HEAD\");\n \tconst char *value;\n \n+\tif (curr_branch) {\n+\t\tchar *key = xstrfmt(\"branch.%s.pullmode\", curr_branch->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 \ndiff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\nindex 663c15fcd7..59894dd15a 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -493,6 +493,17 @@ test_expect_success 'branch.to-rebase.rebase should override pull.rebase' '\n \ttest_cmp expect actual\n '\n \n+test_expect_success 'branch..pullmode overrides pull.mode' '\n+\tgit reset --hard before-rebase &&\n+\ttest_config pull.mode rebase &&\n+\ttest_config branch.to-rebase.pullmode merge &&\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 --rebase warns on --verify-signatures' '\n \tgit reset --hard before-rebase &&\n \tgit pull --rebase --verify-signatures . copy 2>err &&\n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429228","messageId":"20210705123209.1808663-29-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 28/35] pull: add pull.mode=fast-forward","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:32:02Z","receivedAt":"2021-07-05T12:33:02Z","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/742df4c2-2bc5-8a4b-8de1-cd5e48718398@redhat.com/\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/git/4ay6w9i74cygt6ii1b0db7wg.1398433713382@email.android.com\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n Documentation/config/branch.txt |  4 +--\n Documentation/config/pull.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/branch.txt b/Documentation/config/branch.txt\nindex 5f412caf62..a61c80c90b 100644\n--- a/Documentation/config/branch.txt\n+++ b/Documentation/config/branch.txt\n@@ -103,8 +103,8 @@ branch.<name>.updateMode::\n \n branch.<name>.pullMode::\n \tWhen `git pull` is run, this determines the mode of operation,\n-\tpossible values are 'merge' and 'rebase'. See `pull.mode` for doing this\n-\tin a non branch-specific manner.\n+\tpossible values are 'fast-forward', 'merge', and 'rebase'.\n+\tSee `pull.mode` for doing this in a non branch-specific manner.\n \n branch.<name>.description::\n \tBranch description, can be edited with\ndiff --git a/Documentation/config/pull.txt b/Documentation/config/pull.txt\nindex 3fb9bfdfea..7b3dddf4d4 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/builtin/pull.c b/builtin/pull.c\nindex bb3c0b55f9..295c13fbe8 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -979,7 +979,7 @@ static void show_advice_pull_non_ff(void)\n \t\t \"\\n\"\n \t\t \"  git config pull.mode merge  # the default strategy\\n\"\n \t\t \"  git config pull.mode rebase\\n\"\n-\t\t \"  git config pull.ff only     # fast-forward only\\n\"\n+\t\t \"  git config pull.mode fast-forward\\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, --merge,\\n\"\n@@ -1023,6 +1023,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@@ -1113,6 +1114,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, 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 59894dd15a..7ea558651d 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -869,4 +869,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 main && 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 main\n+}\n+\n+setup_ff () {\n+\tsetup_other main\n+}\n+\n+setup_non_ff () {\n+\tsetup_other main^\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.32.0.36.g70aac2b1aa\n\n"},{"id":"429229","messageId":"20210705123209.1808663-30-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 29/35] pull: reorganize mode conditionals","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:32:03Z","receivedAt":"2021-07-05T12:33:03Z","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 295c13fbe8..cbc102ee45 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -1114,12 +1114,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, 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, either merge or rebase.\\n\"));\n \t}\n \n \tif (opt_rebase >= REBASE_TRUE) {\n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429230","messageId":"20210705123209.1808663-31-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 30/35] pull: add diverging advice on fast-forward mode","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:32:04Z","receivedAt":"2021-07-05T12:33:05Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n builtin/pull.c | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex cbc102ee45..f4f822700a 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -1118,8 +1118,10 @@ int cmd_pull(int argc, const char **argv, const char *prefix)\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\tif (mode == PULL_MODE_FAST_FORWARD) {\n+\t\t\tdiverging_advice();\n \t\t\tdie(_(\"The pull was not fast-forward, either merge or rebase.\\n\"));\n+\t\t}\n \t}\n \n \tif (opt_rebase >= REBASE_TRUE) {\n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429231","messageId":"20210705123209.1808663-32-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 31/35] pull: improve --rebase and pull.rebase interaction","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:32:05Z","receivedAt":"2021-07-05T12:33:09Z","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 f4f822700a..e304b22bd8 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 7ea558651d..b3b70d145f 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -597,6 +597,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.32.0.36.g70aac2b1aa\n\n"},{"id":"429232","messageId":"20210705123209.1808663-33-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 32/35] pull: improve default warning","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:32:06Z","receivedAt":"2021-07-05T12:33:10Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"We don't want to start by recommending a permanent configuration, but a\ntemporary solution so they start training their fingers and maybe learn\nhow 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, it's better use --global in the configuration commands like we\ndid with push.default.\n\nAnd finally, point to the documentation that explains what is a\nnon-fast-forward, and how to solve it:\n\n  git help fast-forward\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n builtin/pull.c | 21 +++++++++++----------\n 1 file changed, 11 insertions(+), 10 deletions(-)\n\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex e304b22bd8..27ce2f2183 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -979,18 +979,19 @@ 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.mode merge  # the default strategy\\n\"\n-\t\t \"  git config pull.mode rebase\\n\"\n-\t\t \"  git config pull.mode fast-forward\\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 replace \\\"git config\\\" with \\\"git config --global\\\" to set a default\\n\"\n-\t\t \"preference for all repositories. You can also pass --rebase, --merge,\\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 quell this message by running one of the following commands:\\n\"\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.mode fast-forward\\n\"\n+\t\t \"\\n\"\n+\t\t \"For more information check \\\"git help fast-forward\\\".\"));\n }\n \n int cmd_pull(int argc, const char **argv, const char *prefix)\n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429233","messageId":"20210705123209.1808663-34-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 33/35] pull: advice of future changes","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:32:07Z","receivedAt":"2021-07-05T12:33:13Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Now that we have `pull.mode=fast-forward`, we can make it the default any\ntime we want to.\n\nFor now, simply explain the upcoming changes in the default warning, and\nmention how to turn on the future default mode.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n builtin/pull.c               | 28 +++++++++++++------------\n t/t5520-pull.sh              |  8 ++++++++\n t/t7601-merge-pull-config.sh | 40 ++++++++++++++++++------------------\n 3 files changed, 43 insertions(+), 33 deletions(-)\n\ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 27ce2f2183..4771c953d2 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -979,19 +979,21 @@ 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 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 --merge # the default\\n\"\n-\t\t \"  git pull --rebase\\n\"\n-\t\t \"\\n\"\n-\t\t \"You can quell this message by running one of the following commands:\\n\"\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.mode fast-forward\\n\"\n-\t\t \"\\n\"\n-\t\t \"For more information check \\\"git help fast-forward\\\".\"));\n+\tadvise(_(\"The pull was not a fast-forward, in the future you will have to choose\\n\"\n+\t\t\"between a merge or a rebase.\\n\"\n+\t\t\"\\n\"\n+\t\t\"To quell this message you have two main options:\\n\"\n+\t\t\"\\n\"\n+\t\t\"1. Adopt the new behavior:\\n\"\n+\t\t\"\\n\"\n+\t\t\"  git config --global pull.mode fast-forward\\n\"\n+\t\t\"\\n\"\n+\t\t\"2. Maintain the current behavior:\\n\"\n+\t\t\"\\n\"\n+\t\t\"  git config --global pull.mode merge\\n\"\n+\t\t\"\\n\"\n+\t\t\"For now we will fall back to the traditional behavior: merge.\\n\"\n+\t\t\"For more information check \\\"git help fast-forward\\\".\"));\n }\n \n int cmd_pull(int argc, const char **argv, const char *prefix)\ndiff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\nindex b3b70d145f..bc1c601b8b 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -923,4 +923,12 @@ test_expect_success 'non-fast-forward error message (pull.mode=fast-forward)' '\n \ttest_i18ngrep \"The pull was not fast-forward\" error\n '\n \n+test_expect_success 'non-fast-forward warning (default)' '\n+\tsetup_non_ff &&\n+\tgit pull 2> error &&\n+\tcat error &&\n+\ttest_i18ngrep \"The pull was not a fast-forward\" error &&\n+\ttest_i18ngrep \"in the future you will have to choose\" error\n+'\n+\n test_done\ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex 47fd2e2d05..16d1e1ba47 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -30,65 +30,65 @@ test_expect_success 'setup' '\n test_expect_success 'pull.rebase not set' '\n \tgit reset --hard c0 &&\n \tgit pull . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"in the future you will have to choose\" 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+\ttest_i18ngrep ! \"in the future you will have to choose\" 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 ! \"in the future you will have to choose\" 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 ! \"in the future you will have to choose\" 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 ! \"in the future you will have to choose\" 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 ! \"in the future you will have to choose\" err\n '\n \n test_expect_success 'pull.rebase not set and --merge given' '\n \tgit reset --hard c0 &&\n \tgit pull --merge . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"in the future you will have to choose\" 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 ! \"in the future you will have to choose\" 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 ! \"in the future you will have to choose\" 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 ! \"in the future you will have to choose\" err\n '\n \n test_expect_success 'pull.rebase not set (not-fast-forward)' '\n@@ -96,65 +96,65 @@ test_expect_success 'pull.rebase not set (not-fast-forward)' '\n \tgit -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 \"in the future you will have to choose\" 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+\ttest_i18ngrep ! \"in the future you will have to choose\" 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 \tgit pull . c1 2>err &&\n-\ttest_i18ngrep \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep \"in the future you will have to choose\" 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 \"in the future you will have to choose\" 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 \"in the future you will have to choose\" 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 ! \"in the future you will have to choose\" err\n '\n \n test_expect_success 'pull.rebase not set and --merge given (not-fast-forward)' '\n \tgit reset --hard c2 &&\n \tgit pull --merge . c1 2>err &&\n-\ttest_i18ngrep ! \"Pulling without specifying how to reconcile\" err\n+\ttest_i18ngrep ! \"in the future you will have to choose\" 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 \"in the future you will have to choose\" 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 \"in the future you will have to choose\" 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 \"in the future you will have to choose\" err\n '\n \n test_expect_success 'merge c1 with c2' '\n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429234","messageId":"20210705123209.1808663-35-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 34/35] FUTURE: pull: enable ff-only mode by default","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:32:08Z","receivedAt":"2021-07-05T12:33:14Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"The user has been warned that this change was coming and should have\nalready configured his/her preference.\n\nIt's time to flip the switch and make ff-only the default.\n\nThere's no need for the annoying warning anymore.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n Documentation/git-pull.txt   |   3 +\n builtin/pull.c               |  30 +-------\n t/t5520-pull.sh              |  28 +++-----\n t/t7601-merge-pull-config.sh | 130 -----------------------------------\n 4 files changed, 16 insertions(+), 175 deletions(-)\n\ndiff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\nindex d23128fa72..c2768fadb1 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -35,6 +35,9 @@ Default values for <repository> and <branch> are read from the\n \"remote\" and \"merge\" configuration for the current branch\n as set by linkgit:git-branch[1] `--track`.\n \n+By default non-fast-forward merges fail, so you need to specify if you want to\n+do a merge or a rebase.\n+\n Assume the following history exists and the current branch is\n \"`master`\":\n \ndiff --git a/builtin/pull.c b/builtin/pull.c\nindex 4771c953d2..24c72e33c6 100644\n--- a/builtin/pull.c\n+++ b/builtin/pull.c\n@@ -977,25 +977,6 @@ static int get_can_ff(struct object_id *orig_head, struct object_id *orig_merge_\n \treturn ret;\n }\n \n-static void show_advice_pull_non_ff(void)\n-{\n-\tadvise(_(\"The pull was not a fast-forward, in the future you will have to choose\\n\"\n-\t\t\"between a merge or a rebase.\\n\"\n-\t\t\"\\n\"\n-\t\t\"To quell this message you have two main options:\\n\"\n-\t\t\"\\n\"\n-\t\t\"1. Adopt the new behavior:\\n\"\n-\t\t\"\\n\"\n-\t\t\"  git config --global pull.mode fast-forward\\n\"\n-\t\t\"\\n\"\n-\t\t\"2. Maintain the current behavior:\\n\"\n-\t\t\"\\n\"\n-\t\t\"  git config --global pull.mode merge\\n\"\n-\t\t\"\\n\"\n-\t\t\"For now we will fall back to the traditional behavior: merge.\\n\"\n-\t\t\"For more information check \\\"git help fast-forward\\\".\"));\n-}\n-\n int cmd_pull(int argc, const char **argv, const char *prefix)\n {\n \tconst char *repo, **refspecs;\n@@ -1123,14 +1104,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 (!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\tdiverging_advice();\n-\t\t\tdie(_(\"The pull was not fast-forward, either merge or rebase.\\n\"));\n-\t\t}\n+\tif ((!mode || mode == PULL_MODE_FAST_FORWARD) && !can_ff) {\n+\t\tdiverging_advice();\n+\t\tdie(_(\"The pull was not fast-forward, either merge or rebase.\\n\"));\n \t}\n \n \tif (opt_rebase >= REBASE_TRUE) {\ndiff --git a/t/t5520-pull.sh b/t/t5520-pull.sh\nindex bc1c601b8b..dea3ea8c37 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\n@@ -897,38 +897,30 @@ setup_non_ff () {\n \tsetup_other main^\n }\n \n-test_expect_success 'fast-forward (pull.mode=fast-forward)' '\n+test_expect_success 'fast-forward (default)' '\n \tsetup_ff &&\n-\tgit -c pull.mode=fast-forward pull\n+\tgit pull\n '\n \n-test_expect_success 'non-fast-forward (pull.mode=fast-forward)' '\n+test_expect_success 'non-fast-forward (default)' '\n \tsetup_non_ff &&\n-\ttest_must_fail git -c pull.mode=fast-forward pull\n+\ttest_must_fail git pull\n '\n \n-test_expect_success 'non-fast-forward with merge (pull.mode=fast-forward)' '\n+test_expect_success 'non-fast-forward with merge (default)' '\n \tsetup_non_ff &&\n-\tgit -c pull.mode=fast-forward pull --merge\n+\tgit pull --merge\n '\n \n-test_expect_success 'non-fast-forward with rebase (pull.mode=fast-forward)' '\n+test_expect_success 'non-fast-forward with rebase (default)' '\n \tsetup_non_ff &&\n-\tgit -c pull.mode=fast-forward pull --rebase\n+\tgit pull --rebase\n '\n \n-test_expect_success 'non-fast-forward error message (pull.mode=fast-forward)' '\n+test_expect_success 'non-fast-forward error message (default)' '\n \tsetup_non_ff &&\n-\ttest_must_fail git -c pull.mode=fast-forward pull 2> error &&\n+\ttest_must_fail git pull 2> error &&\n \ttest_i18ngrep \"The pull was not fast-forward\" error\n '\n \n-test_expect_success 'non-fast-forward warning (default)' '\n-\tsetup_non_ff &&\n-\tgit pull 2> error &&\n-\tcat error &&\n-\ttest_i18ngrep \"The pull was not a fast-forward\" error &&\n-\ttest_i18ngrep \"in the future you will have to choose\" error\n-'\n-\n test_done\ndiff --git a/t/t7601-merge-pull-config.sh b/t/t7601-merge-pull-config.sh\nindex 16d1e1ba47..c6c44ec570 100755\n--- a/t/t7601-merge-pull-config.sh\n+++ b/t/t7601-merge-pull-config.sh\n@@ -27,136 +27,6 @@ test_expect_success 'setup' '\n \tgit tag c3\n '\n \n-test_expect_success 'pull.rebase not set' '\n-\tgit reset --hard c0 &&\n-\tgit pull . c1 2>err &&\n-\ttest_i18ngrep ! \"in the future you will have to choose\" 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 ! \"in the future you will have to choose\" 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 ! \"in the future you will have to choose\" 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 ! \"in the future you will have to choose\" 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 ! \"in the future you will have to choose\" 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 ! \"in the future you will have to choose\" err\n-'\n-\n-test_expect_success 'pull.rebase not set and --merge given' '\n-\tgit reset --hard c0 &&\n-\tgit pull --merge . c1 2>err &&\n-\ttest_i18ngrep ! \"in the future you will have to choose\" 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 ! \"in the future you will have to choose\" 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 ! \"in the future you will have to choose\" 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 ! \"in the future you will have to choose\" 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_decode_color <err >decoded &&\n-\ttest_i18ngrep \"<YELLOW>hint: \" decoded &&\n-\ttest_i18ngrep \"in the future you will have to choose\" 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 ! \"in the future you will have to choose\" 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-\tgit pull . c1 2>err &&\n-\ttest_i18ngrep \"in the future you will have to choose\" 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 \"in the future you will have to choose\" 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 \"in the future you will have to choose\" 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 ! \"in the future you will have to choose\" err\n-'\n-\n-test_expect_success 'pull.rebase not set and --merge given (not-fast-forward)' '\n-\tgit reset --hard c2 &&\n-\tgit pull --merge . c1 2>err &&\n-\ttest_i18ngrep ! \"in the future you will have to choose\" 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 \"in the future you will have to choose\" 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 \"in the future you will have to choose\" 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 \"in the future you will have to choose\" err\n-'\n-\n test_expect_success 'merge c1 with c2' '\n \tgit reset --hard c1 &&\n \ttest -f c0.c &&\n-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429235","messageId":"20210705123209.1808663-36-felipe.contreras@gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-1-felipe.contreras@gmail.com","subject":"[RFC PATCH 35/35] !fixup FUTURE: pull: enable ff-only mode by default","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-05T12:32:09Z","receivedAt":"2021-07-05T12:33:15Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Fix all the tests.\n\nTODO: some of these should probably not even use `git pull .` but\n`git merge` instead.\n\nSigned-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n---\n t/t4013-diff-various.sh       |  2 +-\n t/t5520-pull.sh               | 16 ++++++++--------\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       | 16 ++++++++--------\n t/t6409-merge-subtree.sh      |  6 +++---\n t/t6417-merge-ours-theirs.sh  | 10 +++++-----\n t/t7603-merge-reduce-heads.sh |  2 +-\n 10 files changed, 39 insertions(+), 39 deletions(-)\n\ndiff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh\nindex 7fadc985cc..48ae585e1a 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 --merge -s ours . 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 dea3ea8c37..ad4fe1998e 100755\n--- a/t/t5520-pull.sh\n+++ b/t/t5520-pull.sh\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 --merge . 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 --merge\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 --merge --autostash\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 --merge --autostash\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 --merge --autostash\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 --merge --no-autostash\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 --merge --no-autostash\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 --merge --no-autostash\n '\n \n test_expect_success 'pull.rebase' '\ndiff --git a/t/t5521-pull-options.sh b/t/t5521-pull-options.sh\nindex 63a688bdbf..361800a6f4 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 --all --force --merge\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 --allow-unrelated-histories --merge ../src side\n \t)\n '\n \ndiff --git a/t/t5524-pull-msg.sh b/t/t5524-pull-msg.sh\nindex c278adaa5a..2f8a20de18 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 --log --merge &&\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 --log=1 --merge &&\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 b1d614ce18..dabbea76d3 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 --merge --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 --merge --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 --merge --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 --merge --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 --merge --set-upstream upstream HEAD &&\n \tcheck_config main upstream HEAD &&\n \tgit checkout other &&\n-\tgit pull --set-upstream upstream HEAD &&\n+\tgit pull --merge --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 --merge --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 e845d621f6..1c9b121814 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 --merge 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 --merge 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 425dad97d5..833b799e25 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 --merge . 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 --merge . 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 --merge . 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 --merge . 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 --merge . 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 --merge . 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 --merge . yellow &&\n \ttest_path_is_missing M &&\n \tgit reset --hard anchor\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 --merge . 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 d406b2343c..e1011f6325 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 --merge -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 --merge -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 --merge -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 ac9aee9a66..06cebb2974 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 --merge -s recursive -Xours . side &&\n+\tgit reset --hard main && git pull --merge -s recursive -X ours . side &&\n+\tgit reset --hard main && git pull --merge -s recursive -Xtheirs . side &&\n+\tgit reset --hard main && git pull --merge -s recursive -X theirs . side &&\n+\tgit reset --hard main && test_must_fail git pull --merge -s recursive -X bork . side\n '\n \n test_expect_success SYMLINKS 'symlink with -Xours/-Xtheirs' '\ndiff --git a/t/t7603-merge-reduce-heads.sh b/t/t7603-merge-reduce-heads.sh\nindex 98948955ae..566b1c1a2a 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 --merge . 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-- \n2.32.0.36.g70aac2b1aa\n\n"},{"id":"429337","messageId":"87bl7f5ho1.fsf@evledraar.gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-2-felipe.contreras@gmail.com","subject":"Re: [RFC PATCH 01/35] merge: improve fatal fast-forward message","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-07-06T20:07:02Z","receivedAt":"2021-07-06T20:08:50Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Jul 05 2021, Felipe Contreras wrote:\n\n> Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n> ---\n>  builtin/merge.c | 2 +-\n>  1 file changed, 1 insertion(+), 1 deletion(-)\n>\n> diff --git a/builtin/merge.c b/builtin/merge.c\n> index a8a843b1f5..05e631229d 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(_(\"unable to fast-forward\"));\n\nI read the existing message a bit more like \"this makes no sense\nanymore\" (correct) and the latter more like \"we encountered an\nerror\". Perhaps something like:\n\n    \"Can't merge X with Y, in --ff-only mode, would need to create a merge commit\", tip_name\n"},{"id":"429338","messageId":"878s2j5hk6.fsf@evledraar.gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-6-felipe.contreras@gmail.com","subject":"Re: [RFC PATCH 05/35] fast-forward: add advice for novices","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-07-06T20:09:57Z","receivedAt":"2021-07-06T20:11:11Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Jul 05 2021, Felipe Contreras wrote:\n\n> It doesn't hurt showing it on `git merge --ff-only` too.\n>\n> Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n> ---\n>  advice.c        | 11 +++++++++++\n>  advice.h        |  1 +\n>  builtin/merge.c |  4 +++-\n>  3 files changed, 15 insertions(+), 1 deletion(-)\n>\n> diff --git a/advice.c b/advice.c\n> index 0b9c89c48a..8f068c8be9 100644\n> --- a/advice.c\n> +++ b/advice.c\n> @@ -326,3 +326,14 @@ void detach_advice(const char *new_name)\n>  \n>  \tfprintf(stderr, fmt, new_name);\n>  }\n> +\n> +void diverging_advice(void)\n> +{\n> +\tadvise(_(\"Diverging branches can't be fast-forwarded, you need to either:\\n\"\n> +\t\t\"\\n\"\n> +\t\t\"\\tgit merge\\n\"\n> +\t\t\"\\n\"\n> +\t\t\"or:\\n\"\n> +\t\t\"\\n\"\n> +\t\t\"\\tgit rebase\\n\"));\n> +}\n> diff --git a/advice.h b/advice.h\n> index bd26c385d0..6ce967c962 100644\n> --- a/advice.h\n> +++ b/advice.h\n> @@ -97,5 +97,6 @@ void NORETURN die_resolve_conflict(const char *me);\n>  void NORETURN die_conclude_merge(void);\n>  void advise_on_updating_sparse_paths(struct string_list *pathspec_list);\n>  void detach_advice(const char *new_name);\n> +void diverging_advice(void);\n>  \n>  #endif /* ADVICE_H */\n> diff --git a/builtin/merge.c b/builtin/merge.c\n> index e396943d37..1836f98f82 100644\n> --- a/builtin/merge.c\n> +++ b/builtin/merge.c\n> @@ -1625,8 +1625,10 @@ static int merge_common(int argc, const char **argv, const char *prefix,\n>  \t\t}\n>  \t}\n>  \n> -\tif (fast_forward == FF_ONLY)\n> +\tif (fast_forward == FF_ONLY) {\n> +\t\tdiverging_advice();\n>  \t\tdie(_(\"unable to fast-forward\"));\n> +\t}\n>  \n>  \tif (autostash)\n>  \t\tcreate_autostash(the_repository,\n\n...ah, and re my comment on the earlier patch here's where we're adding\nadvice.\n\nI'd think just squash that into this, or mention in the commit message\n\"a later commit will add an advice() before this, where this new wording\nwill make more sense\" or something...\n"},{"id":"429340","messageId":"875yxn5hh2.fsf@evledraar.gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-11-felipe.contreras@gmail.com","subject":"Re: [RFC PATCH 10/35] update: add --ff option","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-07-06T20:12:15Z","receivedAt":"2021-07-06T20:13:02Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Jul 05 2021, Felipe Contreras wrote:\n\n>  --------\n>  linkgit:git-fetch[1], linkgit:git-fast-forward[1],\n> diff --git a/builtin/update.c b/builtin/update.c\n> index 1a69896aa8..34681fe21a 100644\n> --- a/builtin/update.c\n> +++ b/builtin/update.c\n> @@ -7,12 +7,22 @@\n>  #include \"run-command.h\"\n>  #include \"dir.h\"\n>  \n> +enum update_mode_type {\n> +\tUPDATE_MODE_FAST_FORWARD = 0\n> +};\n\nNit: If the value isn't important let's leave it out, also if you add a\ntrailing comma the subsequent commit where you add another value is less\nchurny. C supports that just fine.\n"},{"id":"429341","messageId":"8735sr5hfg.fsf@evledraar.gmail.com","threadId":"56049","inReplyTo":"20210705123209.1808663-12-felipe.contreras@gmail.com","subject":"Re: [RFC PATCH 11/35] update: add --merge mode","fromName":"Ævar Arnfjörð Bjarmason","fromEmail":"avarab@gmail.com","sentAt":"2021-07-06T20:13:30Z","receivedAt":"2021-07-06T20:14:00Z","isPatch":true,"sender":{"key":"avarab@gmail.com","avatar":"https://avatars.githubusercontent.com/u/45301?v=4"},"body":"\nOn Mon, Jul 05 2021, Felipe Contreras wrote:\n\n> Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n> ---\n>  Documentation/git-update.txt |  4 ++++\n>  builtin/update.c             | 20 +++++++++++++++++++-\n>  t/t5563-update.sh            | 15 +++++++++++++++\n>  3 files changed, 38 insertions(+), 1 deletion(-)\n>\n> diff --git a/Documentation/git-update.txt b/Documentation/git-update.txt\n> index df778b7ef4..075653c6fd 100644\n> --- a/Documentation/git-update.txt\n> +++ b/Documentation/git-update.txt\n> @@ -29,6 +29,10 @@ OPTIONS\n>  --ff::\n>  \tForces a fast-forward.\n>  \n> +-m::\n> +--merge::\n> +\tForces a merge.\n> +\n>  SEE ALSO\n>  --------\n>  linkgit:git-fetch[1], linkgit:git-fast-forward[1],\n> diff --git a/builtin/update.c b/builtin/update.c\n> index 34681fe21a..5946b383f5 100644\n> --- a/builtin/update.c\n> +++ b/builtin/update.c\n> @@ -8,7 +8,8 @@\n>  #include \"dir.h\"\n>  \n>  enum update_mode_type {\n> -\tUPDATE_MODE_FAST_FORWARD = 0\n> +\tUPDATE_MODE_FAST_FORWARD = 0,\n> +\tUPDATE_MODE_MERGE\n>  };\n>  \n>  static enum update_mode_type mode = UPDATE_MODE_FAST_FORWARD;\n> @@ -22,6 +23,9 @@ static struct option update_options[] = {\n>  \tOPT_SET_INT_F('f', \"ff\", &mode,\n>  \t\tN_(\"incorporate changes by fast-forwarding\"),\n>  \t\tUPDATE_MODE_FAST_FORWARD, PARSE_OPT_NONEG),\n> +\tOPT_SET_INT_F('m', \"merge\", &mode,\n> +\t\tN_(\"incorporate changes by merging\"),\n> +\t\tUPDATE_MODE_MERGE, PARSE_OPT_NONEG),\n>  \n>  \tOPT_END()\n>  };\n> @@ -50,6 +54,18 @@ static int run_fast_forward(void)\n>  \treturn ret;\n>  }\n>  \n> +static int run_merge(void)\n> +{\n> +\tint ret;\n> +\tstruct strvec args = STRVEC_INIT;\n> +\n> +\tstrvec_pushl(&args, \"merge\", \"FETCH_HEAD\", NULL);\n> +\n> +\tret = run_command_v_opt(args.v, RUN_GIT_CMD);\n> +\tstrvec_clear(&args);\n> +\treturn ret;\n> +}\n> +\n>  int cmd_update(int argc, const char **argv, const char *prefix)\n>  {\n>  \tif (!getenv(\"GIT_REFLOG_ACTION\"))\n> @@ -68,6 +84,8 @@ int cmd_update(int argc, const char **argv, const char *prefix)\n>  \n>  \tif (mode == UPDATE_MODE_FAST_FORWARD)\n>  \t\treturn run_fast_forward();\n> +\tif (mode == UPDATE_MODE_MERGE)\n> +\t\treturn run_merge();\n>  \n>  \treturn 1;\n>  }\n> diff --git a/t/t5563-update.sh b/t/t5563-update.sh\n> index 951df41ac4..833a5285da 100755\n> --- a/t/t5563-update.sh\n> +++ b/t/t5563-update.sh\n> @@ -42,4 +42,19 @@ test_expect_success 'non-fast-forward update' '\n>  \t)\n>  '\n>  \n> +test_expect_success 'git update non-fast-forward with merge' '\n> +\ttest_when_finished \"rm -rf test\" &&\n> +\t(\n> +\tgit clone . test &&\n> +\tcd test &&\n> +\tgit checkout -b other master^ &&\n> +\t>new &&\n\nWe usually indent the subshell...\n\n> +\tgit add new &&\n> +\tgit commit -m new &&\n\nAnd if we can use test_commit, less verbose.\n"},{"id":"429343","messageId":"60e4bf9a6a628_1c4281208b@natae.notmuch","threadId":"56049","inReplyTo":"87bl7f5ho1.fsf@evledraar.gmail.com","subject":"Re: [RFC PATCH 01/35] merge: improve fatal fast-forward message","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-06T20:39:54Z","receivedAt":"2021-07-06T20:39:59Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Ævar Arnfjörð Bjarmason wrote:\n> \n> On Mon, Jul 05 2021, Felipe Contreras wrote:\n> \n> > Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n> > ---\n> >  builtin/merge.c | 2 +-\n> >  1 file changed, 1 insertion(+), 1 deletion(-)\n> >\n> > diff --git a/builtin/merge.c b/builtin/merge.c\n> > index a8a843b1f5..05e631229d 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(_(\"unable to fast-forward\"));\n> \n> I read the existing message a bit more like \"this makes no sense\n> anymore\" (correct) and the latter more like \"we encountered an\n> error\".\n\nI mean, this is the documentation of --ff-only:\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\nSo if you do `git merge --ff-only` you are telling git: \"I want you to\nexit with an error when the fast-forward is not possible\".\n\nIf you do:\n\n  % git merge --ff-only\n  fatal: Not possible to fast-forward, aborting.\n\nThat \"aborting\" part is redundant; we know `git merge` should abort if\nthe fast-forward is not possible, we explicitely told git to do that.\n\nMoreover the \"fatal: \" prefix also indicates that git aborted.\n\nThen you have \"Not possible to fast-forward\", which if memory serves\nwell should be in lowercase (altghough can't find that in the\nguidelines).\n\n\"unable to fast-forward\" is simply a more succinct way of saying\n\"not possible to fast-forward\". Sure, we are not explaining why,\nalthough I can't think of any other reason why we could not fast-forward.\n\n> Perhaps something like:\n> \n>     \"Can't merge X with Y, in --ff-only mode, would need to create a merge commit\", tip_name\n\nI don't think die() messages should be that big, how about?\n\n  branches diverged, can't fast-forward\n\n-- \nFelipe Contreras"},{"id":"429344","messageId":"60e4c03c58a45_1c4281208cb@natae.notmuch","threadId":"56049","inReplyTo":"878s2j5hk6.fsf@evledraar.gmail.com","subject":"Re: [RFC PATCH 05/35] fast-forward: add advice for novices","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-06T20:42:36Z","receivedAt":"2021-07-06T20:42:39Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Ævar Arnfjörð Bjarmason wrote:\n> On Mon, Jul 05 2021, Felipe Contreras wrote:\n\n> > --- a/builtin/merge.c\n> > +++ b/builtin/merge.c\n> > @@ -1625,8 +1625,10 @@ static int merge_common(int argc, const char **argv, const char *prefix,\n> >  \t\t}\n> >  \t}\n> >  \n> > -\tif (fast_forward == FF_ONLY)\n> > +\tif (fast_forward == FF_ONLY) {\n> > +\t\tdiverging_advice();\n> >  \t\tdie(_(\"unable to fast-forward\"));\n> > +\t}\n> >  \n> >  \tif (autostash)\n> >  \t\tcreate_autostash(the_repository,\n> \n> ...ah, and re my comment on the earlier patch here's where we're adding\n> advice.\n> \n> I'd think just squash that into this, or mention in the commit message\n> \"a later commit will add an advice() before this, where this new wording\n> will make more sense\" or something...\n\nI don't think we lost any information in the previous patch. In fact\nwith my suggestion we gained a little bit.\n\n-- \nFelipe Contreras"},{"id":"429345","messageId":"60e4c13ce851f_1c428120869@natae.notmuch","threadId":"56049","inReplyTo":"875yxn5hh2.fsf@evledraar.gmail.com","subject":"Re: [RFC PATCH 10/35] update: add --ff option","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-06T20:46:52Z","receivedAt":"2021-07-06T20:46:57Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Ævar Arnfjörð Bjarmason wrote:\n> \n> On Mon, Jul 05 2021, Felipe Contreras wrote:\n> \n> >  --------\n> >  linkgit:git-fetch[1], linkgit:git-fast-forward[1],\n> > diff --git a/builtin/update.c b/builtin/update.c\n> > index 1a69896aa8..34681fe21a 100644\n> > --- a/builtin/update.c\n> > +++ b/builtin/update.c\n> > @@ -7,12 +7,22 @@\n> >  #include \"run-command.h\"\n> >  #include \"dir.h\"\n> >  \n> > +enum update_mode_type {\n> > +\tUPDATE_MODE_FAST_FORWARD = 0\n> > +};\n> \n> Nit: If the value isn't important let's leave it out,\n\nYes, in one instantiation of the series it did matter, but not in the\ncurrent one. I'll drop it.\n\n> also if you add a trailing comma the subsequent commit where you add\n> another value is less churny. C supports that just fine.\n\nI know, and I actually prefer that style, but I've seen the comma\ndropped in many instances in the current code base. I was just following\nthe current style.\n\nBut if you prefer it as well I'll add it.\n\n-- \nFelipe Contreras"},{"id":"429346","messageId":"04de01d772a8$540c6690$fc2533b0$@nexbridge.com","threadId":"56049","inReplyTo":"60e4bf9a6a628_1c4281208b@natae.notmuch","subject":"RE: [RFC PATCH 01/35] merge: improve fatal fast-forward message","fromName":"Randall S. Becker","fromEmail":"rsbecker@nexbridge.com","sentAt":"2021-07-06T20:48:46Z","receivedAt":"2021-07-06T20:48:59Z","isPatch":true,"sender":{"key":"randall.becker@nexbridge.ca","avatar":"https://avatars.githubusercontent.com/u/28956764?v=4"},"body":"On July 6, 2021 4:40 PM, Felipe Contreras wrote:\n>Subject: Re: [RFC PATCH 01/35] merge: improve fatal fast-forward message\n>\n>Ævar Arnfjörð Bjarmason wrote:\n>>\n>> On Mon, Jul 05 2021, Felipe Contreras wrote:\n>>\n>> > Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n>> > ---\n>> >  builtin/merge.c | 2 +-\n>> >  1 file changed, 1 insertion(+), 1 deletion(-)\n>> >\n>> > diff --git a/builtin/merge.c b/builtin/merge.c index\n>> > a8a843b1f5..05e631229d 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(_(\"unable to fast-forward\"));\n>>\n>> I read the existing message a bit more like \"this makes no sense\n>> anymore\" (correct) and the latter more like \"we encountered an error\".\n>\n>I mean, this is the documentation of --ff-only:\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>So if you do `git merge --ff-only` you are telling git: \"I want you to exit with an error when the fast-forward is not possible\".\n>\n>If you do:\n>\n>  % git merge --ff-only\n>  fatal: Not possible to fast-forward, aborting.\n>\n>That \"aborting\" part is redundant; we know `git merge` should abort if the fast-forward is not possible, we explicitely told git to do that.\n\n`git merge` is a special operation where errors (conflicts, for one) may leave the repository in a merge pending state where you subsequently may have to use `git merge --abort` to reset the situation or `git add` to continue. The `aborting` output makes it clear that you do not have to do the `--abort` and *cannot* do the `add` because there was an implicit `--abort` done resulting from the failure. This is important information for the user.\n\n"},{"id":"429347","messageId":"60e4c257a998d_1c42812085c@natae.notmuch","threadId":"56049","inReplyTo":"8735sr5hfg.fsf@evledraar.gmail.com","subject":"Re: [RFC PATCH 11/35] update: add --merge mode","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-06T20:51:35Z","receivedAt":"2021-07-06T20:51:41Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Ævar Arnfjörð Bjarmason wrote:\n> On Mon, Jul 05 2021, Felipe Contreras wrote:\n\n> > --- a/t/t5563-update.sh\n> > +++ b/t/t5563-update.sh\n> > @@ -42,4 +42,19 @@ test_expect_success 'non-fast-forward update' '\n> >  \t)\n> >  '\n> >  \n> > +test_expect_success 'git update non-fast-forward with merge' '\n> > +\ttest_when_finished \"rm -rf test\" &&\n> > +\t(\n> > +\tgit clone . test &&\n> > +\tcd test &&\n> > +\tgit checkout -b other master^ &&\n> > +\t>new &&\n> \n> We usually indent the subshell...\n\nAll right.\n\n> > +\tgit add new &&\n> > +\tgit commit -m new &&\n> \n> And if we can use test_commit, less verbose.\n\nOK. Sure.\n\nFTR. These tests come from 2013, they don't fit my current style and I\nwanted to rewrite them completely since there's a lot of duplicated\ncode. But I thought: what the hell, they work. Plus there's only so much\ntime I'm willing to spend on an RFC v1 of this series (which probably\nwon't be merged anyway).\n\nCheers.\n\n-- \nFelipe Contreras"},{"id":"429350","messageId":"xmqq1r8bi2l0.fsf@gitster.g","threadId":"56049","inReplyTo":"04de01d772a8$540c6690$fc2533b0$@nexbridge.com","subject":"Re: [RFC PATCH 01/35] merge: improve fatal fast-forward message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-07-06T20:56:11Z","receivedAt":"2021-07-06T20:56:17Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Randall S. Becker\" <rsbecker@nexbridge.com> writes:\n\n>>If you do:\n>>\n>>  % git merge --ff-only\n>>  fatal: Not possible to fast-forward, aborting.\n>>\n>>That \"aborting\" part is redundant; we know `git merge` should abort\n> if the fast-forward is not possible, we explicitely told git to do\n> that.\n>\n> `git merge` is a special operation where errors (conflicts, for one)\n> may leave the repository in a merge pending state where you\n> subsequently may have to use `git merge --abort` to reset the\n> situation or `git add` to continue. The `aborting` output makes it\n> clear that you do not have to do the `--abort` and *cannot* do the\n> `add` because there was an implicit `--abort` done resulting from the\n> failure. This is important information for the user.\n\nIf so, adding \", aborting\" to the end is misleading.  In this\nparticular failure mode, the command pretends that the merge did not\neven start.\n\n"},{"id":"429356","messageId":"60e4c71099ef_1c4281208ad@natae.notmuch","threadId":"56049","inReplyTo":"04de01d772a8$540c6690$fc2533b0$@nexbridge.com","subject":"RE: [RFC PATCH 01/35] merge: improve fatal fast-forward message","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-06T21:11:44Z","receivedAt":"2021-07-06T21:11:48Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Randall S. Becker wrote:\n> On July 6, 2021 4:40 PM, Felipe Contreras wrote:\n> >Subject: Re: [RFC PATCH 01/35] merge: improve fatal fast-forward message\n> >\n> >Ævar Arnfjörð Bjarmason wrote:\n> >>\n> >> On Mon, Jul 05 2021, Felipe Contreras wrote:\n> >>\n> >> > Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n> >> > ---\n> >> >  builtin/merge.c | 2 +-\n> >> >  1 file changed, 1 insertion(+), 1 deletion(-)\n> >> >\n> >> > diff --git a/builtin/merge.c b/builtin/merge.c index\n> >> > a8a843b1f5..05e631229d 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(_(\"unable to fast-forward\"));\n> >>\n> >> I read the existing message a bit more like \"this makes no sense\n> >> anymore\" (correct) and the latter more like \"we encountered an error\".\n> >\n> >I mean, this is the documentation of --ff-only:\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> >So if you do `git merge --ff-only` you are telling git: \"I want you to exit with an error when the fast-forward is not possible\".\n> >\n> >If you do:\n> >\n> >  % git merge --ff-only\n> >  fatal: Not possible to fast-forward, aborting.\n> >\n> >That \"aborting\" part is redundant; we know `git merge` should abort if the fast-forward is not possible, we explicitely told git to do that.\n> \n> `git merge` is a special operation where errors (conflicts, for one)\n> may leave the repository in a merge pending state where you\n> subsequently may have to use `git merge --abort` to reset the\n> situation or `git add` to continue. The `aborting` output makes it\n> clear that you do not have to do the `--abort` and *cannot* do the\n> `add` because there was an implicit `--abort` done resulting from the\n> failure.\n\nBut this is not a `git merge`, this is a `git merge --ff-only`; they are\ndifferent operations. There *never* is a need for `--abort` with\n`git merge --ff-only`.\n\nAnyway, the error message is meant for `git fast-forward` which\ndefinitely doesn't need any `--abort`.\n\nInitially I created a new variable to have a different error message for\n`git merge --ff-only` and `git fast-forward`, precisely to avoid\nchanging the current error message of `git merge --ff-only` and thus\navoid any inertial comments like this one. But then I thought there was\nno need to complicate the series when both can be improved at once.\nApparently that's not the case.\n\nI guess I'll add it back.\n\nCheers.\n\n-- \nFelipe Contreras"},{"id":"429357","messageId":"60e4c7e33bca0_1c428120827@natae.notmuch","threadId":"56049","inReplyTo":"xmqq1r8bi2l0.fsf@gitster.g","subject":"Re: [RFC PATCH 01/35] merge: improve fatal fast-forward message","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-06T21:15:15Z","receivedAt":"2021-07-06T21:15:19Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Junio C Hamano wrote:\n> \"Randall S. Becker\" <rsbecker@nexbridge.com> writes:\n> \n> >>If you do:\n> >>\n> >>  % git merge --ff-only\n> >>  fatal: Not possible to fast-forward, aborting.\n> >>\n> >>That \"aborting\" part is redundant; we know `git merge` should abort\n> > if the fast-forward is not possible, we explicitely told git to do\n> > that.\n> >\n> > `git merge` is a special operation where errors (conflicts, for one)\n> > may leave the repository in a merge pending state where you\n> > subsequently may have to use `git merge --abort` to reset the\n> > situation or `git add` to continue. The `aborting` output makes it\n> > clear that you do not have to do the `--abort` and *cannot* do the\n> > `add` because there was an implicit `--abort` done resulting from the\n> > failure. This is important information for the user.\n> \n> If so, adding \", aborting\" to the end is misleading.  In this\n> particular failure mode, the command pretends that the merge did not\n> even start.\n\nThat's true.\n\nWhatever is the case for that \"aborting\" to be there I don't think it's\nadding any value.\n\n-- \nFelipe Contreras\n"},{"id":"429359","messageId":"04df01d772ad$c9ac7430$5d055c90$@nexbridge.com","threadId":"56049","inReplyTo":"60e4c71099ef_1c4281208ad@natae.notmuch","subject":"RE: [RFC PATCH 01/35] merge: improve fatal fast-forward message","fromName":"Randall S. Becker","fromEmail":"rsbecker@nexbridge.com","sentAt":"2021-07-06T21:27:51Z","receivedAt":"2021-07-06T21:28:02Z","isPatch":true,"sender":{"key":"randall.becker@nexbridge.ca","avatar":"https://avatars.githubusercontent.com/u/28956764?v=4"},"body":"On July 6, 2021 5:12 PM, Felipe Contreras wrote:\n>Randall S. Becker wrote:\n>> On July 6, 2021 4:40 PM, Felipe Contreras wrote:\n>> >Subject: Re: [RFC PATCH 01/35] merge: improve fatal fast-forward\n>> >message\n>> >\n>> >Ævar Arnfjörð Bjarmason wrote:\n>> >>\n>> >> On Mon, Jul 05 2021, Felipe Contreras wrote:\n>> >>\n>> >> > Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n>> >> > ---\n>> >> >  builtin/merge.c | 2 +-\n>> >> >  1 file changed, 1 insertion(+), 1 deletion(-)\n>> >> >\n>> >> > diff --git a/builtin/merge.c b/builtin/merge.c index\n>> >> > a8a843b1f5..05e631229d 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(_(\"unable to fast-forward\"));\n>> >>\n>> >> I read the existing message a bit more like \"this makes no sense\n>> >> anymore\" (correct) and the latter more like \"we encountered an error\".\n>> >\n>> >I mean, this is the documentation of --ff-only:\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>> >So if you do `git merge --ff-only` you are telling git: \"I want you to exit with an error when the fast-forward is not possible\".\n>> >\n>> >If you do:\n>> >\n>> >  % git merge --ff-only\n>> >  fatal: Not possible to fast-forward, aborting.\n>> >\n>> >That \"aborting\" part is redundant; we know `git merge` should abort if the fast-forward is not possible, we explicitely told git to do that.\n>>\n>> `git merge` is a special operation where errors (conflicts, for one)\n>> may leave the repository in a merge pending state where you\n>> subsequently may have to use `git merge --abort` to reset the\n>> situation or `git add` to continue. The `aborting` output makes it\n>> clear that you do not have to do the `--abort` and *cannot* do the\n>> `add` because there was an implicit `--abort` done resulting from the\n>> failure.\n>\n>But this is not a `git merge`, this is a `git merge --ff-only`; they are different operations. There *never* is a need for `--abort` with `git\n>merge --ff-only`.\n\nWell, you know that and I know that, but having to explain this to every new git user who will operationally use git merge --ff-only within hours or days of their first clone is a different matter.\n\n>Anyway, the error message is meant for `git fast-forward` which definitely doesn't need any `--abort`.\n>\n>Initially I created a new variable to have a different error message for `git merge --ff-only` and `git fast-forward`, precisely to avoid\n>changing the current error message of `git merge --ff-only` and thus avoid any inertial comments like this one. But then I thought there\n>was no need to complicate the series when both can be improved at once.\n>Apparently that's not the case.\n>\n>I guess I'll add it back.\n\nThanks. I understand the purpose here.\n\n-Randall\n\n"},{"id":"429360","messageId":"04e001d772ae$3d87e0b0$b897a210$@nexbridge.com","threadId":"56049","inReplyTo":"xmqq1r8bi2l0.fsf@gitster.g","subject":"RE: [RFC PATCH 01/35] merge: improve fatal fast-forward message","fromName":"Randall S. Becker","fromEmail":"rsbecker@nexbridge.com","sentAt":"2021-07-06T21:31:05Z","receivedAt":"2021-07-06T21:31:17Z","isPatch":true,"sender":{"key":"randall.becker@nexbridge.ca","avatar":"https://avatars.githubusercontent.com/u/28956764?v=4"},"body":"On July 6, 2021 4:56 PM, Junio C Hamano wrote:\n>\"Randall S. Becker\" <rsbecker@nexbridge.com> writes:\n>\n>>>If you do:\n>>>\n>>>  % git merge --ff-only\n>>>  fatal: Not possible to fast-forward, aborting.\n>>>\n>>>That \"aborting\" part is redundant; we know `git merge` should abort\n>> if the fast-forward is not possible, we explicitely told git to do\n>> that.\n>>\n>> `git merge` is a special operation where errors (conflicts, for one)\n>> may leave the repository in a merge pending state where you\n>> subsequently may have to use `git merge --abort` to reset the\n>> situation or `git add` to continue. The `aborting` output makes it\n>> clear that you do not have to do the `--abort` and *cannot* do the\n>> `add` because there was an implicit `--abort` done resulting from the\n>> failure. This is important information for the user.\n>\n>If so, adding \", aborting\" to the end is misleading.  In this particular failure mode, the command pretends that the merge did not even\n>start.\n\nYou know that, and I know that. I contend that git users do not generally want to care about failure modes. While a flow description might be instructive, I doubt many git users would look at it. We can only hope that front ends know how to make this clean for them.\n\n-Randall\n\n"},{"id":"429362","messageId":"xmqqsg0rglc2.fsf@gitster.g","threadId":"56049","inReplyTo":"04e001d772ae$3d87e0b0$b897a210$@nexbridge.com","subject":"Re: [RFC PATCH 01/35] merge: improve fatal fast-forward message","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2021-07-06T21:54:05Z","receivedAt":"2021-07-06T21:54:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Randall S. Becker\" <rsbecker@nexbridge.com> writes:\n\n> On July 6, 2021 4:56 PM, Junio C Hamano wrote:\n>>\"Randall S. Becker\" <rsbecker@nexbridge.com> writes:\n>>\n>>>>If you do:\n>>>>\n>>>>  % git merge --ff-only\n>>>>  fatal: Not possible to fast-forward, aborting.\n>>>>\n>>>>That \"aborting\" part is redundant; we know `git merge` should abort\n>>> if the fast-forward is not possible, we explicitely told git to do\n>>> that.\n>>>\n>>> `git merge` is a special operation where errors (conflicts, for one)\n>>> may leave the repository in a merge pending state where you\n>>> subsequently may have to use `git merge --abort` to reset the\n>>> situation or `git add` to continue. The `aborting` output makes it\n>>> clear that you do not have to do the `--abort` and *cannot* do the\n>>> `add` because there was an implicit `--abort` done resulting from the\n>>> failure. This is important information for the user.\n>>\n>>If so, adding \", aborting\" to the end is misleading.  In this particular failure mode, the command pretends that the merge did not even\n>>start.\n>\n> You know that, and I know that. I contend that git users do not\n> generally want to care about failure modes. While a flow\n> description might be instructive, I doubt many git users would\n> look at it. We can only hope that front ends know how to make this\n> clean for them.\n\nThey may not care about \"failure modes\" but they would want to know\nthat in this situation, unlike other times, they do not have to say\n\"git merge --abort\" (or \"git reset --hard\").  Saying \", aborting\"\ndoes not help as much as saying say \", not starting a merge\", for\nexample.\n\n"},{"id":"429370","messageId":"60e4d5b03dbe8_1c4281208b1@natae.notmuch","threadId":"56049","inReplyTo":"04df01d772ad$c9ac7430$5d055c90$@nexbridge.com","subject":"RE: [RFC PATCH 01/35] merge: improve fatal fast-forward message","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-06T22:14:08Z","receivedAt":"2021-07-06T22:14:12Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Randall S. Becker wrote:\n> On July 6, 2021 5:12 PM, Felipe Contreras wrote:\n\n> >But this is not a `git merge`, this is a `git merge --ff-only`; they\n> >are different operations. There *never* is a need for `--abort` with\n> >`git merge --ff-only`.\n> \n> Well, you know that and I know that, but having to explain this to\n> every new git user who will operationally use git merge --ff-only\n> within hours or days of their first clone is a different matter.\n\nNew users don't do `git merge --ff-only`, they very likely don't do\n`git merge` either, nor do they do `git pull --ff-only`. This is obvious\nfrom the countless threads where users do `git pull` without knowing\nthat they will create a true merge by mistake.\n\nEven less likely is that they will know about `git merge --abort`, and even\nless that they will know both `git merge --ff-only` and\n`git merge --abort`.\n\nEven even less likely that they will mistakenly expect\n`git merge --ff-only` to be left inside a temporary stated to be\nresolved by `git merge --abort`.\n\nBut as Junio said, *if* such a hypothetical user exists, we don't want\nto keep misleading them into thinking that a true merge was somehow\naborted, because it wasn't.\n\nIt's much better to simply let go the current error message.\n\nCheers.\n\n-- \nFelipe Contreras\n"},{"id":"429373","messageId":"04e201d772b5$f1812a20$d4837e60$@nexbridge.com","threadId":"56049","inReplyTo":"xmqqsg0rglc2.fsf@gitster.g","subject":"RE: [RFC PATCH 01/35] merge: improve fatal fast-forward message","fromName":"Randall S. Becker","fromEmail":"rsbecker@nexbridge.com","sentAt":"2021-07-06T22:26:14Z","receivedAt":"2021-07-06T22:26:24Z","isPatch":true,"sender":{"key":"randall.becker@nexbridge.ca","avatar":"https://avatars.githubusercontent.com/u/28956764?v=4"},"body":"On July 6, 2021 5:54 PM Junio C Hamano wrote:\n>\"Randall S. Becker\" <rsbecker@nexbridge.com> writes:\n>\n>> On July 6, 2021 4:56 PM, Junio C Hamano wrote:\n>>>\"Randall S. Becker\" <rsbecker@nexbridge.com> writes:\n>>>\n>>>>>If you do:\n>>>>>\n>>>>>  % git merge --ff-only\n>>>>>  fatal: Not possible to fast-forward, aborting.\n>>>>>\n>>>>>That \"aborting\" part is redundant; we know `git merge` should abort\n>>>> if the fast-forward is not possible, we explicitely told git to do\n>>>> that.\n>>>>\n>>>> `git merge` is a special operation where errors (conflicts, for one)\n>>>> may leave the repository in a merge pending state where you\n>>>> subsequently may have to use `git merge --abort` to reset the\n>>>> situation or `git add` to continue. The `aborting` output makes it\n>>>> clear that you do not have to do the `--abort` and *cannot* do the\n>>>> `add` because there was an implicit `--abort` done resulting from\n>>>> the failure. This is important information for the user.\n>>>\n>>>If so, adding \", aborting\" to the end is misleading.  In this\n>>>particular failure mode, the command pretends that the merge did not even start.\n>>\n>> You know that, and I know that. I contend that git users do not\n>> generally want to care about failure modes. While a flow description\n>> might be instructive, I doubt many git users would look at it. We can\n>> only hope that front ends know how to make this clean for them.\n>\n>They may not care about \"failure modes\" but they would want to know that in this situation, unlike other times, they do not have to say\n>\"git merge --abort\" (or \"git reset --hard\").  Saying \", aborting\"\n>does not help as much as saying say \", not starting a merge\", for example.\n\nI like that a lot, actually \"not starting a merge\" is much more helpful.\n\n"}]}