{"thread":{"id":"12869","subject":"Re: [RFC/PATCH 3/4] Head reduction before selecting merge strategy","startedAt":"2008-03-26T03:58:26Z","lastAt":"2008-03-27T03:10:19Z","messageCount":4,"participants":["Sverre Hvammen Johansen","Jakub Narebski","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"73106","messageId":"402c10cd0803252058k2f35b33fr99ec7446235eeb6e@mail.gmail.com","threadId":"12869","inReplyTo":null,"subject":"Re: [RFC/PATCH 3/4] Head reduction before selecting merge strategy","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-03-26T03:58:26Z","receivedAt":"2008-03-26T03:58:26Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"See the documentation for an explanation of this feature.\n\nSigned-off-by: Sverre Hvammen Johansen <hvammen@gmail.com>\n---\n Documentation/git-merge.txt |   43 +++++++++++++++++++++++-\n git-merge.sh                |   76 +++++++++++++++++++++++++++++--------------\n 2 files changed, 93 insertions(+), 26 deletions(-)\n\ndiff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt\nindex 2af33d8..e94d26b 100644\n--- a/Documentation/git-merge.txt\n+++ b/Documentation/git-merge.txt\n@@ -36,7 +36,7 @@ include::merge-options.txt[]\n <remote>::\n        Other branch head merged into our branch.  You need at\n        least one <remote>.  Specifying more than one <remote>\n-       obviously means you are trying an Octopus.\n+       usually means you are trying an Octopus.\n\n\n include::fast-forward-options.txt[]\n@@ -133,6 +133,47 @@ merge (which is typically a fraction of the whole\ntree), you can\n have local modifications in your working tree as long as they do\n not overlap with what the merge updates.\n\n+If more than one commit are specified for the merge, git will try to\n+reduce the number of commits (real parents) by eliminating commits\n+than can be reached from other commits.  The commit message will\n+reflect the actual commits specified but the merge strategy will be\n+selected based on the real parents, but always including `HEAD`.  The\n+real parents (only including `HEAD` if it is real) are the parents\n+recorded in the merge commit object.\n+\n+The following shows master and three topic branches.  topicB is based\n+on topicA, topicA is previously branched off from master, and topicC\n+is based on the current `HEAD` of master:\n+\n+------------\n+                    o---o---o  topicB\n+                   /\n+          o---o---o  topicA\n+         /\n+    o---o---o---o---o---o  master\n+                         \\\n+                          o---o  topicC\n+------------\n+\n+A merger of master with topicA, topicB, and topicC will select the\n+merge strategy based on the three branches master, topicB, and topicC\n+(topicA is eliminated since it can be reached from topicB).  topicB\n+and topicC are the only real parents and are therefore the only\n+parents recorded in the merge commit object:\n+\n+------------\n+         % git checkout master\n+         % git merge topicA topicB topicC\n+\n+                    o---o---o  topicB\n+                   /         \\\n+          o---o---o  topicA   \\\n+         /                     \\\n+    o---o---o---o---o---o       o  master\n+                         \\     /\n+                          o---o  topicC\n+------------\n+\n When there are conflicts, these things happen:\n\n 1. `HEAD` stays the same.\ndiff --git a/git-merge.sh b/git-merge.sh\nindex 2acd2cc..5398606 100755\n--- a/git-merge.sh\n+++ b/git-merge.sh\n@@ -209,24 +209,41 @@ parse_config () {\n\n # Find real parents\n # Set the following variables as followd:\n-#   real_parents: The parents specified on the command line\n+#   real_parents: The real parents except fast forward of head\n #   common:       All common ancestors or not_queried\n #   ff_head:      Fast forward of head\n find_real_parents () {\n-       real_parents=$(git rev-parse \"$@\")\n-       real_parents=${real_parents#$LF}\n-       if test $# = 1\n+       if test $fast_forward = never\n        then\n-               common=$(git merge-base --all $head \"$@\")\n-               if test \"$common\" = $head\n+               real_parents=$(git rev-parse \"$@\")\n+               ff_head=$head\n+               common=not_queried\n+       else\n+               if test $# = 1\n                then\n-                       ff_head=$1\n+                       common=$(git merge-base --all $head \"$1\")\n+                       if test \"$common\" = $head\n+                       then\n+                               real_parents=\n+                               ff_head=$1\n+                       elif test \"$common\" = \"$1\"\n+                       then\n+                               real_parents=\n+                               ff_head=$head\n+                       else\n+                               real_parents=$1\n+                               ff_head=$head\n+\n+                       fi\n                else\n-                       ff_head=$head\n+                       real_parents=$(git show-branch --independent $head \"$@\")\n+                       # Here we may actually lie about which bransh\nis ff of head.\n+                       # This will preserve the order the user gave.\n+                       ff_head=${real_parents%%$LF*}\n+                       real_parents=${real_parents#$ff_head}\n+                       real_parents=${real_parents#$LF}\n+                       common=not_queried\n                fi\n-       else\n-               common=not_queried\n-               ff_head=$head\n        fi\n }\n\n@@ -319,6 +336,12 @@ set x $remoteheads ; shift\n\n find_real_parents \"$@\"\n\n+if test -n \"$real_parents\"\n+then\n+       test $head = $ff_head ||\n+               real_parents=\"$ff_head$LF$real_parents\"\n+fi\n+\n case \"$use_strategies\" in\n '')\n        case \"$real_parents\" in\n@@ -366,13 +389,13 @@ done\n\n echo \"$head\" >\"$GIT_DIR/ORIG_HEAD\"\n\n-if true\n+if test -z \"$real_parents\"\n then\n-       if test $head = $ff_head -a \"$common\" = \"$real_parents\"\n+       if test $head = $ff_head\n        then\n                finish_up_to_date \"Already up-to-date.\"\n                exit 0\n-       elif test $fast_forward != never -a $ff_head = \"$real_parents\"\n+       elif test $fast_forward != never\n        then\n                echo \"Updating $(git rev-parse --short $head)..$(git\nrev-parse --short $ff_head)\"\n                git update-index --refresh 2>/dev/null\n@@ -386,6 +409,14 @@ then\n                finish \"$new_head\" \"$msg\" || exit\n                dropsave\n                exit 0\n+       else\n+               real_parents=\"$ff_head\"\n+               ff_head=$head\n+       fi\n+else\n+       if test $head != $ff_head -a $fast_forward = never\n+       then\n+               real_parents=\"$ff_head$LF$real_parents\"\n        fi\n fi\n\n@@ -500,17 +531,12 @@ done\n # auto resolved the merge cleanly.\n if test '' != \"$result_tree\"\n then\n-    if test $fast_forward = allow\n-    then\n-        parents=$(git show-branch --independent \"$head\" \"$@\")\n-    else\n-        parents=$(git rev-parse \"$head\" \"$@\")\n-    fi\n-    parents=$(echo \"$parents\" | sed -e 's/^/-p /')\n-    result_commit=$(printf '%s\\n' \"$merge_msg\" | git commit-tree\n$result_tree $parents) || exit\n-    finish \"$result_commit\" \"Merge made by $wt_strategy.\"\n-    dropsave\n-    exit 0\n+       test $head = $ff_head && real_parents=\"$head$LF$real_parents\"\n+       parents=$(echo \"$real_parents\" | sed -e 's/^/-p /')\n+       result_commit=$(printf '%s\\n' \"$merge_msg\" | git commit-tree\n$result_tree $parents) || exit\n+       finish \"$result_commit\" \"Merge made by $wt_strategy.\"\n+       dropsave\n+       exit 0\n fi\n\n # Pick the result from the best strategy and have the user fix it up.\n\n-- \nSverre Hvammen Johansen\n"},{"id":"73136","messageId":"m3k5jpved8.fsf@localhost.localdomain","threadId":"12869","inReplyTo":"402c10cd0803252058k2f35b33fr99ec7446235eeb6e@mail.gmail.com","subject":"Re: [RFC/PATCH 3/4] Head reduction before selecting merge strategy","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2008-03-26T12:50:21Z","receivedAt":"2008-03-26T12:50:21Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"\"Sverre Hvammen Johansen\" <hvammen@gmail.com> writes:\n\n> See the documentation for an explanation of this feature.\n\nThat's good that the feature is documented. But I'd like to see 1.)\nwhy this feature is implemented, and perhaps also 2.) how this feature\nis implemented (for example: uses find_real_parents() function.\n \n> +If more than one commit are specified for the merge, git will try to\n> +reduce the number of commits (real parents) by eliminating commits\n> +than can be reached from other commits.  The commit message will\n> +reflect the actual commits specified but the merge strategy will be\n> +selected based on the real parents, but always including `HEAD`.  The\n> +real parents (only including `HEAD` if it is real) are the parents\n> +recorded in the merge commit object.\n\nBy \"real\" you mean \"reduced\" set of commits to merge?  This is not\nclear enough, IMHO.\n\nYou would have to defend that recording reduced set of parents is a\ngood idea (is it always done, or does --ff=never has side-effect of\nrecording _specified_ parents for a merge?).\n\n-- \nJakub Narebski\nPoland\nShadeHawk on #git\n"},{"id":"73140","messageId":"7vlk45e9xn.fsf@gitster.siamese.dyndns.org","threadId":"12869","inReplyTo":"402c10cd0803252058k2f35b33fr99ec7446235eeb6e@mail.gmail.com","subject":"Re: [RFC/PATCH 3/4] Head reduction before selecting merge strategy","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-03-26T16:17:56Z","receivedAt":"2008-03-26T16:17:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"\"Sverre Hvammen Johansen\" <hvammen@gmail.com> writes:\n\n> @@ -133,6 +133,47 @@ merge (which is typically a fraction of the whole\n> tree), you can\n>  have local modifications in your working tree as long as they do\n>  not overlap with what the merge updates.\n>\n> +If more than one commit are specified for the merge, git will try to\n> +reduce the number of commits (real parents) by eliminating commits\n> +than can be reached from other commits...\n\nIn 3/4 you defined \"real parents\" as \"the commits specified to be merged\nfrom the command line\", and you are picking only the independent ones out\nof \"real parents\" to change the set of parents used for the merge\noperation.  What is the reduced set called?\n\n> +...  The commit message will\n> +reflect the actual commits specified but the merge strategy will be\n> +selected based on the real parents, but always including `HEAD`....\n\nNow your terminology gets the other way around and reduced ones are called\n\"real\" and the earlier \"real parents\" are now called \"actual\".\n\nI think \"real\" vs \"actual\" is an invitation for \"which is which\"\nconfusion.  How about calling them \"given\" vs \"reduced\"?\n\nAnyway, \"the commit log message talks about the commits specified by the\nend user, but the command outsmarts the user and does something different\".\n\n> +... The\n> +real parents (only including `HEAD` if it is real) are the parents\n> +recorded in the merge commit object.\n\nSpecifically, \"does something different\" above is \"does not record some of\nthe commits given by the end user as parent commit of the resulting\nmerge\".  Hence the name of the operation: \"head reduction\".\n\nWhile I suspect that it would make sense to simplify parents, I do not\nsee why the seemingly deliberate discrepancy between what is recorded as\nthe parents (i.e. \"reduced parents\" on \"parent \" lines of the resulting\nmerge) and what the log message talks about (i.e. \"given parents\" you feed\nto fmt-merge-msg) is a good idea.  Isn't it more consistent and easier to\nexplain to the users if they match?  Also it might be arguable that this\nhead reduction should be an optional feature.\n\n> +The following shows master and three topic branches.  topicB is based\n> +on topicA, topicA is previously branched off from master, and topicC\n> +is based on the current `HEAD` of master:\n\nWe do not say \"HEAD of branch\".  HEAD spelled in all capital always means\n\"that pointer thing directly under $GIT_DIR that typically talks about\nwhich branch we are on but sometimes can be detached to name a commit\ndirectly.\"  Call it the \"tip of the master branch\".\n\n> +------------\n> +                    o---o---o  topicB\n> +                   /\n> +          o---o---o  topicA\n> +         /\n> +    o---o---o---o---o---o  master\n> +                         \\\n> +                          o---o  topicC\n> +------------\n> +\n> +A merger of master with topicA, topicB, and topicC will select the\n\n\"Merging topicA, B and C to the master branch will select\" may be easier\nto understand.\n\n> +merge strategy based on the three branches master, topicB, and topicC\n> +(topicA is eliminated since it can be reached from topicB).  topicB\n> +and topicC are the only real parents and are therefore the only\n> +parents recorded in the merge commit object:\n\n> +------------\n> +         % git checkout master\n> +         % git merge topicA topicB topicC\n\nPlease do not use C-shell in our examples.\n\n> +\n> +                    o---o---o  topicB\n> +                   /         \\\n> +          o---o---o  topicA   \\\n> +         /                     \\\n> +    o---o---o---o---o---o       o  master\n> +                         \\     /\n> +                          o---o  topicC\n> +------------\n\nI suspect this would be a _very_ unexpected behaviour to untrained eyes\nand would be a source of confusion.  You were on 'master' and merged many\nthings into it, but the resulting commit does not have 'master' as its\nfirst parent.  So far, ORIG_HEAD would always have matched HEAD^1 unless\nyou fast-forwarded.  This alone may be a reason enough that this behaviour\ncan never be the default.\n\n> diff --git a/git-merge.sh b/git-merge.sh\n> index 2acd2cc..5398606 100755\n> --- a/git-merge.sh\n> +++ b/git-merge.sh\n> @@ -209,24 +209,41 @@ parse_config () {\n> ...\n> +                       # This will preserve the order the user gave.\n> +                       ff_head=${real_parents%%$LF*}\n\n\"%%$LF*\"?  Heh, that's clever.\n"},{"id":"73176","messageId":"402c10cd0803262010x4d707de0h3e5b6b28b5ecaf12@mail.gmail.com","threadId":"12869","inReplyTo":"7vlk45e9xn.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFC/PATCH 3/4] Head reduction before selecting merge strategy","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-03-27T03:10:19Z","receivedAt":"2008-03-27T03:10:19Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"On Wed, Mar 26, 2008 at 9:17 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>  In 3/4 you defined \"real parents\" as \"the commits specified to be merged\n>  from the command line\", and you are picking only the independent ones out\n>  of \"real parents\" to change the set of parents used for the merge\n>  operation.  What is the reduced set called?\n\nI was not happy about the split.  2/4 does not make much sense until\nyou read 3/4, and when you read 3/4 you are confused by 2/4.  Is it OK\nthat I squash these together again?\n\n>  I think \"real\" vs \"actual\" is an invitation for \"which is which\"\n>  confusion.  How about calling them \"given\" vs \"reduced\"?\n\nAgree.  But reduced may not be reduced if --ff=never is specified.\n\n>  Anyway, \"the commit log message talks about the commits specified by the\n>  end user, but the command outsmarts the user and does something different\".\n\nThis is also the current behavior of git and I don't think anyone have\ncomplained about it until now that we realize how git is actually\ndoing this.  We want the history to be as simple as possible when\npresented in gitk, but the commit message should record what the user\nasked for.   The commit message is used for later refferense.  The\ncommit message will usually only contain branch names which may or may\nnot make sense when we later look back the history.  That two branches\nhappen to point to the same commit or one is a fast forward of the\nother is just a coincident.  I believe this is how most users want it\nand I don't intend to change the log message.\n\n>  > +... The\n>\n> > +real parents (only including `HEAD` if it is real) are the parents\n>  > +recorded in the merge commit object.\n>\n>  Specifically, \"does something different\" above is \"does not record some of\n>  the commits given by the end user as parent commit of the resulting\n>  merge\".  Hence the name of the operation: \"head reduction\".\n>\n>  While I suspect that it would make sense to simplify parents, I do not\n>  see why the seemingly deliberate discrepancy between what is recorded as\n>  the parents (i.e. \"reduced parents\" on \"parent \" lines of the resulting\n>  merge) and what the log message talks about (i.e. \"given parents\" you feed\n>  to fmt-merge-msg) is a good idea.  Isn't it more consistent and easier to\n>  explain to the users if they match?  Also it might be arguable that this\n>  head reduction should be an optional feature.\n\nIf you use --ff=never it is turned off.\n\n>  I suspect this would be a _very_ unexpected behaviour to untrained eyes\n>  and would be a source of confusion.  You were on 'master' and merged many\n>  things into it, but the resulting commit does not have 'master' as its\n>  first parent.  So far, ORIG_HEAD would always have matched HEAD^1 unless\n>  you fast-forwarded.  This alone may be a reason enough that this behaviour\n>  can never be the default.\n\nI am not sure we need to explain this in the manual.  What do you think?\n\nThis behavior is also the current behavior of git.  I don't think we\nshould care.  When we fast-forward, HEAD^1 may not match ORIG_HEAD\nanyway.\n\n-- \nSverre Hvammen Johansen\n"}]}