{"thread":{"id":"11701","subject":"[RFC/PATCH] Merge strategy single","startedAt":"2008-01-22T08:33:25Z","lastAt":"2008-01-22T08:33:25Z","messageCount":1,"participants":["Sverre Hvammen Johansen"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"66289","messageId":"402c10cd0801220033x764f0f80g9b0516927e6e591e@mail.gmail.com","threadId":"11701","inReplyTo":null,"subject":"[RFC/PATCH] Merge strategy single","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-01-22T08:33:25Z","receivedAt":"2008-01-22T08:33:25Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"A patch for this feature is attached.  I intend to write more tests\nfor this patch next weekend.  Any comments are welcome.\n\n-- \nSverre Hvammen Johansen\n\n\nFrom 1283f5ea17b493a9224fa7a65bb233962bc9ea41 Mon Sep 17 00:00:00 2001\nFrom: Sverre Hvammen Johansen <sj@black.local>\nDate: Tue, 22 Jan 2008 00:29:37 -0800\nSubject: [PATCH] Merge strategy single\n\nA new merge strategy, single is introduces.  This merge\nstrategy fails if the specified heads and HEAD can not\nbe reduced down to only one real parent.  The only allowed\noutcome is a fast forward unless HEAD is up to date with\nthe specified heads.\n\nThis patch also uses the real heads found instead of those\nspecified for real merges.  This means that merge startegies\nthat only take two heads can now accept more than two heads\nif they can be reduced down to only two real heads.\n\nKnown issues:  More tests are needed.  Add documentation.\nBetter handling of fast forward of HEAD when doing real merge.\n\nSigned-off-by: Sverre Hvammen Johansen <hvammen@gmail.com>\n---\n git-merge.sh     |  238 ++++++++++++++++++++++++++++++++++++------------------\n t/t7600-merge.sh |  172 +++++++++++++++++++++++++++++++++++++++\n 2 files changed, 332 insertions(+), 78 deletions(-)\n\ndiff --git a/git-merge.sh b/git-merge.sh\nindex 1c123a3..57f2f00 100755\n--- a/git-merge.sh\n+++ b/git-merge.sh\n@@ -28,7 +28,7 @@ test -z \"$(git ls-files -u)\" ||\n LF='\n '\n \n-all_strategies='recur recursive octopus resolve stupid ours subtree'\n+all_strategies='single recur recursive octopus resolve stupid ours subtree'\n default_twohead_strategies='recursive'\n default_octopus_strategies='octopus'\n no_fast_forward_strategies='subtree ours'\n@@ -167,7 +167,12 @@ parse_config () {\n \t\t\tshift\n \t\t\tcase \" $all_strategies \" in\n \t\t\t*\" $1 \"*)\n-\t\t\t\tuse_strategies=\"$use_strategies$1 \" ;;\n+\t\t\t\tif test \"$use_strategies\" = single -o \"$1\" = single -o -z \"$use_strategies\"\n+\t\t\t\tthen\n+\t\t\t\t\tuse_strategies=\"$1\"\n+\t\t\t\telse\n+\t\t\t\t\tuse_strategies=\"$use_strategies $1\"\n+\t\t\t\tfi ;;\n \t\t\t*)\n \t\t\t\tdie \"available strategies are: $all_strategies\" ;;\n \t\t\tesac\n@@ -274,24 +279,113 @@ do\n done\n set x $remoteheads ; shift\n \n+echo \"$head\" >\"$GIT_DIR/ORIG_HEAD\"\n+\n+find_one_real_parent () {\n+\t# The real parent candidate\n+\treal_parent=$1\n+\tshift\n+\n+\t# Other parents that are indepent of the real parent candidate\n+\tother_parents=\n+\n+\t# Parents that need further processing to determine whether\n+\t# they are independent parents of the parent candidate or not\n+\tparents_x=\n+\n+\twhile test $# -gt 0\n+\tdo\n+\t\tif test $real_parent = $1\n+\t\tthen\n+\t\t\t# Found a parent that is equal to the real\n+\t\t\t# parent candidate\n+\t\t\techo \"Duplicate $(git rev-parse --short $1)\"\n+\t\telse\n+\t\t\tcommon_b=$(git merge-base --all $real_parent $1)\n+\t\t\n+\t\t\tif test $common_b = $1\n+\t\t\tthen\n+\t\t\t\t# Found a parent that is not\n+\t\t\t\t# independent of the real parent\n+\t\t\t\t# candidate\n+\t\t\t\techo \"Possible ff $(git rev-parse --short $1)..$(git rev-parse --short $real_parent).\"\n+\t\t\telif test $common_b = $real_parent\n+\t\t\tthen\n+\t\t\t\t# Found a better real parent candidate\n+\t\t\t\techo \"Possible ff $(git rev-parse --short $real_parent)..$(git rev-parse --short $1).\"\n+\t\t\t\treal_parent=$1\n+\t\t\t\tparents_x=\"$other_parents\"\n+\t\t\t\tother_parents=\n+\t\t\telse\n+\t\t\t\t# Found a parent that is independent\n+\t\t\t\t# of the real parent candidate\n+\t\t\t\tother_parents=\"$other_parents $1\"\n+\t\t\tfi\n+\t\tfi\n+\t\tshift\n+\tdone\n+\n+\t# We have a real parent, some parents we know is independt of\n+\t# this real parent, and some parents that need further\n+\t# processing.\n+\n+\tfor b in $parents_x\n+\tdo\n+\t\tcommon_b=$(git merge-base --all $first_parent $b)\n+\t\tif test $common_b != $b\n+\t\tthen\n+\t\t\tother_parents=\"$other_parents $b\"\n+\t\tfi\n+\tdone\n+\n+\t# We have a real parent and other parents we know is independt\n+\t# of this real parent\n+}\n+\n+find_real_parents () {\n+    find_one_real_parent $head \"$@\"\n+    ff_head=$real_parent\n+    real_parents=\n+\n+    while test -n \"$other_parents\"\n+    do\n+\tfind_one_real_parent $other_parents\n+\treal_parents=\"$real_parents $real_parent\"\n+    done\n+}\n+\n+find_real_parents \"$@\"\n+\n+if test -n \"$real_parents\" -a \"$use_strategies\" != single -a $head != $ff_head\n+then\n+\t# We currently don't handle ff_head\n+\tif test -n \"$real_parents\"\n+\tthen\n+\t\treal_parents=\"$ff_head $real_parents\"\n+\telse\n+\t\treal_parents=$ff_head\n+\tfi\t\n+\tff_head=$head\n+fi\n+\n case \"$use_strategies\" in\n '')\n-\tcase \"$#\" in\n-\t1)\n-\t\tvar=\"`git config --get pull.twohead`\"\n+\tcase \"$real_parents\" in\n+\t?*\" \"?*)\n+\t\tvar=\"`git config --get pull.octopus`\"\n \t\tif test -n \"$var\"\n \t\tthen\n \t\t\tuse_strategies=\"$var\"\n \t\telse\n-\t\t\tuse_strategies=\"$default_twohead_strategies\"\n+\t\t\tuse_strategies=\"$default_octopus_strategies\"\n \t\tfi ;;\n \t*)\n-\t\tvar=\"`git config --get pull.octopus`\"\n+\t\tvar=\"`git config --get pull.twohead`\"\n \t\tif test -n \"$var\"\n \t\tthen\n \t\t\tuse_strategies=\"$var\"\n \t\telse\n-\t\t\tuse_strategies=\"$default_octopus_strategies\"\n+\t\t\tuse_strategies=\"$default_twohead_strategies\"\n \t\tfi ;;\n \tesac\n \t;;\n@@ -319,87 +413,75 @@ do\n \tdone\n done\n \n-case \"$#\" in\n-1)\n-\tcommon=$(git merge-base --all $head \"$@\")\n-\t;;\n-*)\n-\tcommon=$(git show-branch --merge-base $head \"$@\")\n-\t;;\n-esac\n-echo \"$head\" >\"$GIT_DIR/ORIG_HEAD\"\n-\n-case \"$allow_fast_forward,$#,$common,$no_commit\" in\n-?,*,'',*)\n-\t# No common ancestors found. We need a real merge.\n-\t;;\n-?,1,\"$1\",*)\n-\t# If head can reach all the merge then we are up to date.\n-\t# but first the most common case of merging one remote.\n+if test -n \"$real_parents\"\n+then\n+\tif test \"$use_strategies\" = single\n+\tthen\n+\t\tdie \"Merge strategy single can not handle more than one real parent\"\n+\tfi\n+elif test $head = $ff_head\n+then\n \tfinish_up_to_date \"Already up-to-date.\"\n \texit 0\n-\t;;\n-t,1,\"$head\",*)\n-\t# Again the most common case of merging one remote.\n-\techo \"Updating $(git rev-parse --short $head)..$(git rev-parse --short $1)\"\n+elif test $allow_fast_forward = t\n+then\n+\techo \"Updating $(git rev-parse --short $head)..$(git rev-parse --short $ff_head)\"\n \tgit update-index --refresh 2>/dev/null\n \tmsg=\"Fast forward\"\n \tif test -n \"$have_message\"\n \tthen\n \t\tmsg=\"$msg (no commit created; -m option ignored)\"\n \tfi\n-\tnew_head=$(git rev-parse --verify \"$1^0\") &&\n+\tnew_head=$(git rev-parse --verify \"$ff_head^0\") &&\n \tgit read-tree -v -m -u --exclude-per-directory=.gitignore $head \"$new_head\" &&\n \tfinish \"$new_head\" \"$msg\" || exit\n \tdropsave\n \texit 0\n-\t;;\n-?,1,?*\"$LF\"?*,*)\n-\t# We are not doing octopus and not fast forward.  Need a\n-\t# real merge.\n-\t;;\n-?,1,*,)\n-\t# We are not doing octopus, not fast forward, and have only\n-\t# one common.\n-\tgit update-index --refresh 2>/dev/null\n-\tcase \"$allow_trivial_merge\" in\n-\tt)\n-\t\t# See if it is really trivial.\n-\t\tgit var GIT_COMMITTER_IDENT >/dev/null || exit\n-\t\techo \"Trying really trivial in-index merge...\"\n-\t\tif git read-tree --trivial -m -u -v $common $head \"$1\" &&\n-\t\t   result_tree=$(git write-tree)\n-\t\tthen\n-\t\t\techo \"Wonderful.\"\n-\t\t\tresult_commit=$(\n-\t\t\t\tprintf '%s\\n' \"$merge_msg\" |\n-\t\t\t\tgit commit-tree $result_tree -p HEAD -p \"$1\"\n-\t\t\t) || exit\n-\t\t\tfinish \"$result_commit\" \"In-index merge\"\n-\t\t\tdropsave\n-\t\t\texit 0\n-\t\tfi\n-\t\techo \"Nope.\"\n-\tesac\n-\t;;\n-*)\n-\t# An octopus.  If we can reach all the remote we are up to date.\n-\tup_to_date=t\n-\tfor remote\n-\tdo\n-\t\tcommon_one=$(git merge-base --all $head $remote)\n-\t\tif test \"$common_one\" != \"$remote\"\n-\t\tthen\n-\t\t\tup_to_date=f\n-\t\t\tbreak\n-\t\tfi\n-\tdone\n-\tif test \"$up_to_date\" = t\n+else\n+\tif test \"$use_strategies\" = \"single\"\n \tthen\n-\t\tfinish_up_to_date \"Already up-to-date. Yeeah!\"\n-\t\texit 0\n+\t\tdie \"Merge strategy single can not fast forward when --no-ff is specified\"\n+\telse\n+\t\treal_parents=$ff_head\n+\t\tff_head=$head\n \tfi\n+fi\n+\n+case \"$real_parents\" in\n+?*\" \"?*)\n+\t# We have more than one parent\n+\tcommon=$(git show-branch --merge-base $head $real_parents)\n \t;;\n+*)\n+\t# We have exactly one parent\n+\tcommon=$(git merge-base --all $ff_head $real_parents)\n+\tcase \"$common\" in\n+\t?*\"$LF\"?*)\n+\t\t# We are not doing octopus and not fast forward.  Need a\n+\t\t# real merge.\n+\t\t;;\n+\t*)\n+\t\tgit update-index --refresh 2>/dev/null\n+\t\tif test \"$allow_trivial_merge\" = t\n+\t\tthen\n+\t\t\t# See if it is really trivial.\n+\t\t\tgit var GIT_COMMITTER_IDENT >/dev/null || exit\n+\t\t\techo \"Trying really trivial in-index merge...\"\n+\t\t\tif git read-tree --trivial -m -u -v $common $head $real_parents &&\n+\t\t\t\tresult_tree=$(git write-tree)\n+\t\t\tthen\n+\t\t\t\techo \"Wonderful.\"\n+\t\t\t\tresult_commit=$(\n+\t\t\t\t\tprintf '%s\\n' \"$merge_msg\" |\n+\t\t\t\t\tgit commit-tree $result_tree -p HEAD -p $real_parents\n+\t\t\t\t) || exit\n+\t\t\t\tfinish \"$result_commit\" \"In-index merge\"\n+\t\t\t\tdropsave\n+\t\t\t\texit 0\n+\t\t\tfi\n+\t\t\techo \"Nope.\"\n+\t\tfi ;;\n+\tesac ;;\n esac\n \n # We are going to make a new commit.\n@@ -440,7 +522,7 @@ do\n     # Remember which strategy left the state in the working tree\n     wt_strategy=$strategy\n \n-    git-merge-$strategy $common -- \"$head_arg\" \"$@\"\n+    git-merge-$strategy $common -- \"$head_arg\" $real_parents\n     exit=$?\n     if test \"$no_commit\" = t && test \"$exit\" = 0\n     then\n@@ -478,9 +560,9 @@ if test '' != \"$result_tree\"\n then\n     if test \"$allow_fast_forward\" = \"t\"\n     then\n-        parents=$(git show-branch --independent \"$head\" \"$@\")\n+        parents=$(git show-branch --independent \"$head\" $real_parents)\n     else\n-        parents=$(git rev-parse \"$head\" \"$@\")\n+        parents=$(git rev-parse \"$head\" $real_parents)\n     fi\n     parents=$(echo \"$parents\" | sed -e 's/^/-p /')\n     result_commit=$(printf '%s\\n' \"$merge_msg\" | git commit-tree $result_tree $parents) || exit\n@@ -510,7 +592,7 @@ case \"$best_strategy\" in\n \techo \"Rewinding the tree to pristine...\"\n \trestorestate\n \techo \"Using the $best_strategy to prepare resolving by hand.\"\n-\tgit-merge-$best_strategy $common -- \"$head_arg\" \"$@\"\n+\tgit-merge-$best_strategy $common -- \"$head_arg\" $real_parents\n \t;;\n esac\n \ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex 50c51c8..fe01941 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -57,6 +57,18 @@ cat >file.9 <<EOF\n 9 X\n EOF\n \n+cat  >result.0 <<EOF\n+1\n+2\n+3\n+4\n+5\n+6\n+7\n+8\n+9\n+EOF\n+\n cat  >result.1 <<EOF\n 1 X\n 2\n@@ -437,4 +449,164 @@ test_expect_success 'merge c0 with c1 (ff overrides no-ff)' '\n \n test_debug 'gitk --all'\n \n+test_expect_success 'merge c0 with c1 (-s single in config)' '\n+\tgit reset --hard c0 &&\n+\tgit config branch.master.mergeoptions \"-s single\" &&\n+\tgit merge c1 &&\n+\ttest_tick &&\n+\tverify_merge file result.1 &&\n+\tverify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with c0 (--strategy=single in config)' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"--strategy single\" &&\n+\tgit merge c0 &&\n+\tverify_merge file result.1 &&\n+\tverify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with c2 (--strategy=single in config)' '\n+\tgit reset --hard c1 &&\n+\ttest_tick &&\n+\tgit config branch.master.mergeoptions \"--strategy single\" &&\n+\tif git merge c2\n+\tthen\n+\t\tfalse\n+\telse\n+\t\tverify_merge file result.1 &&\n+\t\tverify_head $c1\n+\tfi\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with c1 (strategy=single)' '\n+\tgit reset --hard c0 &&\n+\ttest_tick &&\n+\tgit merge c1 --strategy=single &&\n+\tverify_merge file result.1 &&\n+\tverify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with c0 (strategy single)' '\n+\tgit reset --hard c1 &&\n+\ttest_tick &&\n+\tgit merge c0 --strategy single &&\n+\tverify_merge file result.1 &&\n+\tverify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with c2 (-s single)' '\n+\tgit reset --hard c1 &&\n+\ttest_tick &&\n+\tif git merge c2 -s single\n+\tthen\n+\t\tfalse\n+\telse\n+\t\tverify_merge file result.1 &&\n+\t\tverify_head $c1\n+\tfi\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with c1 and c2 (-s single)' '\n+\tgit reset --hard c0 &&\n+\tif git merge c1 c2 -s single\n+\tthen\n+\t\tfalse\n+\telse\n+\t\tverify_merge file result.0 &&\n+\t\tverify_head $c0\n+\tfi\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with c0 (-s single and no-ff)' '\n+\tgit reset --hard c1 &&\n+\ttest_tick &&\n+\tgit merge -s single --no-ff c0 &&\n+\tverify_merge file result.1 &&\n+\tverify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with c2 (--strategy=single and no-ff)' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"--no-ff\" &&\n+\ttest_tick &&\n+\tif git merge c2 --strategy=single\n+\tthen\n+\t\tfalse\n+\telse\n+\t\tverify_merge file result.1 &&\n+\t\tverify_head $c1\n+\tfi\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with c1 (no-ff and -s single)' '\n+\tgit reset --hard c0 &&\n+\tgit config branch.master.mergeoptions \"-s single\" &&\n+\ttest_tick &&\n+\tif git merge --no-ff c1\n+\tthen\n+\t\tfalse\n+\telse\n+\t\tverify_merge file result.0 &&\n+\t\tverify_head $c0\n+\tfi\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with c2 (ff and -s single)' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"-s single\" &&\n+\ttest_tick &&\n+\tif git merge --ff c2\n+\tthen\n+\t\tfalse\n+\telse\n+\t\tverify_merge file result.1 &&\n+\t\tverify_head $c1\n+\tfi\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with c1 and c2' '\n+\tgit reset --hard c0 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge c1 c2 &&\n+\tverify_merge file result.1-5 &&\n+\tverify_parents $c1 $c2\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with c0, c2, c0, and c1' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge c0 c2 c0 c1 &&\n+\tverify_merge file result.1-5 &&\n+\tverify_parents $c1 $c2\n+'\n+\n+test_debug 'gitk --all'\n+\n test_done\n-- \n1.5.3.3\n\n"}]}