{"thread":{"id":"37682","subject":"[RFC/PATCH] git-merge: implement --ff-only-merge option.","startedAt":"2014-10-07T16:35:10Z","lastAt":"2014-10-08T10:02:41Z","messageCount":7,"participants":["Sergey Organov","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"250288","messageId":"1412699710-3480-1-git-send-email-sorganov@gmail.com","threadId":"37682","inReplyTo":null,"subject":"[RFC/PATCH] git-merge: implement --ff-only-merge option.","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2014-10-07T16:35:10Z","receivedAt":"2014-10-07T16:35:10Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"This option allows to create merge commit when fast-forward is\npossible, and abort otherwise. I.e. it's equivalent to --ff-only,\nexcept that it finally creates merge commit instead of\nfast-forwarding.\n\nOne may also consider this option to be equivalent to --no-ff with\nadditional check that the command without --no-ff  would indeed result\nin fast-forward.\n\nUseful to incorporate topic branch as single merge commit, ensuring\nthe left-side of the merge has no changes (our-diff-empty-merge).\n\nSigned-off-by: Sergey Organov <sorganov@gmail.com>\n---\n builtin/merge.c | 39 ++++++++++++++++++++++++++++++---------\n 1 file changed, 30 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex dff043d..39d0f1e 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -79,7 +79,8 @@ static const char *pull_twohead, *pull_octopus;\n enum ff_type {\n \tFF_NO,\n \tFF_ALLOW,\n-\tFF_ONLY\n+\tFF_ONLY,\n+\tFF_ONLY_MERGE\n };\n \n static enum ff_type fast_forward = FF_ALLOW;\n@@ -206,6 +207,9 @@ static struct option builtin_merge_options[] = {\n \t{ OPTION_SET_INT, 0, \"ff-only\", &fast_forward, NULL,\n \t\tN_(\"abort if fast-forward is not possible\"),\n \t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, FF_ONLY },\n+\t{ OPTION_SET_INT, 0, \"ff-only-merge\", &fast_forward, NULL,\n+\t\tN_(\"create merge commit when fast-forward is possible, abort otherwise\"),\n+\t\tPARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, FF_ONLY_MERGE },\n \tOPT_RERERE_AUTOUPDATE(&allow_rerere_auto),\n \tOPT_BOOL(0, \"verify-signatures\", &verify_signatures,\n \t\tN_(\"Verify that the named commit has a valid GPG signature\")),\n@@ -591,6 +595,8 @@ static int git_merge_config(const char *k, const char *v, void *cb)\n \t\t\tfast_forward = boolval ? FF_ALLOW : FF_NO;\n \t\t} else if (v && !strcmp(v, \"only\")) {\n \t\t\tfast_forward = FF_ONLY;\n+\t\t} else if (v && !strcmp(v, \"merge\")) {\n+\t\t\tfast_forward = FF_ONLY_MERGE;\n \t\t} /* do not barf on values from future versions of git */\n \t\treturn 0;\n \t} else if (!strcmp(k, \"merge.defaulttoupstream\")) {\n@@ -866,7 +872,7 @@ static int finish_automerge(struct commit *head,\n \n \tfree_commit_list(common);\n \tparents = remoteheads;\n-\tif (!head_subsumed || fast_forward == FF_NO)\n+\tif (!head_subsumed || (fast_forward == FF_NO || fast_forward == FF_ONLY_MERGE))\n \t\tcommit_list_insert(head, &parents);\n \tstrbuf_addch(&merge_msg, '\\n');\n \tprepare_to_commit(remoteheads);\n@@ -1162,6 +1168,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \tif (squash) {\n \t\tif (fast_forward == FF_NO)\n \t\t\tdie(_(\"You cannot combine --squash with --no-ff.\"));\n+\t\tif (fast_forward == FF_ONLY_MERGE)\n+\t\t\tdie(_(\"You cannot combine --squash with --ff-only-merge.\"));\n \t\toption_commit = 0;\n \t}\n \n@@ -1206,7 +1214,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t\t\t\"empty head\"));\n \t\tif (squash)\n \t\t\tdie(_(\"Squash commit into empty head not supported yet\"));\n-\t\tif (fast_forward == FF_NO)\n+\t\tif (fast_forward == FF_NO || fast_forward == FF_ONLY_MERGE)\n \t\t\tdie(_(\"Non-fast-forward commit does not make sense into \"\n \t\t\t    \"an empty head\"));\n \t\tremoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);\n@@ -1292,6 +1300,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\tsetenv(buf.buf, merge_remote_util(commit)->name, 1);\n \t\tstrbuf_reset(&buf);\n \t\tif (fast_forward != FF_ONLY &&\n+\t\t    fast_forward != FF_ONLY_MERGE &&\n \t\t    merge_remote_util(commit) &&\n \t\t    merge_remote_util(commit)->obj &&\n \t\t    merge_remote_util(commit)->obj->type == OBJ_TAG)\n@@ -1312,7 +1321,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \n \tfor (i = 0; i < use_strategies_nr; i++) {\n \t\tif (use_strategies[i]->attr & NO_FAST_FORWARD)\n-\t\t\tfast_forward = FF_NO;\n+\t\t\tif(fast_forward != FF_ONLY_MERGE)\n+\t\t\t\tfast_forward = FF_NO;\n \t\tif (use_strategies[i]->attr & NO_TRIVIAL)\n \t\t\tallow_trivial = 0;\n \t}\n@@ -1342,9 +1352,19 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t */\n \t\tfinish_up_to_date(\"Already up-to-date.\");\n \t\tgoto done;\n-\t} else if (fast_forward != FF_NO && !remoteheads->next &&\n-\t\t\t!common->next &&\n-\t\t\t!hashcmp(common->item->object.sha1, head_commit->object.sha1)) {\n+\t} else if (fast_forward != FF_NO &&\n+\t\t   !remoteheads->next &&\n+\t\t   !common->next &&\n+\t\t   !hashcmp(common->item->object.sha1, head_commit->object.sha1)) {\n+\n+\t\tif (fast_forward == FF_ONLY_MERGE) {\n+\t\t\t/*\n+\t\t\t * We are going to fast-forward, but options force us to create\n+\t\t\t * merge commit instead.\n+\t\t\t */\n+\t\t\tgoto commit;\n+\t\t}\n+\n \t\t/* Again the most common case of merging one remote. */\n \t\tstruct strbuf msg = STRBUF_INIT;\n \t\tstruct commit *commit;\n@@ -1389,7 +1409,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)\n \t\t * only one common.\n \t\t */\n \t\trefresh_cache(REFRESH_QUIET);\n-\t\tif (allow_trivial && fast_forward != FF_ONLY) {\n+\t\tif (allow_trivial && fast_forward != FF_ONLY && fast_forward != FF_ONLY_MERGE) {\n \t\t\t/* See if it is really trivial. */\n \t\t\tgit_committer_info(IDENT_STRICT);\n \t\t\tprintf(_(\"Trying really trivial in-index merge...\\n\"));\n@@ -1430,9 +1450,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 || fast_forward == FF_ONLY_MERGE)\n \t\tdie(_(\"Not possible to fast-forward, aborting.\"));\n \n+commit:\t\n \t/* We are going to make a new commit. */\n \tgit_committer_info(IDENT_STRICT);\n \n-- \n1.9.3\n"},{"id":"250295","messageId":"xmqqzjd7enet.fsf@gitster.dls.corp.google.com","threadId":"37682","inReplyTo":"1412699710-3480-1-git-send-email-sorganov@gmail.com","subject":"Re: [RFC/PATCH] git-merge: implement --ff-only-merge option.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-10-07T18:37:46Z","receivedAt":"2014-10-07T18:37:46Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Sergey Organov <sorganov@gmail.com> writes:\n\n> This option allows to create merge commit when fast-forward is\n> possible, and abort otherwise. I.e. it's equivalent to --ff-only,\n> except that it finally creates merge commit instead of\n> fast-forwarding.\n>\n> One may also consider this option to be equivalent to --no-ff with\n> additional check that the command without --no-ff  would indeed result\n> in fast-forward.\n>\n> Useful to incorporate topic branch as single merge commit, ensuring\n> the left-side of the merge has no changes (our-diff-empty-merge).\n>\n> Signed-off-by: Sergey Organov <sorganov@gmail.com>\n> ---\n\nThe workflow this implements sounds like \"because we can\", not\n\"because it will help us do X and Y and Z\".  Why would it be useful\nto limit the history to a shape where all merges are the ones that\ncould have been fast-forwarded?\n\nI cannot justify that sensibly myself, which in turn makes the\nfeature smell to me that it is encouraging a wrong workflow.\n"},{"id":"250311","messageId":"87fvezsk5k.fsf@osv.gnss.ru","threadId":"37682","inReplyTo":"xmqqzjd7enet.fsf@gitster.dls.corp.google.com","subject":"Re: [RFC/PATCH] git-merge: implement --ff-only-merge option.","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2014-10-07T20:24:23Z","receivedAt":"2014-10-07T20:24:23Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Sergey Organov <sorganov@gmail.com> writes:\n>\n>> This option allows to create merge commit when fast-forward is\n>> possible, and abort otherwise. I.e. it's equivalent to --ff-only,\n>> except that it finally creates merge commit instead of\n>> fast-forwarding.\n>>\n>> One may also consider this option to be equivalent to --no-ff with\n>> additional check that the command without --no-ff  would indeed result\n>> in fast-forward.\n>>\n>> Useful to incorporate topic branch as single merge commit, ensuring\n>> the left-side of the merge has no changes (our-diff-empty-merge).\n>>\n>> Signed-off-by: Sergey Organov <sorganov@gmail.com>\n>> ---\n>\n> The workflow this implements sounds like \"because we can\", not\n> \"because it will help us do X and Y and Z\".\n\nWell, I do have full time job, and while I think I can instantly invent\nquite a few things from the \"because we can\" camp, I usually don't.\n\n> Why would it be useful to limit the history to a shape where all\n> merges are the ones that could have been fast-forwarded?\n\nExcept by true merge, how else can I express with git that 'n'\nconsequitive commits constitute single logical change (being originally\nsome topic branch)? Now I just want such special kind of merge to be\nentirely trivial merge on one side. i.e. perfectly clean merge every\ntime.\n\nMoreover, as topic branches are usually rebased before merge anyway,\nwhy shouldn't I have simple capability to enforce it?\n\n> I cannot justify that sensibly myself, which in turn makes the\n> feature smell to me that it is encouraging a wrong workflow.\n\nWhat's wrong, exactly, in enforcing rebasing of topic branches before\nmerge? Basically, I need --ff-only, only I don't want git to forget that\nthis entire set of commits is logically single unit. Neither do I want\nto loose the structure of commits that --squash offers.\n\n-- Sergey.\n"},{"id":"250312","messageId":"xmqqegujeibx.fsf@gitster.dls.corp.google.com","threadId":"37682","inReplyTo":"87fvezsk5k.fsf@osv.gnss.ru","subject":"Re: [RFC/PATCH] git-merge: implement --ff-only-merge option.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-10-07T20:27:30Z","receivedAt":"2014-10-07T20:27:30Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Sergey Organov <sorganov@gmail.com> writes:\n\n>> Why would it be useful to limit the history to a shape where all\n>> merges are the ones that could have been fast-forwarded?\n>\n> Except by true merge, how else can I express with git that 'n'\n> consequitive commits constitute single logical change (being originally\n> some topic branch)?\n\nYou are justifying --no-ff, aren't you?\n\n> Moreover, as topic branches are usually rebased before merge anyway,\n> why shouldn't I have simple capability to enforce it?\n\nBecause rebasing immediately before is considered a bad manner,\ni.e. encouraging a wrong workflow?\n"},{"id":"250315","messageId":"87a957obm6.fsf@osv.gnss.ru","threadId":"37682","inReplyTo":"xmqqegujeibx.fsf@gitster.dls.corp.google.com","subject":"Re: [RFC/PATCH] git-merge: implement --ff-only-merge option.","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2014-10-07T20:42:25Z","receivedAt":"2014-10-07T20:42:25Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Sergey Organov <sorganov@gmail.com> writes:\n>\n>>> Why would it be useful to limit the history to a shape where all\n>>> merges are the ones that could have been fast-forwarded?\n>>\n>> Except by true merge, how else can I express with git that 'n'\n>> consequitive commits constitute single logical change (being originally\n>> some topic branch)?\n>\n> You are justifying --no-ff, aren't you?\n\nYes, and I already said it's close. But I don't want such merge commit\nto contain any non-trivialities. Currently I check it manually before\nissuing \"merge --no-ff\" and was hoping for some automation.\n\n>\n>> Moreover, as topic branches are usually rebased before merge anyway,\n>> why shouldn't I have simple capability to enforce it?\n>\n> Because rebasing immediately before is considered a bad manner,\n> i.e. encouraging a wrong workflow?\n\nWhy? What is wrong about it?\n\nPlease also notice that I don't try to impose this on anybody who does\nconsider it wrong workflow.\n\n-- \nSergey.\n"},{"id":"250321","messageId":"xmqqwq8bd0hw.fsf@gitster.dls.corp.google.com","threadId":"37682","inReplyTo":"87a957obm6.fsf@osv.gnss.ru","subject":"Re: [RFC/PATCH] git-merge: implement --ff-only-merge option.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2014-10-07T21:38:03Z","receivedAt":"2014-10-07T21:38:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Sergey Organov <sorganov@gmail.com> writes:\n\n>> Because rebasing immediately before is considered a bad manner,\n>> i.e. encouraging a wrong workflow?\n>\n> Why? What is wrong about it?\n\nSearching the kernel archive for messages that talks about \"rebase\"\nand \"pull-request\" from Linus would tell us why it is frowned upon\nin a prominent early adopter project of Git.\n\nYou destroy what you have been testing and replace it with an\nuntested one.  If you merge, and if the result of the merge is\nbroken, at least you would have something that used to work at its\nsecond parent (i.e. the tip of your topic).\n\n> Please also notice that I don't try to impose this on anybody who does\n> consider it wrong workflow.\n\nI know ;-).  I didn't say anything about \"imposing\", did I?\n\nHaving an option to make it easy to do something undesirable gives\npeople an excuse to say \"See Git has this option to let me do that\neasily, it is an officially sanctioned and encouraged workflow\".\n"},{"id":"250349","messageId":"87mw96g9q6.fsf@osv.gnss.ru","threadId":"37682","inReplyTo":"xmqqwq8bd0hw.fsf@gitster.dls.corp.google.com","subject":"Re: [RFC/PATCH] git-merge: implement --ff-only-merge option.","fromName":"Sergey Organov","fromEmail":"sorganov@gmail.com","sentAt":"2014-10-08T10:02:41Z","receivedAt":"2014-10-08T10:02:41Z","isPatch":true,"sender":{"key":"sorganov@gmail.com","avatar":"https://avatars.githubusercontent.com/u/8501568?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Sergey Organov <sorganov@gmail.com> writes:\n>\n>>> Because rebasing immediately before is considered a bad manner,\n>>> i.e. encouraging a wrong workflow?\n>>\n>> Why? What is wrong about it?\n>\n> Searching the kernel archive for messages that talks about \"rebase\"\n> and \"pull-request\" from Linus would tell us why it is frowned upon\n> in a prominent early adopter project of Git.\n\nI don't argue it's wrong for work-flows used for Linux kernel.\n\nEven in general, once pull-request is issued, it's bad idea to rebase\nyour work, as you've already published your history.\n\nI don't see how it is relevant to my proposal though. It was bad idea in\ngeneral to rewrite published history before this patch, it still is with\nthis patch, and the patch itself doesn't encourage anybody to rebase\npublished history as it is about true merge, not about rebase.\n\n> You destroy what you have been testing and replace it with an untested\n> one. If you merge, and if the result of the merge is broken, at least\n> you would have something that used to work at its second parent (i.e.\n> the tip of your topic).\n\nThis seems to apply to rebasing in general. I have nothing against\nwork-flows that don't routinely use rebasing, but there are other\nwork-flows around as well. \n\nIn my workflows, I do test the result of rebase, and I'd simply revert\nthe rebase if the result appears to be broken. When the result is OK, I\ndo the final merge. If it is perfectly trivial merge, it needs no\ntesting at all as it doesn't change content. It's this latter condition\nthat my suggested new option for \"git merge\" tries to ensure.\n\nHopefully we are not going into arguing whose work flow is better.\n\n>> Please also notice that I don't try to impose this on anybody who does\n>> consider it wrong workflow.\n>\n> I know ;-).  I didn't say anything about \"imposing\", did I?\n>\n> Having an option to make it easy to do something undesirable gives\n> people an excuse to say \"See Git has this option to let me do that\n> easily, it is an officially sanctioned and encouraged workflow\".\n\nWhatever you can do \"easily\" with my patch, you can do \"easily\" with\n--no-ff right now, except that with my option instead of --no-ff, you\nwon't get non-trivial merges you didn't intend.\n\nSo I rather see the message of my proposal as: provided you rebase your\n(topic) branches anyway, you can use this feature to keep essential\ninformation in the history and avoid complications of non-trivial\nmerges.\n\n-- \nSergey.\n"}]}