{"thread":{"id":"56140","subject":"[PATCH 0/7] fast-forward: new command with advice and documentation","startedAt":"2021-07-22T00:08:59Z","lastAt":"2021-07-23T20:14:33Z","messageCount":12,"participants":["Felipe Contreras","Philip Oakley"],"isPatch":true,"patchVersion":1,"patchTotal":7},"messages":[{"id":"430851","messageId":"20210722000854.1899129-1-felipe.contreras@gmail.com","threadId":"56140","inReplyTo":null,"subject":"[PATCH 0/7] fast-forward: new command with advice and documentation","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-22T00:08:47Z","receivedAt":"2021-07-22T00:08:59Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Fast-forwards have frequently been the subject of debate over the years.\nAre they a modifier? (adverb), are they a command? (verb), are they an\nobject? (noun).\n\nWhile these semantic debates are unlikely to go away, what is clear is\nthat people have wanted to do these fast-forwards since as far back as\n2008 [1].\n\nMoreover, all the discussions about the brokenness of `git pull` have\nlead to the conclusion that to synchronize changes there are three\nbasic actions users might want to do:\n\n  * fast-forward\n  * merge\n  * rebase\n\nWe have commands for the latter two, why not the former?\n\n`git fast-forward` is much more user friendly than\n`git merge --ff-only`, and additionally we can print an advice when the\nfast-forward is not possible, plus we can explain in the documentation\nwhat is a fast-forward, when to choose a merge, and when to choose a\nrebase:\n\n  git help fast-forward\n\nMoreover, this will help further efforts to fix `git pull` by\nsimplifying the mental model of users:\n\n  * git pull          -> git fast-forward\n  * git pull --merge  -> git merge\n  * git pull --rebase -> git rebase\n\nNot to mention other efforts like my proposed `git update` [2].\n\nThis is just the first part of my `git update` patch series [2], which\nactually received feedback so presumably there's a recognition of\nusefulness.\n\nI reordered the series to add the advice first due to comments from\nÆvar Arnfjörð Bjarmason, added more clarifications in the commit\nmessages, fixed one test, and improved the documentation.\n\nThis is v1 since the previous series was a RFC and contained a lot more\nchanges, but I'm attaching the range-diff of these patches anyway.\n\n[1] https://lore.kernel.org/git/loom.20080116T151930-575@post.gmane.org/\n[2] https://lore.kernel.org/git/20210705123209.1808663-1-felipe.contreras@gmail.com/\n\n1:  456cc92db3 ! 1:  c04cae2379 merge: improve fatal fast-forward message\n    @@ Metadata\n      ## Commit message ##\n         merge: improve fatal fast-forward message\n     \n    +    The documentation of --ff-only says:\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 when the user does --ff-only, and a fast-forward is not possible, the\n    +    user wants git to abort, therefore an error message of:\n    +\n    +     fatal: Not possible to fast-forward, aborting.\n    +\n    +    is redundant; no need to say \", aborting\".\n    +\n    +    Additionally, use lowercase and lose the full stop to be consistent with\n    +    other die() messages.\n    +\n         Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n     \n      ## builtin/merge.c ##\n5:  fbbde141b3 ! 2:  d99c5ec51e fast-forward: add advice for novices\n    @@ Metadata\n     Author: Felipe Contreras <felipe.contreras@gmail.com>\n     \n      ## Commit message ##\n    -    fast-forward: add advice for novices\n    +    merge: add diverging advice for novices\n     \n    -    It doesn't hurt showing it on `git merge --ff-only` too.\n    +    Diverging branches is one of the most confusing aspects of distributed\n    +    version control systems for novices, so a little help explaining what to\n    +    do on those situations will come in handy.\n    +\n    +    Right now this advice will be displayed only when the user does\n    +    `git merge --ff-only`, `git pull --ff-only`, or has configured\n    +    `pull.ff=only` for `git pull`, but in the future it's expected that\n    +    --ff-only will be the default for `git pull`, and a proposed `git\n    +    fast-forward` command will imply --ff-only, in addition to `git update`.\n    +\n    +    So it makes sense to add the advice in preparation for those future\n    +    changes, although even currently it helps.\n     \n         Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n     \n    @@ advice.h: void NORETURN die_resolve_conflict(const char *me);\n      #endif /* ADVICE_H */\n     \n      ## builtin/merge.c ##\n    -@@ builtin/merge.c: static int merge_common(int argc, const char **argv, const char *prefix,\n    +@@ builtin/merge.c: int cmd_merge(int argc, const char **argv, const char *prefix)\n      \t\t}\n      \t}\n      \n6:  75be93e2f6 ! 3:  7bb27c22ad fast-forward: make the advise configurable\n    @@ Metadata\n     Author: Felipe Contreras <felipe.contreras@gmail.com>\n     \n      ## Commit message ##\n    -    fast-forward: make the advise configurable\n    +    merge: make the diverging advise configurable\n     \n         With advice.diverging.\n     \n2:  4430c62ccc = 4:  d7943e9826 merge: split cmd_merge()\n3:  891462cc8b ! 5:  f70db9b464 fast-forward: add new builtin\n    @@ Commit message\n         This is one of the most common git operations, it makes sense it has its\n         own built-in.\n     \n    +    Additionally it's more user friendly than `git merge` because by default\n    +    does --ff-only and thus shows the diverging advice.\n    +\n    +    Moreover, the documentation about fast-forwards is scattered, by having\n    +    a standalone command users can be referred to it very simply:\n    +\n    +      git help fast-forward\n    +\n         This is basically the same as `git merge --ff-only` (for now).\n     \n         Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n    @@ t/t7600-merge.sh: test_expect_success 'merges with --ff-only' '\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    ++\tgit fast-forward c3 &&\n     +\tverify_head $c3\n     +'\n     +\n4:  111a1d7ca4 ! 6:  e4e64401b5 doc: fast-forward: explain what it is\n    @@ Documentation/git-fast-forward.txt: DESCRIPTION\n     +Then `git fast-forward` will advance the local `master` to `origin/master`:\n     +\n     +------------\n    -+    D---C---B---A master, origin/master\n    ++    D---C---B---A origin/master\n    ++\t\t^\n    ++\t\t|\n    ++\t      master\n     +------------\n     +\n    -+This operation is not always possible; if you made changes and the branches\n    -+have diverged:\n    ++This operation is not always possible; if you've made changes and the branches\n    ++diverged:\n     +\n     +------------\n     +    D---C---B---A origin/master\n7:  d9b0f2c60d ! 7:  d242ac06ae fast-forward: add help about merge vs. rebase\n    @@ Metadata\n      ## Commit message ##\n         fast-forward: add help about merge vs. rebase\n     \n    +    Now that we have a locus for merge versus rebase documentation, we can\n    +    refer to it on the diverging advice.\n    +\n         Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>\n     \n      ## Documentation/git-fast-forward.txt ##\n    @@ Documentation/git-fast-forward.txt: synchronize the two branches.\n     +MERGE OR REBASE\n     +---------------\n     +\n    -+The decision to whether merge or rebase depends on the situation, and the\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    @@ Documentation/git-fast-forward.txt: synchronize the two branches.\n     +------------\n     +\n     +The nature of distributed version control systems make this divergence\n    -+unavoidable. You must decide how to synchronize 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    ++Should you choose to merge, the two heads (master and origin/master) will be joined\n     +together in a new commit:\n     +\n     +------------\n    @@ Documentation/git-fast-forward.txt: synchronize the two branches.\n     +\t  X---Y---+\n     +------------\n     +\n    -+This new commit is called a merge commit and has two parents (A and Y).\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\nFelipe Contreras (7):\n  merge: improve fatal fast-forward message\n  merge: add diverging advice for novices\n  merge: make the diverging advise configurable\n  merge: split cmd_merge()\n  fast-forward: add new builtin\n  doc: fast-forward: explain what it is\n  fast-forward: add help about merge vs. rebase\n\n .gitignore                             |   1 +\n Documentation/config/advice.txt        |   2 +\n Documentation/git-fast-forward.txt     | 107 +++++++++++++++++++++++++\n Makefile                               |   1 +\n advice.c                               |  15 ++++\n advice.h                               |   2 +\n builtin.h                              |   1 +\n builtin/merge.c                        |  47 +++++++----\n contrib/completion/git-completion.bash |  10 +++\n git.c                                  |   1 +\n t/t7600-merge.sh                       |  21 +++++\n 11 files changed, 194 insertions(+), 14 deletions(-)\n create mode 100644 Documentation/git-fast-forward.txt\n\n-- \n2.32.0.40.gb9b36f9b52\n\n"},{"id":"430852","messageId":"20210722000854.1899129-2-felipe.contreras@gmail.com","threadId":"56140","inReplyTo":"20210722000854.1899129-1-felipe.contreras@gmail.com","subject":"[PATCH 1/7] merge: improve fatal fast-forward message","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-22T00:08:48Z","receivedAt":"2021-07-22T00:09:01Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"The documentation of --ff-only says:\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 when the user does --ff-only, and a fast-forward is not possible, the\nuser wants git to abort, therefore an error message of:\n\n fatal: Not possible to fast-forward, aborting.\n\nis redundant; no need to say \", aborting\".\n\nAdditionally, use lowercase and lose the full stop to be consistent with\nother die() messages.\n\nSigned-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.40.gb9b36f9b52\n\n"},{"id":"430853","messageId":"20210722000854.1899129-3-felipe.contreras@gmail.com","threadId":"56140","inReplyTo":"20210722000854.1899129-1-felipe.contreras@gmail.com","subject":"[PATCH 2/7] merge: add diverging advice for novices","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-22T00:08:49Z","receivedAt":"2021-07-22T00:09:05Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Diverging branches is one of the most confusing aspects of distributed\nversion control systems for novices, so a little help explaining what to\ndo on those situations will come in handy.\n\nRight now this advice will be displayed only when the user does\n`git merge --ff-only`, `git pull --ff-only`, or has configured\n`pull.ff=only` for `git pull`, but in the future it's expected that\n--ff-only will be the default for `git pull`, and a proposed `git\nfast-forward` command will imply --ff-only, in addition to `git update`.\n\nSo it makes sense to add the advice in preparation for those future\nchanges, although even currently it helps.\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 05e631229d..56a526f42c 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -1619,8 +1619,10 @@ int cmd_merge(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.40.gb9b36f9b52\n\n"},{"id":"430854","messageId":"20210722000854.1899129-4-felipe.contreras@gmail.com","threadId":"56140","inReplyTo":"20210722000854.1899129-1-felipe.contreras@gmail.com","subject":"[PATCH 3/7] merge: make the diverging advise configurable","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-22T00:08:50Z","receivedAt":"2021-07-22T00:09:07Z","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.40.gb9b36f9b52\n\n"},{"id":"430855","messageId":"20210722000854.1899129-5-felipe.contreras@gmail.com","threadId":"56140","inReplyTo":"20210722000854.1899129-1-felipe.contreras@gmail.com","subject":"[PATCH 4/7] merge: split cmd_merge()","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-22T00:08:51Z","receivedAt":"2021-07-22T00:09:08Z","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 56a526f42c..2770dabf22 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@@ -1740,3 +1737,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.40.gb9b36f9b52\n\n"},{"id":"430856","messageId":"20210722000854.1899129-6-felipe.contreras@gmail.com","threadId":"56140","inReplyTo":"20210722000854.1899129-1-felipe.contreras@gmail.com","subject":"[PATCH 5/7] fast-forward: add new builtin","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-22T00:08:52Z","receivedAt":"2021-07-22T00:09:14Z","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\nAdditionally it's more user friendly than `git merge` because by default\ndoes --ff-only and thus shows the diverging advice.\n\nMoreover, the documentation about fast-forwards is scattered, by having\na standalone command users can be referred to it very simply:\n\n  git help fast-forward\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 c7c46c017d..a05c309188 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -777,6 +777,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 2770dabf22..1836f98f82 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@@ -1742,3 +1751,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 4bdd27ddc8..ec00c20656 100644\n--- a/contrib/completion/git-completion.bash\n+++ b/contrib/completion/git-completion.bash\n@@ -1787,6 +1787,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..9df1931236 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 fast-forward 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.40.gb9b36f9b52\n\n"},{"id":"430857","messageId":"20210722000854.1899129-7-felipe.contreras@gmail.com","threadId":"56140","inReplyTo":"20210722000854.1899129-1-felipe.contreras@gmail.com","subject":"[PATCH 6/7] doc: fast-forward: explain what it is","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-22T00:08:53Z","receivedAt":"2021-07-22T00:09:15Z","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 | 35 +++++++++++++++++++++++++++++-\n 1 file changed, 34 insertions(+), 1 deletion(-)\n\ndiff --git a/Documentation/git-fast-forward.txt b/Documentation/git-fast-forward.txt\nindex d457022629..38c920964f 100644\n--- a/Documentation/git-fast-forward.txt\n+++ b/Documentation/git-fast-forward.txt\n@@ -15,11 +15,44 @@ 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 origin/master\n+\t\t^\n+\t\t|\n+\t      master\n+------------\n+\n+This operation is not always possible; if you've made changes and the branches\n+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.40.gb9b36f9b52\n\n"},{"id":"430858","messageId":"20210722000854.1899129-8-felipe.contreras@gmail.com","threadId":"56140","inReplyTo":"20210722000854.1899129-1-felipe.contreras@gmail.com","subject":"[PATCH 7/7] fast-forward: add help about merge vs. rebase","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-22T00:08:54Z","receivedAt":"2021-07-22T00:09:15Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Now that we have a locus for merge versus rebase documentation, we can\nrefer to it on the diverging advice.\n\nSigned-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 38c920964f..1989fdec4a 100644\n--- a/Documentation/git-fast-forward.txt\n+++ b/Documentation/git-fast-forward.txt\n@@ -50,6 +50,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+Should 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.40.gb9b36f9b52\n\n"},{"id":"430975","messageId":"76d59e73-ae5d-3bef-4aa3-5d1af9611577@iee.email","threadId":"56140","inReplyTo":"20210722000854.1899129-8-felipe.contreras@gmail.com","subject":"Re: [PATCH 7/7] fast-forward: add help about merge vs. rebase","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.email","sentAt":"2021-07-22T22:57:30Z","receivedAt":"2021-07-22T22:57:35Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"minor nit/query on format..\nOn 22/07/2021 01:08, Felipe Contreras wrote:\n> Now that we have a locus for merge versus rebase documentation, we can\n> refer to it on the diverging advice.\n>\n> 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>\n> diff --git a/Documentation/git-fast-forward.txt b/Documentation/git-fast-forward.txt\n> index 38c920964f..1989fdec4a 100644\n> --- a/Documentation/git-fast-forward.txt\n> +++ b/Documentation/git-fast-forward.txt\n> @@ -50,6 +50,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> +Should 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\n.. here, in my email reader, the arrow doesn't align to the commit\nbecause of the single char (+) indent relative to tab spacing. Does this\ncause any problems when formatted in the html/web style?\n\nIt was my impression that, for ASCII art diagrams, we used space\nindenting, leaving tabs for regular text indents. I quss you have an\nauto tab setting that converts the 8-spaces to tabs in the source txt.\n\nIt is good to have diagrams for the visual learners!\n\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]\n> diff --git a/advice.c b/advice.c\n> index 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--\nPhilip\n"},{"id":"431059","messageId":"60faf81658201_defb208d4@natae.notmuch","threadId":"56140","inReplyTo":"76d59e73-ae5d-3bef-4aa3-5d1af9611577@iee.email","subject":"Re: [PATCH 7/7] fast-forward: add help about merge vs. rebase","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-23T17:10:46Z","receivedAt":"2021-07-23T17:10:54Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Philip Oakley wrote:\n> minor nit/query on format..\n> On 22/07/2021 01:08, Felipe Contreras wrote:\n> > Now that we have a locus for merge versus rebase documentation, we can\n> > refer to it on the diverging advice.\n> >\n> > 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> >\n> > diff --git a/Documentation/git-fast-forward.txt b/Documentation/git-fast-forward.txt\n> > index 38c920964f..1989fdec4a 100644\n> > --- a/Documentation/git-fast-forward.txt\n> > +++ b/Documentation/git-fast-forward.txt\n> > @@ -50,6 +50,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> > +Should 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> \n> .. here, in my email reader, the arrow doesn't align to the commit\n> because of the single char (+) indent relative to tab spacing. Does this\n> cause any problems when formatted in the html/web style?\n\nNo, it looks fine.\n\nI don't have a problem with tabs or spaces, but git-pull.txt uses tabs.\n\n> It is good to have diagrams for the visual learners!\n\nActually, it seems there's no such thing as \"visual learners\" [1]; we\nare all visual learners.\n\nCheers.\n\n> > +    D---C---B---A---M master\n> > +\t \\\t   /\n> > +\t  X---Y---+\n> > +------------\n\n[1] https://www.theatlantic.com/science/archive/2018/04/the-myth-of-learning-styles/557687/\n\n-- \nFelipe Contreras\n"},{"id":"431085","messageId":"b5cdd34b-c50e-f6f4-4462-c869836d309d@iee.email","threadId":"56140","inReplyTo":"60faf81658201_defb208d4@natae.notmuch","subject":"Re: [PATCH 7/7] fast-forward: add help about merge vs. rebase","fromName":"Philip Oakley","fromEmail":"philipoakley@iee.email","sentAt":"2021-07-23T19:14:41Z","receivedAt":"2021-07-23T19:14:46Z","isPatch":true,"sender":{"key":"philipoakley@iee.email","avatar":"https://avatars.githubusercontent.com/u/914343?v=4"},"body":"On 23/07/2021 18:10, Felipe Contreras wrote:\n>> It is good to have diagrams for the visual learners!\n> Actually, it seems there's no such thing as \"visual learners\" [1]; we\n> are all visual learners.\n>\n> Cheers.\n>\n>>> +    D---C---B---A---M master\n>>> +\t \\\t   /\n>>> +\t  X---Y---+\n>>> +------------\n> [1] https://www.theatlantic.com/science/archive/2018/04/the-myth-of-learning-styles/557687/\nIt's a double myth, in that many try to suggest that a person has a\nsingle style (untrue), or that a single style is relevant in a\nparticular context, when it should be that sometime individuals fail to\nlearn (a particular item) when not provided with a particular style that\nwould suit them in the moment. I.e the removal of a particular format\n(e.g. no visuals) will reduce the effectiveness of the manual (excepting\nthe 'The pictures are better on radio' aphorism ;-).\n\nPhilip\n"},{"id":"431094","messageId":"60fb2324ef527_1073e208f4@natae.notmuch","threadId":"56140","inReplyTo":"b5cdd34b-c50e-f6f4-4462-c869836d309d@iee.email","subject":"Re: [PATCH 7/7] fast-forward: add help about merge vs. rebase","fromName":"Felipe Contreras","fromEmail":"felipe.contreras@gmail.com","sentAt":"2021-07-23T20:14:28Z","receivedAt":"2021-07-23T20:14:33Z","isPatch":true,"sender":{"key":"felipe.contreras@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8358?v=4"},"body":"Philip Oakley wrote:\n> On 23/07/2021 18:10, Felipe Contreras wrote:\n> >> It is good to have diagrams for the visual learners!\n> > Actually, it seems there's no such thing as \"visual learners\" [1]; we\n> > are all visual learners.\n> >\n> > Cheers.\n> >\n> >>> +    D---C---B---A---M master\n> >>> +\t \\\t   /\n> >>> +\t  X---Y---+\n> >>> +------------\n> > [1] https://www.theatlantic.com/science/archive/2018/04/the-myth-of-learning-styles/557687/\n> It's a double myth, in that many try to suggest that a person has a\n> single style (untrue), or that a single style is relevant in a\n> particular context, when it should be that sometime individuals fail to\n> learn (a particular item) when not provided with a particular style that\n> would suit them in the moment. I.e the removal of a particular format\n> (e.g. no visuals) will reduce the effectiveness of the manual (excepting\n> the 'The pictures are better on radio' aphorism ;-).\n\nThe myth (as I understand it) is that people have a primary learning\nstyle (e.g. \"I'm a visual learner\").\n\nThe truth is that everyone benefits from multiple styles.\n\nEither way--myth or no myth--I think we can agree it's better to have\nboth text and visuals.\n\nCheers.\n\n-- \nFelipe Contreras\n"}]}