{"thread":{"id":"13487","subject":"[PATCH] Fast forward strategies allow, never, and only","startedAt":"2008-05-12T05:17:26Z","lastAt":"2008-05-12T12:13:54Z","messageCount":2,"participants":["Sverre Hvammen Johansen","Miklos Vajna"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"76674","messageId":"402c10cd0805112217u2688eac6h8a650b7ab7bb4883@mail.gmail.com","threadId":"13487","inReplyTo":null,"subject":"[PATCH] Fast forward strategies allow, never, and only","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-05-12T05:17:26Z","receivedAt":"2008-05-12T05:17:26Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"I am hereby sending an updated version of these patches as\nattachments to this mail.  The patch series consists of the\nfollowing six patches:\n\n  0001-Documentation-for-joining-more-than-two-histories.patch\n  0002-New-merge-tests.patch\n  0003-Introduce-ff-fast-forward-option.patch\n  0004-Restructure-git-merge.sh.patch\n  0005-Head-reduction-before-selecting-merge-strategy.patch\n  0006-Introduce-fast-forward-option-only.patch\n\nThe first patch add some documentation, for how git currently handles\nmerges where more than one remote is involved.   The second patch add\nsome tests.  The third, fifth, and sixth adds new features and they are all\ntrivial.  The fourth patch computes the reduced parents but uses it only\nto determine whether we are up-to-date or doing a fast forward.\n\n-- \nSverre Hvammen Johansen\n\n\nFrom 781b3c10294db46e91d633c85a15fc2e1e1093b1 Mon Sep 17 00:00:00 2001\nFrom: Sverre Hvammen Johansen <hvammen@gmail.com>\nDate: Sun, 11 May 2008 17:19:12 -0700\nSubject: [PATCH 1/6] Documentation for joining more than two histories\n\nAdded some documentation for how git-merge currently\nworks when more than two histories are joined.\n\nSigned-off-by: Sverre Hvammen Johansen <hvammen@gmail.com>\n---\n Documentation/git-merge.txt |   36 ++++++++++++++++++++++++++++++++++++\n 1 files changed, 36 insertions(+), 0 deletions(-)\n\ndiff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt\nindex ef1f055..aa56512 100644\n--- a/Documentation/git-merge.txt\n+++ b/Documentation/git-merge.txt\n@@ -151,6 +151,42 @@ After seeing a conflict, you can do two things:\n    should be, and run `git-commit` to commit the result.\n \n \n+JOINING MORE THAN TWO HISTORIES\n+-------------------------------\n+\n+More than one remote may be specified on the command line.  Those\n+remotes are used for selecting the merge startegy and is also used in\n+the merge commit message.  However, some of these remotes may not be\n+independent.  Only remotes with independent heads (reduced parents)\n+will be recorded in the merge commit object.\n+\n+The following shows master and two topic branches.  topicB is based\n+on topicA, topicA is previously branched off from master:\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+------------\n+\n+Merging topicA and topicB to the master branch will select the merge\n+strategy based on all three branches (an Octopus).  master and topicB\n+are the reduced parents and are therefore the only parents recorded in\n+the merge commit object:\n+\n+------------\n+\n+                    o---o---o  topicB\n+                   /         \\\n+          o---o---o  topicA   o  master\n+         /                   / \n+    o---o---o---o---o---o---o\n+\n+------------\n+\n SEE ALSO\n --------\n linkgit:git-fmt-merge-msg[1], linkgit:git-pull[1],\n-- \n1.5.5.54.gc6550\n\n\n\nFrom 38fe432f824a1c6d225cc0cb17c65a9374fe948c Mon Sep 17 00:00:00 2001\nFrom: Sverre Hvammen Johansen <hvammen@gmail.com>\nDate: Sat, 3 May 2008 21:37:15 -0700\nSubject: [PATCH 2/6] New merge tests\n\nIntroduce new merge tests for preparation of new features:\n\n  --ff=<fast forward option>\n  Head reduction\n  --ff=only\n\nSet up the framework (based on t7600) and add some test cases\nthat should succeed independent of the new features.\n\nSigned-off-by: Sverre Hvammen Johansen <hvammen@gmail.com>\n---\n t/t7601-merge-ff-options.sh |  477 +++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 477 insertions(+), 0 deletions(-)\n create mode 100755 t/t7601-merge-ff-options.sh\n\ndiff --git a/t/t7601-merge-ff-options.sh b/t/t7601-merge-ff-options.sh\nnew file mode 100755\nindex 0000000..e499bf5\n--- /dev/null\n+++ b/t/t7601-merge-ff-options.sh\n@@ -0,0 +1,477 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2008 Sverre Hvammen Johansen, based on t7600 by Lars Hjemli\n+#\n+\n+test_description='git-merge\n+\n+Testing basic merge operations/option parsing.'\n+\n+. ./test-lib.sh\n+\n+cat >file <<EOF\n+1\n+2\n+3\n+4\n+5\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+EOF\n+\n+cat >file.1 <<EOF\n+1 X\n+2\n+3\n+4\n+5\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+EOF\n+\n+cat >file.5 <<EOF\n+1\n+2\n+3\n+4\n+5 X\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+EOF\n+\n+cat >file.9 <<EOF\n+1\n+2\n+3\n+4\n+5\n+6\n+7\n+8\n+9 X\n+10\n+11\n+12\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+10\n+11\n+12\n+EOF\n+\n+cat  >result.1 <<EOF\n+1 X\n+2\n+3\n+4\n+5\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+EOF\n+\n+cat >result.1-5 <<EOF\n+1 X\n+2\n+3\n+4\n+5 X\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+EOF\n+\n+cat >result.9 <<EOF\n+1\n+2\n+3\n+4\n+5\n+6\n+7\n+8\n+9 X\n+10\n+11\n+12\n+EOF\n+\n+cat >result.1-5-9 <<EOF\n+1 X\n+2\n+3\n+4\n+5 X\n+6\n+7\n+8\n+9 X\n+10\n+11\n+12\n+EOF\n+\n+cat >result.1-5-9-13 <<EOF\n+1 X\n+2\n+3\n+4\n+5 X\n+6\n+7\n+8\n+9 X\n+10\n+11\n+12\n+13 x\n+EOF\n+\n+cat >result.1-5-13 <<EOF\n+1 X\n+2\n+3\n+4\n+5 X\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+13 x\n+EOF\n+\n+cat >result.5-13 <<EOF\n+1\n+2\n+3\n+4\n+5 X\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+13 x\n+EOF\n+\n+cat >result.1-13 <<EOF\n+1 X\n+2\n+3\n+4\n+5\n+6\n+7\n+8\n+9\n+10\n+11\n+12\n+13 x\n+EOF\n+\n+cat >extend <<EOF\n+13 x\n+EOF\n+\n+\n+create_merge_msgs() {\n+\techo \"Merge commit 'c2'\" >msg.1-5 &&\n+\techo \"Merge commit 'c2'; commit 'c3'\" >msg.1-5-9 &&\n+\techo \"Squashed commit of the following:\" >squash.1 &&\n+\techo >>squash.1 &&\n+\tgit log --no-merges ^HEAD c1 >>squash.1 &&\n+\techo \"Squashed commit of the following:\" >squash.1-5 &&\n+\techo >>squash.1-5 &&\n+\tgit log --no-merges ^HEAD c2 >>squash.1-5 &&\n+\techo \"Squashed commit of the following:\" >squash.1-5-9 &&\n+\techo >>squash.1-5-9 &&\n+\tgit log --no-merges ^HEAD c2 c3 >>squash.1-5-9\n+}\n+\n+verify_diff() {\n+\tif ! diff -u \"$1\" \"$2\"\n+\tthen\n+\t\techo \"$3\"\n+\t\tfalse\n+\tfi\n+}\n+\n+verify_merge() {\n+\tverify_diff \"$2\" \"$1\" \"[OOPS] bad merge result\" &&\n+\tif test $(git ls-files -u | wc -l) -gt 0\n+\tthen\n+\t\techo \"[OOPS] unmerged files\"\n+\t\tfalse\n+\tfi &&\n+\tif ! git diff --exit-code\n+\tthen\n+\t\techo \"[OOPS] working tree != index\"\n+\t\tfalse\n+\tfi &&\n+\tif test -n \"$3\"\n+\tthen\n+\t\tgit show -s --pretty=format:%s HEAD >msg.act &&\n+\t\tverify_diff \"$3\" msg.act \"[OOPS] bad merge message\"\n+\tfi\n+}\n+\n+verify_head() {\n+\tif test \"$1\" != \"$(git rev-parse HEAD)\"\n+\tthen\n+\t\techo \"[OOPS] HEAD != $1\"\n+\t\tfalse\n+\tfi\n+}\n+\n+verify_parents() {\n+\ti=1\n+\twhile test $# -gt 0\n+\tdo\n+\t\tif test \"$1\" != \"$(git rev-parse HEAD^$i)\"\n+\t\tthen\n+\t\t\techo \"[OOPS] HEAD^$i != $1\"\n+\t\t\treturn 1\n+\t\tfi\n+\t\ti=$(expr $i + 1)\n+\t\tshift\n+\tdone\n+}\n+\n+verify_mergeheads() {\n+\ti=1\n+\tif ! test -f .git/MERGE_HEAD\n+\tthen\n+\t\techo \"[OOPS] MERGE_HEAD is missing\"\n+\t\tfalse\n+\tfi &&\n+\twhile test $# -gt 0\n+\tdo\n+\t\thead=$(head -n $i .git/MERGE_HEAD | tail -n 1)\n+\t\tif test \"$1\" != \"$head\"\n+\t\tthen\n+\t\t\techo \"[OOPS] MERGE_HEAD $i != $1\"\n+\t\t\treturn 1\n+\t\tfi\n+\t\ti=$(expr $i + 1)\n+\t\tshift\n+\tdone\n+}\n+\n+verify_no_mergehead() {\n+\tif test -f .git/MERGE_HEAD\n+\tthen\n+\t\techo \"[OOPS] MERGE_HEAD exists\"\n+\t\tfalse\n+\tfi\n+}\n+\n+# Setup with the following commits:\n+#\n+#             y3\n+#            /  \\\n+#           /    \\\n+#         y1      y2\n+#         | \\    / |\n+#         |  \\  /  |\n+#         x1  x0  x2\n+#         |  /  \\  |\n+#         | /    \\ |\n+#         c1      c2\n+#           \\    /\n+#            \\  /\n+#             c0---c3\n+#\n+\n+test_expect_success 'setup' '\n+\tgit add file &&\n+\ttest_tick &&\n+\tgit commit -m \"commit 0\" &&\n+\tgit tag c0 &&\n+\tc0=$(git rev-parse HEAD) &&\n+\n+\tcp file.1 file &&\n+\tgit add file &&\n+\ttest_tick &&\n+\tgit commit -m \"commit 1\" &&\n+\tgit tag c1 &&\n+\tc1=$(git rev-parse HEAD) &&\n+\ttest_tick &&\n+\n+\tgit reset --hard \"$c0\" &&\n+\tcp file.5 file &&\n+\tgit add file &&\n+\tgit commit -m \"commit 2\" &&\n+\ttest_tick &&\n+\tgit tag c2 &&\n+\tc2=$(git rev-parse HEAD) &&\n+\n+\tgit reset --hard \"$c0\" &&\n+\tcp file.9 file &&\n+\tgit add file &&\n+\ttest_tick &&\n+\tgit commit -m \"commit 3\" &&\n+\tgit tag c3 &&\n+\tc3=$(git rev-parse HEAD) &&\n+\ttest_tick &&\n+\n+\tgit reset --hard \"$c1\" &&\n+\tcat extend >>file &&\n+\tgit add file &&\n+\tgit commit -m \"commit 4\" &&\n+\tgit tag x1 &&\n+\tx1=$(git rev-parse HEAD) &&\n+\ttest_tick &&\n+\n+\tgit reset --hard \"$c1\" &&\n+\tgit merge \"$c2\" &&\n+\tgit tag x0 &&\n+\tx0=$(git rev-parse HEAD) &&\n+\ttest_tick &&\n+\n+\tgit reset --hard \"$c2\" &&\n+\tcat extend >>file &&\n+\tgit add file &&\n+\tgit commit -m \"commit 5\" &&\n+\tgit tag x2 &&\n+\tx2=$(git rev-parse HEAD) &&\n+\ttest_tick &&\n+\n+\tgit reset --hard \"$x1\" &&\n+\tgit merge \"$x0\" &&\n+\tgit tag y1 &&\n+\ty1=$(git rev-parse HEAD) &&\n+\ttest_tick &&\n+\n+\tgit reset --hard \"$x0\" &&\n+\tgit merge \"$x2\" &&\n+\tgit tag y2 &&\n+\ty2=$(git rev-parse HEAD) &&\n+\ttest_tick &&\n+\n+\tgit reset --hard \"$y1\" &&\n+\tgit merge \"$y2\" &&\n+\tgit tag y3 &&\n+\ty3=$(git rev-parse HEAD) &&\n+\ttest_tick &&\n+\tgit reset --hard \"$c0\" &&\n+\tcreate_merge_msgs &&\n+\n+\tgit reset --hard x1 &&\n+\tgit clone .git clone &&\n+\tgit config remote.clone.url clone &&\n+\tgit config remote.clone.fetch \"+refs/heads/*:refs/remotes/clone/*\" &&\n+\n+\t(mkdir new && cd new && git init && cp ../file.9 file2 && git add file2 && test_tick && git commit -m \"commit new\") &&\n+\tgit config remote.new.url new &&\n+\tgit config remote.new.fetch \"+refs/heads/*:refs/remotes/new/*\"\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with c0 and c0' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge c0 c0 &&\n+\tverify_merge file result.1 &&\n+\tverify_head $c1\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_expect_success 'merge y2 with x0, c3, and c0' '\n+\tgit reset --hard y2 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge x0 c3 c0 &&\n+\tverify_merge file result.1-5-9-13 &&\n+\tverify_parents $y2 $c3\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge x0 with y2, c3, and c0' '\n+\tgit reset --hard x0 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge y2 c3 c0 &&\n+\tverify_merge file result.1-5-9-13 &&\n+\tverify_parents $y2 $c3\n+'\n+\n+test_debug 'gitk --all'\n+\n+\n+test_expect_success 'merge c1 with c2 and x1' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge c2 x1 &&\n+\tverify_merge file result.1-5-13 &&\n+\tverify_parents $c2 $x1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_done\n-- \n1.5.5.54.gc6550\n\n\n\nFrom b763dd493e5cea45ffecdeb11561deb6b0732aea Mon Sep 17 00:00:00 2001\nFrom: Sverre Hvammen Johansen <hvammen@gmail.com>\nDate: Sat, 3 May 2008 17:02:52 -0700\nSubject: [PATCH 3/6] Introduce -ff=<fast forward option>\n\n--ff now takes an argument allowing --ff to be written\nas --ff=allow and -no-ff to be written as --ff=never.\nThis change allow other fast forward options to be\nintroduced later.\n\nSee the documentation for a further explanation of these options.\n\nSigned-off-by: Sverre Hvammen Johansen <hvammen@gmail.com>\n---\n Documentation/fast-forward-options.txt |   44 ++++++++\n Documentation/git-merge.txt            |    6 +-\n Documentation/git-pull.txt             |    2 +\n Documentation/merge-options.txt        |    9 +-\n git-merge.sh                           |   47 +++++---\n git-pull.sh                            |    4 +-\n t/t7601-merge-ff-options.sh            |  188 ++++++++++++++++++++++++++++++++\n 7 files changed, 277 insertions(+), 23 deletions(-)\n create mode 100644 Documentation/fast-forward-options.txt\n\ndiff --git a/Documentation/fast-forward-options.txt b/Documentation/fast-forward-options.txt\nnew file mode 100644\nindex 0000000..95d0e6f\n--- /dev/null\n+++ b/Documentation/fast-forward-options.txt\n@@ -0,0 +1,44 @@\n+FAST FORWARD OPTIONS\n+--------------------\n+\n+allow::\n+\n+\tDo not generate a merge commit if the merge resolves as a\n+\tfast-forward, only update the branch pointer.  This option is\n+\tequivalent of '--ff' without any argument.  This is the\n+\tdefault behavior.\n+\n+never::\n+\tGenerate a merge commit even if the merge resolves as a\n+\tfast-forward.  This option is equivalent of '--no-ff'.\n+\n+If your workflow is always to branch from the special branch\n+(\"master\") when working on a topic and merge that back to \"master\", if\n+you happen to have worked only on a single topic and the \"master\" was\n+never advanced during the time you worked on that topic, merging the\n+topic back to \"master\" will result in a fast-forward.  When you look\n+back that history, you will not be able to tell where the topic\n+started and ended by following the ancestry chain of the \"master\"\n+branch.\n+\n+Using \"never fast forward\" policy on such a special branch will be a\n+way to make sure that all commits on the first-parent ancestry of that\n+special branch will be merges from something else.  From the history\n+you can determine where the topic started and ended.\n+\n+The following shows two branches forked off from \"master\".  The branch\n+\"master\" have merged in changes from branch \"topicA\" twice and\n+\"topicB\" once:\n+\n+------------\n+         o---o---o---o---o  topicA\n+        /     \\           \\\n+    ---*-------*-------*---*  master\n+      /         \\     /\n+                 o---o  topicB\n+------------\n+\n+The first merge of topicA or the only merge of topicB would have\n+resulted in a fast forward without '--ff=never'.  Topic A consist of\n+those commits that can be reached from master^2 without passing\n+through any of the first-parent ancestries of master.\ndiff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt\nindex aa56512..7e1cb7e 100644\n--- a/Documentation/git-merge.txt\n+++ b/Documentation/git-merge.txt\n@@ -9,7 +9,8 @@ git-merge - Join two or more development histories together\n SYNOPSIS\n --------\n [verse]\n-'git-merge' [-n] [--stat] [--no-commit] [--squash] [-s <strategy>]...\n+'git-merge' [-n] [--stat] [--no-commit] [--squash]\n+\t[-s <strategy>]... [--ff[=<fast forward option>]]\n \t[-m <msg>] <remote> <remote>...\n 'git-merge' <msg> HEAD <remote>...\n \n@@ -37,6 +38,9 @@ include::merge-options.txt[]\n \tleast one <remote>.  Specifying more than one <remote>\n \tobviously means you are trying an Octopus.\n \n+\n+include::fast-forward-options.txt[]\n+\n include::merge-strategies.txt[]\n \n \ndiff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\nindex 66304f0..eb816bc 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -52,6 +52,8 @@ include::pull-fetch-param.txt[]\n \n include::urls-remotes.txt[]\n \n+include::fast-forward-options.txt[]\n+\n include::merge-strategies.txt[]\n \n DEFAULT BEHAVIOUR\ndiff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt\nindex f37a776..8f87758 100644\n--- a/Documentation/merge-options.txt\n+++ b/Documentation/merge-options.txt\n@@ -42,12 +42,11 @@\n \n --no-ff::\n \tGenerate a merge commit even if the merge resolved as a\n-\tfast-forward.\n+\tfast-forward.  --no-ff is an alias for --ff=never.\n \n---ff::\n-\tDo not generate a merge commit if the merge resolved as\n-\ta fast-forward, only update the branch pointer. This is\n-\tthe default behavior of git-merge.\n+--ff[=<fast forward option>]::\n+\tSelect fast forward option.  --ff without any argument\n+\tis an alias for --ff=allow which is the default behavior.\n \n -s <strategy>, \\--strategy=<strategy>::\n \tUse the given merge strategy; can be supplied more than\ndiff --git a/git-merge.sh b/git-merge.sh\nindex 69b35d8..91fada7 100755\n--- a/git-merge.sh\n+++ b/git-merge.sh\n@@ -16,7 +16,7 @@ log                  add list of one-line log to merge commit message\n no-log               don't add list of one-line log to merge commit message\n squash               create a single commit instead of doing a merge\n commit               perform a commit if the merge sucesses (default)\n-ff                   allow fast forward (default)\n+ff?                  fast forward options\n s,strategy=          merge strategy to use\n m,message=           message to be used for the merge commit (if any)\n \"\n@@ -39,7 +39,7 @@ no_fast_forward_strategies='subtree ours'\n no_trivial_strategies='recursive recur subtree ours'\n use_strategies=\n \n-allow_fast_forward=t\n+fast_forward=allow\n allow_trivial_merge=t\n squash= no_commit= log_arg=\n \n@@ -159,8 +159,6 @@ parse_config () {\n \t\t--log|--no-log)\n \t\t\tlog_arg=$1 ;;\n \t\t--squash)\n-\t\t\ttest \"$allow_fast_forward\" = t ||\n-\t\t\t\tdie \"You cannot combine --squash with --no-ff.\"\n \t\t\tsquash=t no_commit=t ;;\n \t\t--no-squash)\n \t\t\tsquash= no_commit= ;;\n@@ -169,11 +167,26 @@ parse_config () {\n \t\t--no-commit)\n \t\t\tno_commit=t ;;\n \t\t--ff)\n-\t\t\tallow_fast_forward=t ;;\n+\t\t\tcase \"$2\" in\n+\t\t\tallow|never)\n+\t\t\t\tfast_forward=$2; shift ;;\n+\t\t\t-*)\n+\t\t\t\tfast_forward=allow ;;\n+\t\t\t*)\n+\t\t\t\tdie \"Available fast-forward options are: allow and newer\" ;;\n+\t\t\tesac\n+\t\t\t;;\n+\t\t--ff=*)\n+\t\t\tfast_forward=${1#--ff=}\n+\t\t\tcase \"$fast_forward\" in\n+\t\t\tallow|never)\n+\t\t\t\t;;\n+\t\t\t*)\n+\t\t\t\tdie \"Available fast-forward options are: allow and newer\" ;;\n+\t\t\tesac\n+\t\t\t;;\n \t\t--no-ff)\n-\t\t\ttest \"$squash\" != t ||\n-\t\t\t\tdie \"You cannot combine --squash with --no-ff.\"\n-\t\t\tallow_fast_forward=f ;;\n+\t\t\tfast_forward=never ;;\n \t\t-s|--strategy)\n \t\t\tshift\n \t\t\tcase \" $all_strategies \" in\n@@ -195,6 +208,8 @@ parse_config () {\n \t\tesac\n \t\tshift\n \tdone\n+\ttest \"$fast_forward\" = allow -o \"$squash\" = \"\" ||\n+\t\tdie \"You cannot combine --squash with --ff=never\"\n \targs_left=$#\n }\n \n@@ -315,7 +330,7 @@ do\n \tdo\n \t\tcase \" $s \" in\n \t\t*\" $ss \"*)\n-\t\t\tallow_fast_forward=f\n+\t\t\tfast_forward=never\n \t\t\tbreak\n \t\t\t;;\n \t\tesac\n@@ -341,17 +356,17 @@ case \"$#\" in\n esac\n echo \"$head\" >\"$GIT_DIR/ORIG_HEAD\"\n \n-case \"$allow_fast_forward,$#,$common,$no_commit\" in\n-?,*,'',*)\n+case \"$fast_forward,$#,$common,$no_commit\" in\n+*,*,'',*)\n \t# No common ancestors found. We need a real merge.\n \t;;\n-?,1,\"$1\",*)\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 \tfinish_up_to_date \"Already up-to-date.\"\n \texit 0\n \t;;\n-t,1,\"$head\",*)\n+allow,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 \tgit update-index --refresh 2>/dev/null\n@@ -366,11 +381,11 @@ t,1,\"$head\",*)\n \tdropsave\n \texit 0\n \t;;\n-?,1,?*\"$LF\"?*,*)\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+*,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@@ -488,7 +503,7 @@ done\n # auto resolved the merge cleanly.\n if test '' != \"$result_tree\"\n then\n-    if test \"$allow_fast_forward\" = \"t\"\n+    if test $fast_forward = allow\n     then\n         parents=$(git show-branch --independent \"$head\" \"$@\")\n     else\ndiff --git a/git-pull.sh b/git-pull.sh\nindex bf0c298..9e91e75 100755\n--- a/git-pull.sh\n+++ b/git-pull.sh\n@@ -4,7 +4,7 @@\n #\n # Fetch one or more remote refs and merge it/them into the current HEAD.\n \n-USAGE='[-n | --no-stat] [--[no-]commit] [--[no-]squash] [--[no-]ff] [-s strategy]... [<fetch-options>] <repo> <head>...'\n+USAGE='[-n | --no-stat] [--[no-]commit] [--[no-]squash] [--ff=<ff-strategy>] [-s strategy]... [<fetch-options>] <repo> <head>...'\n LONG_USAGE='Fetch one or more remote refs and merge it/them into the current HEAD.'\n SUBDIRECTORY_OK=Yes\n OPTIONS_SPEC=\n@@ -41,6 +41,8 @@ do\n \t\tno_ff=--ff ;;\n \t--no-ff)\n \t\tno_ff=--no-ff ;;\n+\t--ff=allow|--ff=never)\n+\t\tno_ff=$1 ;;\n \t-s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\\\n \t\t--strateg=*|--strategy=*|\\\n \t-s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy)\ndiff --git a/t/t7601-merge-ff-options.sh b/t/t7601-merge-ff-options.sh\nindex e499bf5..c7c6d14 100755\n--- a/t/t7601-merge-ff-options.sh\n+++ b/t/t7601-merge-ff-options.sh\n@@ -474,4 +474,192 @@ test_expect_success 'merge c1 with c2 and x1' '\n \n test_debug 'gitk --all'\n \n+test_expect_success 'merge x0 with c1 (--squash combined with --ff=allow)' '\n+\tgit reset --hard x0 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge c1 --squash --ff=allow &&\n+\tverify_merge file result.1-5 &&\n+\tverify_head $x0\n+'\n+\n+test_debug 'gitk --all'\n+\n+\n+test_expect_success 'merge c1 with c2 (--squash combined with --ff=allow)' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge c2 --squash --ff=allow &&\n+\tverify_merge file result.1-5 &&\n+\tverify_head $c1 &&\n+\tgit commit &&\n+\tverify_parents $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+\n+test_expect_success 'merge c1 with x0 (--no-commit combined with --ff=allow)' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge x0 --no-commit --ff=allow &&\n+\tverify_merge file result.1-5 &&\n+\tverify_parents $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+\n+test_expect_success 'merge x0 with c1 (--no-commit combined with --ff=allow)' '\n+\tgit reset --hard x0 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge c1 --no-commit --ff=allow &&\n+\tverify_merge file result.1-5 &&\n+\tverify_head $x0\n+'\n+\n+test_debug 'gitk --all'\n+\n+\n+test_expect_success 'merge c1 with c2 (--no-commit combined with --ff=allow)' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge c2 --no-commit --ff=allow &&\n+\tverify_merge file result.1-5 &&\n+\tverify_head $c1 &&\n+\tgit commit &&\n+\tverify_parents $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+\n+test_expect_success 'merge c1 with x1 (pull --ff=allow)' '\n+\tgit reset --hard c1 &&\n+\ttest_tick &&\n+\tgit pull --ff=allow clone refs/heads/master &&\n+\tverify_merge file result.1-13 &&\n+\tverify_head $x1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge x2 with x1 (pull --ff=allow)' '\n+\tgit reset --hard x2 &&\n+\ttest_tick &&\n+\tgit pull --ff=allow clone refs/heads/master &&\n+\tverify_merge file result.1-5-13 &&\n+\tverify_parents $x2 $x1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with new repository (pull --ff=allow)' '\n+\tgit reset --hard c1 &&\n+\ttest_tick &&\n+\tgit pull --ff=allow new refs/heads/master &&\n+\tverify_merge file result.1 &&\n+\tverify_merge file2 result.9\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge x0 with c1 (--squash combined with --ff=never)' '\n+\tgit reset --hard x0 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\ttest_must_fail git merge c1 --squash --ff=never &&\n+\tverify_merge file result.1-5 &&\n+\tverify_head $x0\n+'\n+\n+test_debug 'gitk --all'\n+\n+\n+test_expect_success 'merge c1 with c2 (--squash combined with --ff=never)' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\ttest_must_fail git merge c2 --squash --ff=never &&\n+\tverify_merge file result.1 &&\n+\tverify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+\n+test_expect_success 'merge c1 with x0 (--no-commit combined with --ff=never)' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge x0 --no-commit --ff=never &&\n+\tverify_merge file result.1-5 &&\n+\tverify_head $c1 &&\n+\tgit commit &&\n+\tverify_parents $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+\n+test_expect_success 'merge x0 with c1 (--no-commit combined with --ff=never)' '\n+\tgit reset --hard x0 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge c1 --no-commit --ff=never &&\n+\tverify_merge file result.1-5 &&\n+\tverify_head $x0\n+'\n+\n+test_debug 'gitk --all'\n+\n+\n+test_expect_success 'merge c1 with c2 (--no-commit combined with --ff=never)' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge c2 --no-commit --ff=never &&\n+\tverify_merge file result.1-5 &&\n+\tverify_head $c1 &&\n+\tgit commit &&\n+\tverify_parents $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+\n+test_expect_success 'merge c1 with x1 (pull --ff=never)' '\n+\tgit reset --hard c1 &&\n+\ttest_tick &&\n+\tgit pull --ff=never clone refs/heads/master &&\n+\tverify_merge file result.1-13 &&\n+\tverify_parents $c1 $x1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge x2 with x1 (pull --ff=never)' '\n+\tgit reset --hard x2 &&\n+\ttest_tick &&\n+\tgit pull --ff=never clone refs/heads/master &&\n+\tverify_merge file result.1-5-13 &&\n+\tverify_parents $x2 $x1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with new repository (pull --ff=never)' '\n+\tgit reset --hard c1 &&\n+\ttest_tick &&\n+\tgit pull --ff=never new refs/heads/master &&\n+\tverify_merge file result.1 &&\n+\tverify_merge file2 result.9\n+'\n+\n+test_debug 'gitk --all'\n+\n test_done\n-- \n1.5.5.54.gc6550\n\n\n\nFrom 265bdb11f363e9c4e23d8129c31dfc506d008303 Mon Sep 17 00:00:00 2001\nFrom: Sverre Hvammen Johansen <hvammen@gmail.com>\nDate: Sat, 3 May 2008 16:30:11 -0700\nSubject: [PATCH 4/6] Restructure git-merge.sh\n\nRestructure git-merge.sh for preparation of new feature:\n\n       Head reduction before selecting merge strategy\n\nSome aspects of this patch does not make much sense without\nthe next patch in this series.\n\nSigned-off-by: Sverre Hvammen Johansen <hvammen@gmail.com>\n---\n git-merge.sh |  186 +++++++++++++++++++++++++++++++++-------------------------\n 1 files changed, 105 insertions(+), 81 deletions(-)\n\ndiff --git a/git-merge.sh b/git-merge.sh\nindex 91fada7..2c9f19f 100755\n--- a/git-merge.sh\n+++ b/git-merge.sh\n@@ -213,6 +213,47 @@ parse_config () {\n \targs_left=$#\n }\n \n+# Find reduced parents\n+# The following variables are set as follow:\n+#   reduced_parents: The reduced parents of those specified on the command line.\n+#                    However, the actual parents are included if we never ff.\n+#   common:          All common ancestors or not_queried\n+#   ff_head:         Head or an reduced parent that may be a candidate for fast forward\n+find_reduced_parents () {\n+\tif test $fast_forward = never\n+\tthen\n+\t\treduced_parents=$(git rev-parse \"$@\")\n+\t\tff_head=$head\n+\t\tcommon=not_queried\n+\telse\n+\t\tif test $# = 1\n+\t\tthen\n+\t\t\tcommon=$(git merge-base --all $head \"$1\")\n+\t\t\tif test \"$common\" = $head\n+\t\t\tthen\n+\t\t\t\treduced_parents=\n+\t\t\t\tff_head=$1\n+\t\t\telif test \"$common\" = \"$1\"\n+\t\t\tthen\n+\t\t\t\treduced_parents=\n+\t\t\t\tff_head=$head\n+\t\t\telse\n+\t\t\t\treduced_parents=$1\n+\t\t\t\tff_head=$head\n+\t\t\t    \n+\t\t\tfi\n+\t\telse\n+\t\t\treduced_parents=$(git show-branch --independent $head \"$@\")\n+\t\t\t# Here we may actually lie about which bransh is ff of head.\n+\t\t\t# This will preserve the order the user gave.\n+\t\t\tff_head=${reduced_parents%%$LF*}\n+\t\t\treduced_parents=${reduced_parents#$ff_head}\n+\t\t\treduced_parents=${reduced_parents#$LF}\n+\t\t\tcommon=not_queried\n+\t\tfi\n+\tfi\n+}\n+\n test $# != 0 || usage\n \n have_message=\n@@ -301,24 +342,28 @@ do\n done\n set x $remoteheads ; shift\n \n+find_reduced_parents \"$@\"\n+\n+actual_parents=$(git rev-parse \"$@\")\n+\n case \"$use_strategies\" in\n '')\n-\tcase \"$#\" in\n-\t1)\n-\t\tvar=\"`git config --get pull.twohead`\"\n+\tcase \"$actual_parents\" in\n+\t?*\"$LF\"?*)\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@@ -346,87 +391,66 @@ 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 \"$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-\tfinish_up_to_date \"Already up-to-date.\"\n-\texit 0\n-\t;;\n-allow,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-\tgit update-index --refresh 2>/dev/null\n-\tmsg=\"Fast forward\"\n-\tif test -n \"$have_message\"\n+if test -z \"$reduced_parents\"\n+then\n+\tif test $head = $ff_head\n \tthen\n-\t\tmsg=\"$msg (no commit created; -m option ignored)\"\n-\tfi\n-\tnew_head=$(git rev-parse --verify \"$1^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\tfinish_up_to_date \"Already up-to-date.\"\n+\t\texit 0\n+\telif test $fast_forward != never\n+\tthen\n+\t\techo \"Updating $(git rev-parse --short $head)..$(git rev-parse --short $ff_head)\"\n+\t\tgit update-index --refresh 2>/dev/null\n+\t\tmsg=\"Fast forward\"\n+\t\tif test -n \"$have_message\"\n \t\tthen\n-\t\t\tup_to_date=f\n-\t\t\tbreak\n+\t\t\tmsg=\"$msg (no commit created; -m option ignored)\"\n \t\tfi\n-\tdone\n-\tif test \"$up_to_date\" = t\n-\tthen\n-\t\tfinish_up_to_date \"Already up-to-date. Yeeah!\"\n+\t\tnew_head=$(git rev-parse --verify \"$ff_head^0\") &&\n+\t\tgit read-tree -v -m -u --exclude-per-directory=.gitignore $head \"$new_head\" &&\n+\t\tfinish \"$new_head\" \"$msg\" || exit\n+\t\tdropsave\n \t\texit 0\n \tfi\n+fi\n+\n+case \"$actual_parents\" in\n+?*\"$LF\"?*)\n+\t# We have more than one actual parent\n+\tcommon=$(git show-branch --merge-base $head $actual_parents)\n \t;;\n+*)\n+\t# We have exactly one actual parent\n+\ttest \"$common\" != not_queried || common=$(git merge-base --all $head $actual_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 $actual_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 $actual_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@@ -467,7 +491,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\" $actual_parents\n     exit=$?\n     if test \"$no_commit\" = t && test \"$exit\" = 0\n     then\n@@ -537,7 +561,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\" $actual_parents\n \t;;\n esac\n \n-- \n1.5.5.54.gc6550\n\n\n\nFrom 1984eb35a19f2dbbae40d91f36f77d723767c6ab Mon Sep 17 00:00:00 2001\nFrom: Sverre Hvammen Johansen <hvammen@gmail.com>\nDate: Sat, 3 May 2008 21:08:59 -0700\nSubject: [PATCH 5/6] Head reduction before selecting merge strategy\n\nThis commit uses the reduced parents instead of the actual\nparents specified for selecting the merge strategy when doing\na merge.  The recorded parents and the commit message stays\nthe same.\n\nSome cases where we before needed an octopus strategy may now be\ndone using a two head strategy.\n\nSee the documentation for further details.\n\nSigned-off-by: Sverre Hvammen Johansen <hvammen@gmail.com>\n---\n Documentation/git-merge.txt |   20 +++++++++-------\n git-merge.sh                |   50 ++++++++++++++++++++++++-------------------\n 2 files changed, 39 insertions(+), 31 deletions(-)\n\ndiff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt\nindex 7e1cb7e..11d53e6 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 \tOther branch head merged into our branch.  You need at\n \tleast one <remote>.  Specifying more than one <remote>\n-\tobviously means you are trying an Octopus.\n+\tusually means you are trying an Octopus.\n \n \n include::fast-forward-options.txt[]\n@@ -158,11 +158,12 @@ After seeing a conflict, you can do two things:\n JOINING MORE THAN TWO HISTORIES\n -------------------------------\n \n-More than one remote may be specified on the command line.  Those\n-remotes are used for selecting the merge startegy and is also used in\n-the merge commit message.  However, some of these remotes may not be\n-independent.  Only remotes with independent heads (reduced parents)\n-will be recorded in the merge commit object.\n+More than one remote may be specified on the command line.  Some of\n+these remotes may not have independent heads.  Git will only use the\n+remotes with independent heads (reduced parents) for selecting the\n+merge startegy.  The reduced parents are the parents recorded in the\n+merge commit object.  However, the commit message will reflect the\n+remotes specified on the command line.\n \n The following shows master and two topic branches.  topicB is based\n on topicA, topicA is previously branched off from master:\n@@ -177,9 +178,10 @@ on topicA, topicA is previously branched off from master:\n ------------\n \n Merging topicA and topicB to the master branch will select the merge\n-strategy based on all three branches (an Octopus).  master and topicB\n-are the reduced parents and are therefore the only parents recorded in\n-the merge commit object:\n+strategy based on the two independent branch heads, tip of the master\n+branch and the tip of the topicB branch (a two head strategy).  The\n+commits are also the reduced parents recorded in the merge commit\n+object:\n \n ------------\n \ndiff --git a/git-merge.sh b/git-merge.sh\nindex 2c9f19f..f080986 100755\n--- a/git-merge.sh\n+++ b/git-merge.sh\n@@ -344,11 +344,16 @@ set x $remoteheads ; shift\n \n find_reduced_parents \"$@\"\n \n-actual_parents=$(git rev-parse \"$@\")\n+# ff_head may be included here or later in actual parents\n+if test -n \"$reduced_parents\"\n+then\n+\ttest $head = $ff_head ||\n+\t\treduced_parents=\"$ff_head$LF$reduced_parents\"\n+fi\n \n case \"$use_strategies\" in\n '')\n-\tcase \"$actual_parents\" in\n+\tcase \"$reduced_parents\" in\n \t?*\"$LF\"?*)\n \t\tvar=\"`git config --get pull.octopus`\"\n \t\tif test -n \"$var\"\n@@ -413,17 +418,23 @@ then\n \t\tfinish \"$new_head\" \"$msg\" || exit\n \t\tdropsave\n \t\texit 0\n+\telse\n+\t\treduced_parents=\"$ff_head\"\n+\t\tff_head=$head\n \tfi\n+else\n+\ttest $head != $ff_head -a $fast_forward = never &&\n+\t\treduced_parents=\"$ff_head$LF$reduced_parents\"\n fi\n \n-case \"$actual_parents\" in\n+case \"$reduced_parents\" in\n ?*\"$LF\"?*)\n-\t# We have more than one actual parent\n-\tcommon=$(git show-branch --merge-base $head $actual_parents)\n+\t# We have more than one reduced parent\n+\tcommon=$(git show-branch --merge-base $head $reduced_parents)\n \t;;\n *)\n-\t# We have exactly one actual parent\n-\ttest \"$common\" != not_queried || common=$(git merge-base --all $head $actual_parents)\n+\t# We have exactly one reduced parent\n+\ttest \"$common\" != not_queried || common=$(git merge-base --all $head $reduced_parents)\n \tcase \"$common\" in\n \t?*\"$LF\"?*)\n \t\t# We are not doing octopus and not fast forward.  Need a\n@@ -436,13 +447,13 @@ case \"$actual_parents\" in\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 $actual_parents &&\n+\t\t\tif git read-tree --trivial -m -u -v $common $head $reduced_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 $actual_parents\n+\t\t\t\t\tgit commit-tree $result_tree -p HEAD -p $reduced_parents\n \t\t\t\t) || exit\n \t\t\t\tfinish \"$result_commit\" \"In-index merge\"\n \t\t\t\tdropsave\n@@ -491,7 +502,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\" $actual_parents\n+    git-merge-$strategy $common -- \"$head_arg\" $reduced_parents\n     exit=$?\n     if test \"$no_commit\" = t && test \"$exit\" = 0\n     then\n@@ -527,17 +538,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 $result_tree $parents) || exit\n-    finish \"$result_commit\" \"Merge made by $wt_strategy.\"\n-    dropsave\n-    exit 0\n+\ttest $head = $ff_head && reduced_parents=\"$head$LF$reduced_parents\"\n+\tparents=$(echo \"$reduced_parents\" | sed -e 's/^/-p /')\n+\tresult_commit=$(printf '%s\\n' \"$merge_msg\" | git commit-tree $result_tree $parents) || exit\n+\tfinish \"$result_commit\" \"Merge made by $wt_strategy.\"\n+\tdropsave\n+\texit 0\n fi\n \n # Pick the result from the best strategy and have the user fix it up.\n@@ -561,7 +567,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\" $actual_parents\n+\tgit-merge-$best_strategy $common -- \"$head_arg\" $reduced_parents\n \t;;\n esac\n \n-- \n1.5.5.54.gc6550\n\n\n\nFrom 6b58d39f1cf364d8b0c91da519aace607172b724 Mon Sep 17 00:00:00 2001\nFrom: Sverre Hvammen Johansen <hvammen@gmail.com>\nDate: Sat, 3 May 2008 16:55:47 -0700\nSubject: [PATCH 6/6] Introduce fast forward option only\n\nThis commit introduces fast forward option 'only'.  With --ff=only\nmerge succeeds only if it resolves to fast-forward merge.\n\nThis feature is useful for cases where a rebase is desired\ninstead of a real merge.  This option can then be used to\navoid an accidental merge.\n\nSee the documentation for further details.\n\nSigned-off-by: Sverre Hvammen Johansen <hvammen@gmail.com>\n---\n Documentation/fast-forward-options.txt |    9 ++\n git-merge.sh                           |   12 +-\n git-pull.sh                            |    2 +-\n t/t7601-merge-ff-options.sh            |  214 ++++++++++++++++++++++++++++++++\n 4 files changed, 231 insertions(+), 6 deletions(-)\n\ndiff --git a/Documentation/fast-forward-options.txt b/Documentation/fast-forward-options.txt\nindex 95d0e6f..4445b0e 100644\n--- a/Documentation/fast-forward-options.txt\n+++ b/Documentation/fast-forward-options.txt\n@@ -12,6 +12,10 @@ never::\n \tGenerate a merge commit even if the merge resolves as a\n \tfast-forward.  This option is equivalent of '--no-ff'.\n \n+only::\n+\tOnly allow a fast-forward.  The merge will fail unless HEAD is\n+\tup to date or the merge resolves as a fast-forward.\n+\n If your workflow is always to branch from the special branch\n (\"master\") when working on a topic and merge that back to \"master\", if\n you happen to have worked only on a single topic and the \"master\" was\n@@ -42,3 +46,8 @@ The first merge of topicA or the only merge of topicB would have\n resulted in a fast forward without '--ff=never'.  Topic A consist of\n those commits that can be reached from master^2 without passing\n through any of the first-parent ancestries of master.\n+\n+However, if the workflow require that the branch you are merging with\n+is based on the current HEAD you can use \"only fast forward\" policy to\n+enforce fast forward or a failure.  The last merge of topicA in\n+the example above would have failed with '--ff=only'.\ndiff --git a/git-merge.sh b/git-merge.sh\nindex f080986..94ff719 100755\n--- a/git-merge.sh\n+++ b/git-merge.sh\n@@ -168,21 +168,21 @@ parse_config () {\n \t\t\tno_commit=t ;;\n \t\t--ff)\n \t\t\tcase \"$2\" in\n-\t\t\tallow|never)\n+\t\t\tallow|never|only)\n \t\t\t\tfast_forward=$2; shift ;;\n \t\t\t-*)\n \t\t\t\tfast_forward=allow ;;\n \t\t\t*)\n-\t\t\t\tdie \"Available fast-forward options are: allow and newer\" ;;\n+\t\t\t\tdie \"Available fast-forward options are: allow, newer, and only\" ;;\n \t\t\tesac\n \t\t\t;;\n \t\t--ff=*)\n \t\t\tfast_forward=${1#--ff=}\n \t\t\tcase \"$fast_forward\" in\n-\t\t\tallow|never)\n+\t\t\tallow|never|only)\n \t\t\t\t;;\n \t\t\t*)\n-\t\t\t\tdie \"Available fast-forward options are: allow and newer\" ;;\n+\t\t\t\tdie \"Available fast-forward options are: allow, newer, and only\" ;;\n \t\t\tesac\n \t\t\t;;\n \t\t--no-ff)\n@@ -209,7 +209,7 @@ parse_config () {\n \t\tshift\n \tdone\n \ttest \"$fast_forward\" = allow -o \"$squash\" = \"\" ||\n-\t\tdie \"You cannot combine --squash with --ff=never\"\n+\t\tdie \"You cannot combine --squash with --ff=never or --ff=only.\"\n \targs_left=$#\n }\n \n@@ -347,6 +347,8 @@ find_reduced_parents \"$@\"\n # ff_head may be included here or later in actual parents\n if test -n \"$reduced_parents\"\n then\n+\ttest $fast_forward = only &&\n+\t\tdie \"--ff=only can not handle more than one real parent\"\n \ttest $head = $ff_head ||\n \t\treduced_parents=\"$ff_head$LF$reduced_parents\"\n fi\ndiff --git a/git-pull.sh b/git-pull.sh\nindex 9e91e75..c5fa1ee 100755\n--- a/git-pull.sh\n+++ b/git-pull.sh\n@@ -41,7 +41,7 @@ do\n \t\tno_ff=--ff ;;\n \t--no-ff)\n \t\tno_ff=--no-ff ;;\n-\t--ff=allow|--ff=never)\n+\t--ff=allow|--ff=only|--ff=never)\n \t\tno_ff=$1 ;;\n \t-s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\\\n \t\t--strateg=*|--strategy=*|\\\ndiff --git a/t/t7601-merge-ff-options.sh b/t/t7601-merge-ff-options.sh\nindex c7c6d14..56e8370 100755\n--- a/t/t7601-merge-ff-options.sh\n+++ b/t/t7601-merge-ff-options.sh\n@@ -662,4 +662,218 @@ test_expect_success 'merge c1 with new repository (pull --ff=never)' '\n \n test_debug 'gitk --all'\n \n+test_expect_success 'merge c0 with c1 (--ff=only overrides --no-ff)' '\n+\tgit reset --hard c0 &&\n+\tgit config branch.master.mergeoptions \"--no-ff\" &&\n+\tgit merge --ff=only c1 &&\n+\tverify_merge file result.1 &&\n+\tverify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with c1 (--ff=only in config)' '\n+\tgit reset --hard c0 &&\n+\tgit config branch.master.mergeoptions \"--ff=only\" &&\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 (--ff=only in config)' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"--ff=only\" &&\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 (--ff=only in config)' '\n+\tgit reset --hard c1 &&\n+\ttest_tick &&\n+\tgit config branch.master.mergeoptions \"--ff=only\" &&\n+\ttest_must_fail git merge c2 &&\n+\tverify_merge file result.1 &&\n+\tverify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with c1 (--ff=only)' '\n+\tgit reset --hard c0 &&\n+\ttest_tick &&\n+\tgit merge --ff=only c1 &&\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 (--ff=only)' '\n+\tgit reset --hard c1 &&\n+\ttest_tick &&\n+\tgit merge --ff=only c0 &&\n+\tverify_merge file result.1 &&\n+\tverify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with c1 and c2 (--ff=only)' '\n+\tgit reset --hard c0 &&\n+\ttest_must_fail git merge --ff=only c1 c2 &&\n+\tverify_merge file result.0 &&\n+\tverify_head $c0\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with c0 (--ff=only)' '\n+\tgit reset --hard c1 &&\n+\ttest_tick &&\n+\tgit merge --ff=only 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 (--ff=only overrides --no-ff)' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"--no-ff\" &&\n+\ttest_tick &&\n+\ttest_must_fail git merge c2 --ff=only &&\n+\tverify_merge file result.1 &&\n+\tverify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with c1 (--no-ff overrides --ff=only)' '\n+\tgit reset --hard c0 &&\n+\tgit config branch.master.mergeoptions \"--ff=only\" &&\n+\ttest_tick &&\n+\tgit merge --no-ff c1 &&\n+\tverify_merge file result.1 &&\n+\tverify_parents $c0 $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with c2 (--ff owerrides --ff=only)' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"--ff=only\" &&\n+\ttest_tick &&\n+\tgit merge --ff 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 x0 (--squash combined with --ff=only)' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\ttest_must_fail git merge x0 --squash --ff=only &&\n+\tverify_merge file result.1 &&\n+\tverify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+\n+test_expect_success 'merge x0 with c1 (--squash combined with --ff=only)' '\n+\tgit reset --hard x0 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\ttest_must_fail git merge c1 --squash --ff=only &&\n+\tverify_merge file result.1-5 &&\n+\tverify_head $x0\n+'\n+\n+test_debug 'gitk --all'\n+\n+\n+test_expect_success 'merge c1 with c2 (--squash combined with --ff=only)' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\ttest_must_fail git merge c2 --squash --ff=only &&\n+\tverify_merge file result.1 &&\n+\tverify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+\n+test_expect_success 'merge c1 with x0 (--no-commit combined with --ff=only)' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge x0 --no-commit --ff=only &&\n+\tverify_merge file result.1-5 &&\n+\tverify_head $x0\n+'\n+\n+test_debug 'gitk --all'\n+\n+\n+test_expect_success 'merge x0 with c1 (--no-commit combined with --ff=only)' '\n+\tgit reset --hard x0 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge c1 --no-commit --ff=only &&\n+\tverify_merge file result.1-5 &&\n+\tverify_head $x0\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with c2 (--no-commit combined with --ff=only)' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\ttest_must_fail git merge c2 --no-commit --ff=only &&\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 x1 (pull --ff=only)' '\n+\tgit reset --hard c1 &&\n+\ttest_tick &&\n+\tgit pull --ff=only clone refs/heads/master &&\n+\tverify_merge file result.1-13 &&\n+\tverify_head $x1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge x2 with x1 (pull --ff=only)' '\n+\tgit reset --hard x2 &&\n+\ttest_tick &&\n+\ttest_must_fail git pull --ff=only clone refs/heads/master &&\n+\tverify_merge file result.5-13 &&\n+\tverify_head $x2\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with new repository (pull --ff=only)' '\n+\tgit reset --hard c1 &&\n+\ttest_tick &&\n+\ttest_must_fail git pull --ff=only new refs/heads/master &&\n+\tverify_merge file result.1 &&\n+\tverify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n test_done\n-- \n1.5.5.54.gc6550\n\n"},{"id":"76698","messageId":"20080512121354.GM27724@genesis.frugalware.org","threadId":"13487","inReplyTo":"402c10cd0805112217u2688eac6h8a650b7ab7bb4883@mail.gmail.com","subject":"Re: [PATCH] Fast forward strategies allow, never, and only","fromName":"Miklos Vajna","fromEmail":"vmiklos@frugalware.org","sentAt":"2008-05-12T12:13:54Z","receivedAt":"2008-05-12T12:13:54Z","isPatch":true,"sender":{"key":"vmiklos@frugalware.org","avatar":"https://gravatar.com/avatar/401c1cbbb3a5d13e650c691a2c71d6fd0b80df1a01bc74d9f1972675dd58f2bd?d=mp&s=160"},"body":"On Sun, May 11, 2008 at 10:17:26PM -0700, Sverre Hvammen Johansen <hvammen@gmail.com> wrote:\n> I am hereby sending an updated version of these patches as\n> attachments to this mail.  The patch series consists of the\n> following six patches:\n> \n>   0001-Documentation-for-joining-more-than-two-histories.patch\n>   0002-New-merge-tests.patch\n>   0003-Introduce-ff-fast-forward-option.patch\n>   0004-Restructure-git-merge.sh.patch\n>   0005-Head-reduction-before-selecting-merge-strategy.patch\n>   0006-Introduce-fast-forward-option-only.patch\n> \n> The first patch add some documentation, for how git currently handles\n> merges where more than one remote is involved.   The second patch add\n> some tests.  The third, fifth, and sixth adds new features and they are all\n> trivial.  The fourth patch computes the reduced parents but uses it only\n> to determine whether we are up-to-date or doing a fast forward.\n\nAccording to Documentation/SubmittingPatches, you should not attach\npatches, but send one email / patch with the patch inline.\n\nJust use git-send-email to do so. :)\n"}]}