{"thread":{"id":"12628","subject":"[RFC/PATCH] Fast forward strategies allow, never, and only","startedAt":"2008-03-11T02:59:17Z","lastAt":"2008-04-24T05:39:48Z","messageCount":27,"participants":["Sverre Hvammen Johansen","Ping Yin","Junio C Hamano","Jakub Narebski","Jon Loeliger"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"71643","messageId":"402c10cd0803101959q619efa86pbd501e5e2cc018c2@mail.gmail.com","threadId":"12628","inReplyTo":null,"subject":"[RFC/PATCH] Fast forward strategies allow, never, and only","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-03-11T02:59:17Z","receivedAt":"2008-03-11T02:59:17Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":">From 795bd1b3e70f011b675061ecae322527a9f0695c Mon Sep 17 00:00:00 2001\nFrom: Sverre Hvammen Johansen <sj@gmail.com>\nDate: Sun, 9 Mar 2008 21:43:56 -0800\nSubject: [PATCH] Fast forward strategies allow, never, and only.\n\nNew fast forward strategies, only, is introduced.  This new fast\nforward strategy prevents real merges.\n\nFF strategy \"only\" 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.  However,\nfast-forward of head in combination with a real merge is\nhandled as before.\n\nSigned-off-by: Sverre Hvammen Johansen <sj@black.local>\n---\n Documentation/fast-forward-strategies.txt |   16 +\n Documentation/git-merge.txt               |    6 +-\n Documentation/git-pull.txt                |    2 +\n Documentation/merge-options.txt           |   11 +-\n git-merge.sh                              |  291 +++++++++++-----\n git-pull.sh                               |    4 +-\n t/t7601-merge-ff-strategies.sh            |  549 +++++++++++++++++++++++++++++\n 7 files changed, 778 insertions(+), 101 deletions(-)\n create mode 100644 Documentation/fast-forward-strategies.txt\n create mode 100755 t/t7601-merge-ff-strategies.sh\n\ndiff --git a/Documentation/fast-forward-strategies.txt\nb/Documentation/fast-forward-strategies.txt\nnew file mode 100644\nindex 0000000..1d6da26\n--- /dev/null\n+++ b/Documentation/fast-forward-strategies.txt\n@@ -0,0 +1,16 @@\n+FAST FORWARD STRATEGIES\n+-----------------------\n+\n+allow::\n+\tDo not generate a merge commit if the merge resolved\n+\tas a fast-forward, only update the branch pointer.\n+\tThis is the default behavior of git-merge.\n+\n+never::\n+\tGenerate a merge commit even if the merge resolved\n+\tas a fast-forward.\n+\n+only::\n+\tOnly allow a fast-forward.  The merge will fail\n+\tunless HEAD is up to date or the merge resolved as\n+        a fast-forward.\ndiff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt\nindex c136b10..fbf3ebe 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] [--summary] [--no-commit] [--squash] [-s <strategy>]...\n+'git-merge' [-n] [--summary] [--no-commit] [--squash]\n+\t[-s <strategy>]... [--ff[=<fast forward strategy>]]\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-strategies.txt[]\n+\n include::merge-strategies.txt[]\n\n\ndiff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt\nindex 7378943..99d205c 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -50,6 +50,8 @@ include::pull-fetch-param.txt[]\n\n include::urls-remotes.txt[]\n\n+include::fast-forward-strategies.txt[]\n+\n include::merge-strategies.txt[]\n\n DEFAULT BEHAVIOUR\ndiff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt\nindex 9f1fc82..848b786 100644\n--- a/Documentation/merge-options.txt\n+++ b/Documentation/merge-options.txt\n@@ -29,12 +29,13 @@\n\n --no-ff::\n \tGenerate a merge commit even if the merge resolved as a\n-\tfast-forward.\n+\tfast-forward.  --on-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 strategy>]::\n+\tSelect fast forward strategy.  --ff without any argument\n+\tis an alias for --ff=allow which is the default behavior\n+\tof git-merge.  It will not generate a merge commit if the\n+\tmerge resolved as a fast-forward,\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 7dbbb1d..873e4cb 100755\n--- a/git-merge.sh\n+++ b/git-merge.sh\n@@ -12,7 +12,7 @@ summary              show a diffstat at the end of the merge\n n,no-summary         don't show a diffstat at the end of the merge\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?                  allow fast forward (default)\n s,strategy=          merge strategy to use\n m,message=           message to be used for the merge commit (if any)\n \"\n@@ -35,7 +35,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=\n\n@@ -153,8 +153,8 @@ parse_config () {\n \t\t--summary)\n \t\t\tshow_diffstat=t ;;\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\ttest \"$fast_forward\" = allow ||\n+\t\t\t\tdie \"You cannot combine --squash with --ff=never.\"\n \t\t\tsquash=t no_commit=t ;;\n \t\t--no-squash)\n \t\t\tsquash= no_commit= ;;\n@@ -163,11 +163,28 @@ 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|only)\n+\t\t\t\tfast_forward=$2 squash= no_commit= ; shift ;;\n+\t\t\t-*)\n+\t\t\t\tfast_forward=allow squash= no_commit= ;;\n+\t\t\t*)\n+\t\t\t\tdie \"available fast-forward strategies are: allow, newer, and only\" ;;\n+\t\t\tesac\n+\t\t\t;;\n+\t\t--ff=*)\n+\t\t\tfast_forward=$(echo $1 |cut -d = -f 2) squash= no_commit=\n+\t\t\tcase $fast_forward in\n+\t\t\t    allow|never|only)\n+\t\t\t\t;;\n+\t\t\t    *)\n+\t\t\t\tdie \"available fast-forward strategies are: allow, newer, and only\" ;;\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@@ -279,24 +296,125 @@ 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\t\techo \"Ignoring $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\n--short $real_parent).\"\n+\t\t\t\techo \"Ignoring $1\"\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\nrev-parse --short $1).\"\n+\t\t\t\techo \"Ignoring $real_parent\"\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 $real_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 independent\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+if test $fast_forward = never -o\n+then\n+\treal_parents=\"$@\"\n+\tff_head=$head\n+else\n+\tfind_real_parents \"$@\"\n+fi\n+\n+if test -n \"$real_parents\"\n+then\n+\tcase $fast_forward in\n+\tonly)\n+\t\tdie \"Fast forward strategy only can only handle one real parent\" ;;\n+\tnever|allow)\n+\t\tif test $head != $ff_head\n+\t\tthen\n+\t\t\treal_parents=\"$ff_head $real_parents\"\n+\t\t\tff_head=$head\n+\t\tfi\n+\t\t;;\n+\tesac\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@@ -308,7 +426,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@@ -323,88 +441,73 @@ do\n \t\tesac\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-\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-\tgit update-index --refresh 2>/dev/null\n-\tmsg=\"Fast forward\"\n-\tif test -n \"$have_message\"\n+if test -z \"$real_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\treal_parents=\"$ff_head\"\n+\t\tff_head=$head\n+\telse\n+\t\techo \"Updating $(git rev-parse --short $head)..$(git rev-parse\n--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\n\"$new_head\" &&\n+\t\tfinish \"$new_head\" \"$msg\" || exit\n+\t\tdropsave\n \t\texit 0\n \tfi\n+else\n+\tif test $head != $ff_head -a $fast_forward = never\n+\tthen\n+\t\treal_parents=\"$ff_head $real_parents\"\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@@ -445,7 +548,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@@ -481,11 +584,11 @@ 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 != never\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\n$result_tree $parents) || exit\n@@ -515,7 +618,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/git-pull.sh b/git-pull.sh\nindex 3ce32b5..b78cfdd 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-summary] [--[no-]commit] [--[no-]squash]\n[--[no-]ff] [-s strategy]... [<fetch-options>] <repo> <head>...'\n+USAGE='[-n | --no-summary] [--[no-]commit] [--[no-]squash]\n[--ff=<ff-strategy>] [-s strategy]... [<fetch-options>] <repo>\n<head>...'\n LONG_USAGE='Fetch one or more remote refs and merge it/them into the\ncurrent 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=*)\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-strategies.sh b/t/t7601-merge-ff-strategies.sh\nnew file mode 100755\nindex 0000000..6c0a91a\n--- /dev/null\n+++ b/t/t7601-merge-ff-strategies.sh\n@@ -0,0 +1,549 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2007 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.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+\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\nfile2 && 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 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+\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 (--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+\tif git --ff=only merge c1 c2\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 (--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+\tif git merge c2 --ff=only\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 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 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+test_done\n-- \n1.5.3.3\n\n-- \nSverre Hvammen Johansen\n"},{"id":"71645","messageId":"402c10cd0803102018k2f1dd9e0k6b766671f36ff5f0@mail.gmail.com","threadId":"12628","inReplyTo":"402c10cd0803101959q619efa86pbd501e5e2cc018c2@mail.gmail.com","subject":"Re: [RFC/PATCH] Fast forward strategies allow, never, and only","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-03-11T03:18:47Z","receivedAt":"2008-03-11T03:18:47Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"Hi,\n\nI have split the original patch I had for the fast forward strategies\nin two.  I hope to get something close to this patch accepted.  I need\nthis feature for the Accurev integration I am working on.  I will be\nable to spend time on this the next two weeks.\n\n-- \nSverre Hvammen Johansen\n"},{"id":"71652","messageId":"46dff0320803102217s39f290c3k1f627a638b7aebf5@mail.gmail.com","threadId":"12628","inReplyTo":"402c10cd0803102018k2f1dd9e0k6b766671f36ff5f0@mail.gmail.com","subject":"Re: [RFC/PATCH] Fast forward strategies allow, never, and only","fromName":"Ping Yin","fromEmail":"pkufranky@gmail.com","sentAt":"2008-03-11T05:17:57Z","receivedAt":"2008-03-11T05:17:57Z","isPatch":true,"sender":{"key":"pkufranky@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5346?v=4"},"body":"On Tue, Mar 11, 2008 at 11:18 AM, Sverre Hvammen Johansen\n<hvammen@gmail.com> wrote:\n> Hi,\n>\n>  I have split the original patch I had for the fast forward strategies\n>  in two.  I hope to get something close to this patch accepted.  I need\n>  this feature for the Accurev integration I am working on.  I will be\n>  able to spend time on this the next two weeks.\n>\n\nExpecting it will be accepted. It's a useful feature for me.\n\n>\n>  --\n>  Sverre Hvammen Johansen\n>  --\n>  To unsubscribe from this list: send the line \"unsubscribe git\" in\n>  the body of a message to majordomo@vger.kernel.org\n>  More majordomo info at  http://vger.kernel.org/majordomo-info.html\n>\n\n\n\n-- \nPing Yin\n"},{"id":"71655","messageId":"7vk5k9eqax.fsf@gitster.siamese.dyndns.org","threadId":"12628","inReplyTo":"402c10cd0803101959q619efa86pbd501e5e2cc018c2@mail.gmail.com","subject":"Re: [RFC/PATCH] Fast forward strategies allow, never, and only","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-03-11T06:19:02Z","receivedAt":"2008-03-11T06:19:02Z","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>>From 795bd1b3e70f011b675061ecae322527a9f0695c Mon Sep 17 00:00:00 2001\n> From: Sverre Hvammen Johansen <sj@gmail.com>\n> Date: Sun, 9 Mar 2008 21:43:56 -0800\n> Subject: [PATCH] Fast forward strategies allow, never, and only.\n\nPlease do not do this.  The first line is not part of anything but is only\na mail message boundary in mbox format.  Reproducing From: is fine\nespecially if the patch author is different from the person who is sending\nthe patch, but I do not think it is necessary in this case.  Date and\nSubject should also go, as taking them from the e-mail header is just as\ngood.\n\n> New fast forward strategies, only, is introduced.  This new fast\n> forward strategy prevents real merges.\n\n\"What it does\".\n\n> FF strategy \"only\" fails if the specified heads and HEAD can not\n> be reduced down to only one real parent.  The only allowed\n> outcome is a fast forward unless HEAD is up to date with\n> the specified heads.\n\n\"What it does\" continues.\n\n> This patch also uses the real heads found instead of those\n> specified for real merges.  This means that merge startegies\n> that only take two heads can now accept more than two heads\n> if they can be reduced down to only two real heads.  However,\n> fast-forward of head in combination with a real merge is\n> handled as before.\n\n\"What it does\" continues further.\n\nWhat's lacking is \"why this is a good idea\".\n\n> Signed-off-by: Sverre Hvammen Johansen <sj@black.local>\n\nPlease check \"[user] email\" section in your .git/config file.\n\n> @@ -0,0 +1,16 @@\n> +FAST FORWARD STRATEGIES\n> +-----------------------\n\nIn the context of \"merge\", the word \"strategy\" is already taken to mean\nsomething quite different, and I am afraid that reusing the word may cause\nconfusion.  Probably this should be called fast forward options.\n\n> +allow::\n> +\tDo not generate a merge commit if the merge resolved\n> +\tas a fast-forward, only update the branch pointer.\n> +\tThis is the default behavior of git-merge.\n\nMakes one wonder if git-pull uses different default from the default for\nthe git-merge command.\n\n> +never::\n> +\tGenerate a merge commit even if the merge resolved\n> +\tas a fast-forward.\n> +\n> +only::\n> +\tOnly allow a fast-forward.  The merge will fail\n> +\tunless HEAD is up to date or the merge resolved as\n> +        a fast-forward.\n\nFunny indentation on the last line...\n\n> @@ -29,12 +29,13 @@\n>\n>  --no-ff::\n>  \tGenerate a merge commit even if the merge resolved as a\n> -\tfast-forward.\n> +\tfast-forward.  --on-ff is an alias for --ff=never.\n\nReally?\n\n> @@ -153,8 +153,8 @@ parse_config () {\n>  \t\t--summary)\n>  \t\t\tshow_diffstat=t ;;\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\ttest \"$fast_forward\" = allow ||\n> +\t\t\t\tdie \"You cannot combine --squash with --ff=never.\"\n\nWhy does the user get this message after saying --ff=only?\n\n>  \t\t\tsquash=t no_commit=t ;;\n>  \t\t--no-squash)\n>  \t\t\tsquash= no_commit= ;;\n> @@ -163,11 +163,28 @@ 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|only)\n> +\t\t\t\tfast_forward=$2 squash= no_commit= ; shift ;;\n> +\t\t\t-*)\n> +\t\t\t\tfast_forward=allow squash= no_commit= ;;\n> +\t\t\t*)\n> +\t\t\t\tdie \"available fast-forward strategies are: allow, newer, and only\" ;;\n\nShouldn't \"squash= no_commit=\" be shared across case arms?\n\nHow does this code parse \"git merge --ff my_other_branch\"?\n\nShouldn't you issue the same error message for these two inputs?\n\n\t\"git merge --ff=never --squash\" \n\t\"git merge --squash --ff=never\"\n\n> +\t\t--ff=*)\n> +\t\t\tfast_forward=$(echo $1 |cut -d = -f 2) squash= no_commit=\n\nI do not know the reason why, and theoretically there shouldn't be any\ncorrelation, but somehow I see \"cut\" used more often in shell scripts that\nare sloppily done.\n\nAt least you would need to quote \"$1\" above (and ideally protect yourself\nagainst nonsense input like \"--ff=-n\"), but in this case, I think it is\nsimpler to say:\n\n\tfast_forward=${1#--ff=}\n\n> @@ -279,24 +296,125 @@ do\n>  done\n>  set x $remoteheads ; shift\n>\n> +echo \"$head\" >\"$GIT_DIR/ORIG_HEAD\"\n> +\n> +find_one_real_parent () {\n\nThere are too many leftover debugging output in this function.\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\t\techo \"Ignoring $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\n> --short $real_parent).\"\n\nLinewrapped by MUA.\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\nWhat does this complex double loop compute differently from what \"git\nshow-branch --independent\" gives you?  Aside from that you will run slower\nbut you can take more than 25 branches?\n\nMore generally, I doubt it is really useful to let the user throw millions\nof potentially duplicate refs and have the merge silently record a\nfiltered out results.  Yes, you made the process of culling duplicates too\nchatty in the above part of the patch, and fmt-merge-msg will hopefully\nstill show what the user gave on the command line, but the heads used by\nthe real merge process now is very different from it.  The merge comment\nis totally disconnected from the reality.  Why is this an improvement?\n\nIf the goal is to allow Octopus that is more complex than the simplest\nkind, don't.  Octopus was deliberately written to allow the most simple\nkind and nothing else for a reason: bisectability.\n\nThe user should know what he is merging; throwing many heads that he does\nnot even know how they relate to each other, and call the resulting mess a\nmerge feels like a sure way to encourage a bad workflow.  Perhaps I am\nmissing something obvious that you are trying to automate, but I do not\nsee it, as there were no justification in the proposed commit log message\nnor in the documentation.\n\n> +if test $fast_forward = never -o\n\n-o?\n\n> +then\n> +\treal_parents=\"$@\"\n> +\tff_head=$head\n> +else\n> +\tfind_real_parents \"$@\"\n> +fi\n\nThis part is simply unacceptable.  At least please do not needlessly call\nfind_real_parents in the most common case of giving only one remote head.\n\n> diff --git a/t/t7601-merge-ff-strategies.sh b/t/t7601-merge-ff-strategies.sh\n> new file mode 100755\n> index 0000000..6c0a91a\n> --- /dev/null\n> +++ b/t/t7601-merge-ff-strategies.sh\n> @@ -0,0 +1,549 @@\n> +#!/bin/sh\n> +#\n> +# Copyright (c) 2007 Lars Hjemli\n> +#\n\nReally?\n\n> -- \n> 1.5.3.3\n\nMakes reviewers suspect that the patch submitter is not keeping up to\ndate.\n"},{"id":"71673","messageId":"m33aqxzknl.fsf@localhost.localdomain","threadId":"12628","inReplyTo":"402c10cd0803101959q619efa86pbd501e5e2cc018c2@mail.gmail.com","subject":"Re: [RFC/PATCH] Fast forward strategies allow, never, and only","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2008-03-11T09:15:34Z","receivedAt":"2008-03-11T09:15:34Z","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> +FAST FORWARD STRATEGIES\n> +-----------------------\n\nI think this should be named FAST FORWARD OPTIONS or something like\nthat.\n\n> +\n> +allow::\n> +\tDo not generate a merge commit if the merge resolved\n> +\tas a fast-forward, only update the branch pointer.\n> +\tThis is the default behavior of git-merge.\n\nThis is equivalent of current '--ff'; perhaps this should be mentioned\nas well in this option description.\n\n> +never::\n> +\tGenerate a merge commit even if the merge resolved\n> +\tas a fast-forward.\n\nThis is equivalent of current '--no-ff'; nevertheless I think that it\nwould be better to name this strategy 'commit' or 'merge', as in\n--ff=merge, or --ff=commit.\n\n> +only::\n> +\tOnly allow a fast-forward.  The merge will fail\n> +\tunless HEAD is up to date or the merge resolved as\n> +     a fast-forward.\n\nThis is equivalent of '--ff-only' or '--strategy=ff'... Errr...\nI'm sorry, such option does not exist, and it would be I guess\nuseful addition to default non '+' fetch refspec allowing fast-forward\nonly, and to receive.denyNonFastForwards to control push behavior.\n\n\n>  --no-ff::\n>  \tGenerate a merge commit even if the merge resolved as a\n> -\tfast-forward.\n> +\tfast-forward.  --on-ff is an alias for --ff=never.\n\nTypo: '--on-ff' instead of '--no-ff'.\n\n-- \nJakub Narebski\nPoland\nShadeHawk on #git\n"},{"id":"71776","messageId":"402c10cd0803112124i2726c32m75b9353d902df320@mail.gmail.com","threadId":"12628","inReplyTo":"m33aqxzknl.fsf@localhost.localdomain","subject":"Re: [RFC/PATCH] Fast forward strategies allow, never, and only","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-03-12T04:24:02Z","receivedAt":"2008-03-12T04:24:02Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"On Tue, Mar 11, 2008 at 1:15 AM, Jakub Narebski <jnareb@gmail.com> wrote:\n>  > +never::\n>  > +     Generate a merge commit even if the merge resolved\n>  > +     as a fast-forward.\n>\n>  This is equivalent of current '--no-ff'; nevertheless I think that it\n>  would be better to name this strategy 'commit' or 'merge', as in\n>  --ff=merge, or --ff=commit.\n\nIf there is consensus to change this I will.\n\n>  > +only::\n>  > +     Only allow a fast-forward.  The merge will fail\n>  > +     unless HEAD is up to date or the merge resolved as\n>  > +     a fast-forward.\n>\n>  This is equivalent of '--ff-only' or '--strategy=ff'... Errr...\n>  I'm sorry, such option does not exist, and it would be I guess\n>  useful addition to default non '+' fetch refspec allowing fast-forward\n>  only, and to receive.denyNonFastForwards to control push behavior.\n\nI agree, but it is over my head to implement this now.\n\n-- \nSverre Hvammen Johansen\n"},{"id":"71779","messageId":"7v1w6g8s1w.fsf@gitster.siamese.dyndns.org","threadId":"12628","inReplyTo":"402c10cd0803112124i2726c32m75b9353d902df320@mail.gmail.com","subject":"Re: [RFC/PATCH] Fast forward strategies allow, never, and only","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-03-12T04:50:03Z","receivedAt":"2008-03-12T04:50:03Z","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> On Tue, Mar 11, 2008 at 1:15 AM, Jakub Narebski <jnareb@gmail.com> wrote:\n>>  > +never::\n>>  > +     Generate a merge commit even if the merge resolved\n>>  > +     as a fast-forward.\n>>\n>>  This is equivalent of current '--no-ff'; nevertheless I think that it\n>>  would be better to name this strategy 'commit' or 'merge', as in\n>>  --ff=merge, or --ff=commit.\n>\n> If there is consensus to change this I will.\n\nI do not think Jakub's suggestion makes much sense.  If --ff stands for\n\"fast forward\", then --ff=merge could be explained (very unnaturally) as\n\"(in a) fast forward (situation, create a) merge\", which might make some\nsense as an incomplete sentence, but I cannot explain \"commit\" like that\neven with a broken sentence.\n\nUsing \"fast forward\" as a verb (\"instead of creating a needless merge,\njust move the head\"), then the mode of operations your patch proposes can\nbe described much clearer.  \"Never fast forward, always create an\nartificial merge if needed\", \"Only fast forward is allowed, never advance\nthis head by creating a true merge\", etc.\n\nSo I think the wording is fine.  What's more necessary in the documention\nis how and why these restrictions are useful in what situations, workflows\nand management policies.\n"},{"id":"71782","messageId":"402c10cd0803112246q4ec98018pe9a34b95e32cf1@mail.gmail.com","threadId":"12628","inReplyTo":"7vk5k9eqax.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFC/PATCH] Fast forward strategies allow, never, and only","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-03-12T05:46:30Z","receivedAt":"2008-03-12T05:46:30Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"On Mon, Mar 10, 2008 at 10:19 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> \"Sverre Hvammen Johansen\" <hvammen@gmail.com> writes:\n>\n>  > @@ -153,8 +153,8 @@ parse_config () {\n>  >               --summary)\n>  >                       show_diffstat=t ;;\n>  >               --squash)\n>  > -                     test \"$allow_fast_forward\" = t ||\n>  > -                             die \"You cannot combine --squash with --no-ff.\"\n>  > +                     test \"$fast_forward\" = allow ||\n>  > +                             die \"You cannot combine --squash with --ff=never.\"\n>\n>  Why does the user get this message after saying --ff=only?\n\nBug.  What should the semantic be?  To me it makes sense that a squash\noweride the ff options instead of giving a error, but specifying a ff\noption after --squash is an error.\n\n>  How does this code parse \"git merge --ff my_other_branch\"?\n\nIt is getting late so I need to get back to you about this.\n\n>  Shouldn't you issue the same error message for these two inputs?\n>\n>         \"git merge --ff=never --squash\"\n>         \"git merge --squash --ff=never\"\n\nAllow the first one since --ff=never can be in the config file and\ngive error on the last one.\n\n>  What does this complex double loop compute differently from what \"git\n>  show-branch --independent\" gives you?  Aside from that you will run slower\n>  but you can take more than 25 branches?\n\nThe main issue is that show-branch --independent does not give me the\ndesired order for these branches.  I want the first branch to be head\nor something that can be fast forwarded from head.  The second branch\nshould be the next branch in the specified list that have not been\neliminated or something that can be fast forwarded from this, and so\non and so forth.  This is an absolute requirement for the first\nargument (head).  If show-branch had a documented order and meet the\nabsolute requirement above I would prefer show-branch --independentr\ninstead of this nasty loop.\n\n>  More generally, I doubt it is really useful to let the user throw millions\n>  of potentially duplicate refs and have the merge silently record a\n>  filtered out results.  Yes, you made the process of culling duplicates too\n>  chatty in the above part of the patch, and fmt-merge-msg will hopefully\n>  still show what the user gave on the command line, but the heads used by\n>  the real merge process now is very different from it.  The merge comment\n>  is totally disconnected from the reality.  Why is this an improvement?\n\nWe already do this in the case where we have head pluss one branch.\nIf it results in only one real parent we throw one of them away\nresulting in a fast forward or an \"up to date\".  The suggested patch\nis just a generalization over this to the case where we have head and\nmore than one branch.\n\n>  If the goal is to allow Octopus that is more complex than the simplest\n>  kind, don't.  Octopus was deliberately written to allow the most simple\n>  kind and nothing else for a reason: bisectability.\n\nThat is not the goal.\n\n>  The user should know what he is merging; throwing many heads that he does\n>  not even know how they relate to each other, and call the resulting mess a\n>  merge feels like a sure way to encourage a bad workflow.\n\nWe do merges all the time without knowing what we actually are\nmerging.  That is something that happen in many work flows.  I assume\nthat you don't want a real merge in the case that you are \"up to date\"\nwith your remote or your head can be fast forward.  For the users\nconvenience we do a fast forward or report it to be \"up to date\".\nExactly the same argument holds where there are more than one remote\ninvolved.  The user may not know who is ahead and who is behind and he\nusually want the commit to record a simple history as possible.\n\n>  > +then\n>  > +     real_parents=\"$@\"\n>  > +     ff_head=$head\n>  > +else\n>  > +     find_real_parents \"$@\"\n>  > +fi\n>\n>  This part is simply unacceptable.  At least please do not needlessly call\n>  find_real_parents in the most common case of giving only one remote head.\n\nI now keep common_b in common so subsequent calls to git merge-base\n--all can be optimized away for the most common case.\n\n-- \nSverre Hvammen Johansen\n"},{"id":"71783","messageId":"402c10cd0803112251n675ba7e8n96e2e18ee60ac5ed@mail.gmail.com","threadId":"12628","inReplyTo":"7v1w6g8s1w.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFC/PATCH] Fast forward strategies allow, never, and only","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-03-12T05:51:41Z","receivedAt":"2008-03-12T05:51:41Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"On Tue, Mar 11, 2008 at 8:50 PM, Junio C Hamano <gitster@pobox.com> wrote:\n>  So I think the wording is fine.  What's more necessary in the documention\n>  is how and why these restrictions are useful in what situations, workflows\n>  and management policies.\n\nI agree.  I plan a revised patch later this week.  I may need some\nhelp with the documentation.\n\n-- \nSverre Hvammen Johansen\n"},{"id":"72020","messageId":"402c10cd0803131935g6cf2b224k6a36f75101a03f31@mail.gmail.com","threadId":"12628","inReplyTo":"7vk5k9eqax.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFC/PATCH] Fast forward strategies allow, never, and only","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-03-14T02:35:32Z","receivedAt":"2008-03-14T02:35:32Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"On Mon, Mar 10, 2008 at 10:19 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> \"Sverre Hvammen Johansen\" <hvammen@gmail.com> writes:\n>  >               --ff)\n>  > -                     allow_fast_forward=t ;;\n>  > +                     case \"$2\" in\n>  > +                     allow|never|only)\n>  > +                             fast_forward=$2 squash= no_commit= ; shift ;;\n>  > +                     -*)\n>  > +                             fast_forward=allow squash= no_commit= ;;\n>  > +                     *)\n>  > +                             die \"available fast-forward strategies are: allow, newer, and only\" ;;\n>\n>  How does this code parse \"git merge --ff my_other_branch\"?\n\ngit rev-parse (in git-sh-setup.sh) will rewrite this to \"git merge\n--ff -- my_other_branch\".  However, it will also rewrite \"git merge\n--ff=only my_other_branch\" to \"git merge --ff only --\nmy_other_branch\".  Options in the config file are parsed directly by\nparse_config without these rewrites.  This means that second case\nabove is the case where --ff don't have any arguments.  First and last\ncase is the case where --ff have an argument.\n\n-- \nSverre Hvammen Johansen\n"},{"id":"72211","messageId":"402c10cd0803152344h3fb6781fxd8e6375c8491f8a1@mail.gmail.com","threadId":"12628","inReplyTo":"402c10cd0803112246q4ec98018pe9a34b95e32cf1@mail.gmail.com","subject":"Re: [RFC/PATCH] Fast forward strategies allow, never, and only","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-03-16T06:44:48Z","receivedAt":"2008-03-16T06:44:48Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"On Tue, Mar 11, 2008 at 9:46 PM, Sverre Hvammen Johansen\n<hvammen@gmail.com> wrote:\n>  To me it makes sense that a squash oweride the ff options instead\n>  of giving a error, but specifying a ff option after --squash is an error.\n\nAfter some consideration I am convinced we should allow --squash\ncombined with any of the --ff options.  The same for --no-commit.\n--squash combined with any of the --ff options means that the index\nand the tree will only be updated if a merge without --squash would\nhave done the same.\n\nThat mean that t7600-merge.sh will break as follows:\n\n* FAIL 19: combining --squash and --no-ff is refused\n\n                test_must_fail git merge --squash --no-ff c1 &&\n                test_must_fail git merge --no-ff --squash c1\n\n-- \nSverre Hvammen Johansen\n"},{"id":"72342","messageId":"402c10cd0803172127u480276c9s4f9d716b4912ad5e@mail.gmail.com","threadId":"12628","inReplyTo":"402c10cd0803101959q619efa86pbd501e5e2cc018c2@mail.gmail.com","subject":"Re: [RFC/PATCH Second draft] Fast forward strategies allow, never, and only","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-03-18T04:27:13Z","receivedAt":"2008-03-18T04:27:13Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"New fast forward strategies, only, is introduced.  This new fast\nforward strategy prevents real merges.\n\nFF strategy \"only\" 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.  However,\nfast-forward of head in combination with a real merge is\nhandled as before.\n\nSee the documentation for further explanation of this feature.\n\nSigned-off-by: Sverre Hvammen Johansen <hvammen@gmail.com>\n---\n Documentation/fast-forward-options.txt |   69 +++\n Documentation/git-merge.txt            |    6 +-\n Documentation/git-pull.txt             |    2 +\n Documentation/merge-options.txt        |    9 +-\n git-merge.sh                           |  246 +++++++-----\n git-pull.sh                            |    4 +-\n t/t7600-merge.sh                       |    5 -\n t/t7601-merge-ff-options.sh            |  722 ++++++++++++++++++++++++++++++++\n 8 files changed, 946 insertions(+), 117 deletions(-)\n create mode 100644 Documentation/fast-forward-options.txt\n create mode 100755 t/t7601-merge-ff-options.sh\n\ndiff --git a/Documentation/fast-forward-options.txt\nb/Documentation/fast-forward-options.txt\nnew file mode 100644\nindex 0000000..87fd0ae\n--- /dev/null\n+++ b/Documentation/fast-forward-options.txt\n@@ -0,0 +1,69 @@\n+FAST FORWARD OPTIONS\n+--------------------\n+\n+allow::\n+       Do not generate a merge commit if the merge resolved as a\n+       fast-forward, only update the branch pointer.  This is the\n+       default behavior.  This option is equivalent of '--ff' without\n+       any argument.\n+\n+never::\n+       Generate a merge commit even if the merge resolved as a\n+       fast-forward.  This option is equivalent of '--no-ff'.\n+\n+only::\n+       Only allow a fast-forward.  The merge will fail unless HEAD is\n+       up to date or the merge resolved 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+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'.  The last merge of\n+topicA would have failed with '--ff=only'.  Topic A consist of those\n+commits that can be reached from master^2 without passing through any\n+of the first-parent ancestries of master.\n+\n+However, if your workflow require a linear history for the special\n+branch (\"master\"), topic branches must be rebased before merging them\n+back to \"master\".  A pull or a merge from the \"master branch of a\n+topic branch may accidentally introduce a merge commit that was not\n+already in the topic branch if the topic that were merged was not\n+properly rebased.  This will creating a none linear history.\n+\n+Using \"only fast forward\" policy ensures that whenever a pull or a\n+merge is performed it will fail unless the merge can be resolved as a\n+fast forward.  This will however not guarantee a linear history since\n+the topic branches that are merged in may have merge commits recorded.\n+You may therefor need to use this policy on the topic branches as\n+well.\n+\n+\"Only fast forward\" policy on the \"master\" branch can be enforced by\n+setting the mergeoptions for that branch using git config:\n+\n+------------\n+% git config branch.master.mergeoptions --ff=only\n+------------\ndiff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt\nindex c136b10..2af33d8 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] [--summary] [--no-commit] [--squash] [-s <strategy>]...\n+'git-merge' [-n] [--summary] [--no-commit] [--squash]\n+       [-s <strategy>]... [--ff[=<fast forward option>]]\n        [-m <msg>] <remote> <remote>...\n 'git-merge' <msg> HEAD <remote>...\n\n@@ -37,6 +38,9 @@ include::merge-options.txt[]\n        least one <remote>.  Specifying more than one <remote>\n        obviously 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 7378943..222482f 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -50,6 +50,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 9f1fc82..cf4881b 100644\n--- a/Documentation/merge-options.txt\n+++ b/Documentation/merge-options.txt\n@@ -29,12 +29,11 @@\n\n --no-ff::\n        Generate a merge commit even if the merge resolved as a\n-       fast-forward.\n+       fast-forward.  --no-ff is an alias for --ff=never.\n\n---ff::\n-       Do not generate a merge commit if the merge resolved as\n-       a fast-forward, only update the branch pointer. This is\n-       the default behavior of git-merge.\n+--ff[=<fast forward option>]::\n+       Select fast forward option.  --ff without any argument\n+       is an alias for --ff=allow which is the default behavior.\n\n -s <strategy>, \\--strategy=<strategy>::\n        Use the given merge strategy; can be supplied more than\ndiff --git a/git-merge.sh b/git-merge.sh\nindex 7dbbb1d..a98cd77 100755\n--- a/git-merge.sh\n+++ b/git-merge.sh\n@@ -12,7 +12,7 @@ summary              show a diffstat at the end of the merge\n n,no-summary         don't show a diffstat at the end of the merge\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@@ -35,7 +35,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=\n\n@@ -153,8 +153,6 @@ parse_config () {\n                --summary)\n                        show_diffstat=t ;;\n                --squash)\n-                       test \"$allow_fast_forward\" = t ||\n-                               die \"You cannot combine --squash with --no-ff.\"\n                        squash=t no_commit=t ;;\n                --no-squash)\n                        squash= no_commit= ;;\n@@ -163,18 +161,33 @@ parse_config () {\n                --no-commit)\n                        no_commit=t ;;\n                --ff)\n-                       allow_fast_forward=t ;;\n+                       case \"$2\" in\n+                       allow|never|only)\n+                               fast_forward=$2; shift ;;\n+                       -*)\n+                               fast_forward=allow ;;\n+                       *)\n+                               die \"Available fast-forward strategies\nare: allow, newer, and only\" ;;\n+                       esac\n+                       ;;\n+               --ff=*)\n+                       fast_forward=${1#--ff=}\n+                       case \"$fast_forward\" in\n+                           allow|never|only)\n+                               ;;\n+                           *)\n+                               die \"Available fast-forward strategies\nare: allow, newer, and only\" ;;\n+                       esac\n+                       ;;\n                --no-ff)\n-                       test \"$squash\" != t ||\n-                               die \"You cannot combine --squash with --no-ff.\"\n-                       allow_fast_forward=f ;;\n+                       fast_forward=never ;;\n                -s|--strategy)\n                        shift\n                        case \" $all_strategies \" in\n                        *\" $1 \"*)\n                                use_strategies=\"$use_strategies$1 \" ;;\n                        *)\n-                               die \"available strategies are:\n$all_strategies\" ;;\n+                               die \"Available strategies are:\n$all_strategies\" ;;\n                        esac\n                        ;;\n                -m|--message)\n@@ -279,24 +292,68 @@ do\n done\n set x $remoteheads ; shift\n\n+echo \"$head\" >\"$GIT_DIR/ORIG_HEAD\"\n+\n+if test $fast_forward = never\n+then\n+       real_parents=$(git rev-parse \"$@\")\n+       ff_head=$head\n+       common=\n+else\n+       if test $# = 1\n+       then\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+               real_parents=$(git show-branch --independent $head \"$@\")\n+               ff_head=${real_parents%%$LF*}\n+               real_parents=${real_parents#$ff_head}\n+               real_parents=${real_parents#$LF}\n+               common=\n+       fi\n+fi\n+\n+if test -n \"$real_parents\"\n+then\n+       if test $fast_forward = only\n+       then\n+               die \"--ff=only can not handle more than one real parent\"\n+       elif test $head != $ff_head\n+       then\n+               real_parents=\"$ff_head$LF$real_parents\"\n+       fi\n+fi\n+\n case \"$use_strategies\" in\n '')\n-       case \"$#\" in\n-       1)\n-               var=\"`git config --get pull.twohead`\"\n+       case \"$real_parents\" in\n+       ?*\"$LF\"?*)\n+               var=\"`git config --get pull.octopus`\"\n                if test -n \"$var\"\n                then\n                        use_strategies=\"$var\"\n                else\n-                       use_strategies=\"$default_twohead_strategies\"\n+                       use_strategies=\"$default_octopus_strategies\"\n                fi ;;\n        *)\n-               var=\"`git config --get pull.octopus`\"\n+               var=\"`git config --get pull.twohead`\"\n                if test -n \"$var\"\n                then\n                        use_strategies=\"$var\"\n                else\n-                       use_strategies=\"$default_octopus_strategies\"\n+                       use_strategies=\"$default_twohead_strategies\"\n                fi ;;\n        esac\n        ;;\n@@ -308,7 +365,7 @@ do\n        do\n                case \" $s \" in\n                *\" $ss \"*)\n-                       allow_fast_forward=f\n+                       fast_forward=never\n                        break\n                        ;;\n                esac\n@@ -323,88 +380,72 @@ do\n                esac\n        done\n done\n-\n-case \"$#\" in\n-1)\n-       common=$(git merge-base --all $head \"$@\")\n-       ;;\n-*)\n-       common=$(git show-branch --merge-base $head \"$@\")\n-       ;;\n-esac\n-echo \"$head\" >\"$GIT_DIR/ORIG_HEAD\"\n-\n-case \"$allow_fast_forward,$#,$common,$no_commit\" in\n-?,*,'',*)\n-       # No common ancestors found. We need a real merge.\n-       ;;\n-?,1,\"$1\",*)\n-       # If head can reach all the merge then we are up to date.\n-       # but first the most common case of merging one remote.\n-       finish_up_to_date \"Already up-to-date.\"\n-       exit 0\n-       ;;\n-t,1,\"$head\",*)\n-       # Again the most common case of merging one remote.\n-       echo \"Updating $(git rev-parse --short $head)..$(git rev-parse\n--short $1)\"\n-       git update-index --refresh 2>/dev/null\n-       msg=\"Fast forward\"\n-       if test -n \"$have_message\"\n+if test -z \"$real_parents\"\n+then\n+       if test $head = $ff_head\n        then\n-               msg=\"$msg (no commit created; -m option ignored)\"\n-       fi\n-       new_head=$(git rev-parse --verify \"$1^0\") &&\n-       git read-tree -v -m -u --exclude-per-directory=.gitignore\n$head \"$new_head\" &&\n-       finish \"$new_head\" \"$msg\" || exit\n-       dropsave\n-       exit 0\n-       ;;\n-?,1,?*\"$LF\"?*,*)\n-       # We are not doing octopus and not fast forward.  Need a\n-       # real merge.\n-       ;;\n-?,1,*,)\n-       # We are not doing octopus, not fast forward, and have only\n-       # one common.\n-       git update-index --refresh 2>/dev/null\n-       case \"$allow_trivial_merge\" in\n-       t)\n-               # See if it is really trivial.\n-               git var GIT_COMMITTER_IDENT >/dev/null || exit\n-               echo \"Trying really trivial in-index merge...\"\n-               if git read-tree --trivial -m -u -v $common $head \"$1\" &&\n-                  result_tree=$(git write-tree)\n-               then\n-                       echo \"Wonderful.\"\n-                       result_commit=$(\n-                               printf '%s\\n' \"$merge_msg\" |\n-                               git commit-tree $result_tree -p HEAD -p \"$1\"\n-                       ) || exit\n-                       finish \"$result_commit\" \"In-index merge\"\n-                       dropsave\n-                       exit 0\n-               fi\n-               echo \"Nope.\"\n-       esac\n-       ;;\n-*)\n-       # An octopus.  If we can reach all the remote we are up to date.\n-       up_to_date=t\n-       for remote\n-       do\n-               common_one=$(git merge-base --all $head $remote)\n-               if test \"$common_one\" != \"$remote\"\n+               finish_up_to_date \"Already up-to-date.\"\n+               exit 0\n+       elif test $fast_forward = never\n+       then\n+               real_parents=\"$ff_head\"\n+               ff_head=$head\n+       else\n+               echo \"Updating $(git rev-parse --short $head)..$(git\nrev-parse --short $ff_head)\"\n+               git update-index --refresh 2>/dev/null\n+               msg=\"Fast forward\"\n+               if test -n \"$have_message\"\n                then\n-                       up_to_date=f\n-                       break\n+                       msg=\"$msg (no commit created; -m option ignored)\"\n                fi\n-       done\n-       if test \"$up_to_date\" = t\n-       then\n-               finish_up_to_date \"Already up-to-date. Yeeah!\"\n+               new_head=$(git rev-parse --verify \"$ff_head^0\") &&\n+               git read-tree -v -m -u\n--exclude-per-directory=.gitignore $head \"$new_head\" &&\n+               finish \"$new_head\" \"$msg\" || exit\n+               dropsave\n                exit 0\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+case \"$real_parents\" in\n+?*\"$LF\"?*)\n+       # We have more than one parent\n+       common=$(git show-branch --merge-base $head $real_parents)\n        ;;\n+*)\n+       # We have exactly one parent\n+       test -n \"$common\" || common=$(git merge-base --all $head $real_parents)\n+       case \"$common\" in\n+       ?*\"$LF\"?*)\n+               # We are not doing octopus and not fast forward.  Need a\n+               # real merge.\n+               ;;\n+       *)\n+               git update-index --refresh 2>/dev/null\n+               if test \"$allow_trivial_merge\" = t\n+               then\n+                       # See if it is really trivial.\n+                       git var GIT_COMMITTER_IDENT >/dev/null || exit\n+                       echo \"Trying really trivial in-index merge...\"\n+                       if git read-tree --trivial -m -u -v $common\n$head $real_parents &&\n+                               result_tree=$(git write-tree)\n+                       then\n+                               echo \"Wonderful.\"\n+                               result_commit=$(\n+                                       printf '%s\\n' \"$merge_msg\" |\n+                                       git commit-tree $result_tree\n-p HEAD -p $real_parents\n+                               ) || exit\n+                               finish \"$result_commit\" \"In-index merge\"\n+                               dropsave\n+                               exit 0\n+                       fi\n+                       echo \"Nope.\"\n+               fi ;;\n+       esac ;;\n esac\n\n # We are going to make a new commit.\n@@ -445,7 +486,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@@ -481,17 +522,12 @@ done\n # auto resolved the merge cleanly.\n if test '' != \"$result_tree\"\n then\n-    if test \"$allow_fast_forward\" = \"t\"\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@@ -515,7 +551,7 @@ case \"$best_strategy\" in\n        echo \"Rewinding the tree to pristine...\"\n        restorestate\n        echo \"Using the $best_strategy to prepare resolving by hand.\"\n-       git-merge-$best_strategy $common -- \"$head_arg\" \"$@\"\n+       git-merge-$best_strategy $common -- \"$head_arg\" $real_parents\n        ;;\n esac\n\ndiff --git a/git-pull.sh b/git-pull.sh\nindex 3ce32b5..5bc84a6 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-summary] [--[no-]commit] [--[no-]squash]\n[--[no-]ff] [-s strategy]... [<fetch-options>] <repo> <head>...'\n+USAGE='[-n | --no-summary] [--[no-]commit] [--[no-]squash]\n[--ff=<ff-strategy>] [-s strategy]... [<fetch-options>] <repo>\n<head>...'\n LONG_USAGE='Fetch one or more remote refs and merge it/them into the\ncurrent HEAD.'\n SUBDIRECTORY_OK=Yes\n OPTIONS_SPEC=\n@@ -41,6 +41,8 @@ do\n                no_ff=--ff ;;\n        --no-ff)\n                no_ff=--no-ff ;;\n+       --ff=allow|--ff=only|--ff=never)\n+               no_ff=$1 ;;\n        -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\\\n                --strateg=*|--strategy=*|\\\n        -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy)\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex 5d16628..085f55f 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -428,11 +428,6 @@ test_expect_success 'merge c0 with c1 (no-ff)' '\n\n test_debug 'gitk --all'\n\n-test_expect_success 'combining --squash and --no-ff is refused' '\n-       test_must_fail git merge --squash --no-ff c1 &&\n-       test_must_fail git merge --no-ff --squash c1\n-'\n-\n test_expect_success 'merge c0 with c1 (ff overrides no-ff)' '\n        git reset --hard c0 &&\n        git config branch.master.mergeoptions \"--no-ff\" &&\ndiff --git a/t/t7601-merge-ff-options.sh b/t/t7601-merge-ff-options.sh\nnew file mode 100755\nindex 0000000..e78d32e\n--- /dev/null\n+++ b/t/t7601-merge-ff-options.sh\n@@ -0,0 +1,722 @@\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.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+       echo \"Merge commit 'c2'\" >msg.1-5 &&\n+       echo \"Merge commit 'c2'; commit 'c3'\" >msg.1-5-9 &&\n+       echo \"Squashed commit of the following:\" >squash.1 &&\n+       echo >>squash.1 &&\n+       git log --no-merges ^HEAD c1 >>squash.1 &&\n+       echo \"Squashed commit of the following:\" >squash.1-5 &&\n+       echo >>squash.1-5 &&\n+       git log --no-merges ^HEAD c2 >>squash.1-5 &&\n+       echo \"Squashed commit of the following:\" >squash.1-5-9 &&\n+       echo >>squash.1-5-9 &&\n+       git log --no-merges ^HEAD c2 c3 >>squash.1-5-9\n+}\n+\n+verify_diff() {\n+       if ! diff -u \"$1\" \"$2\"\n+       then\n+               echo \"$3\"\n+               false\n+       fi\n+}\n+\n+verify_merge() {\n+       verify_diff \"$2\" \"$1\" \"[OOPS] bad merge result\" &&\n+       if test $(git ls-files -u | wc -l) -gt 0\n+       then\n+               echo \"[OOPS] unmerged files\"\n+               false\n+       fi &&\n+       if ! git diff --exit-code\n+       then\n+               echo \"[OOPS] working tree != index\"\n+               false\n+       fi &&\n+       if test -n \"$3\"\n+       then\n+               git show -s --pretty=format:%s HEAD >msg.act &&\n+               verify_diff \"$3\" msg.act \"[OOPS] bad merge message\"\n+       fi\n+}\n+\n+verify_head() {\n+       if test \"$1\" != \"$(git rev-parse HEAD)\"\n+       then\n+               echo \"[OOPS] HEAD != $1\"\n+               false\n+       fi\n+}\n+\n+verify_parents() {\n+       i=1\n+       while test $# -gt 0\n+       do\n+               if test \"$1\" != \"$(git rev-parse HEAD^$i)\"\n+               then\n+                       echo \"[OOPS] HEAD^$i != $1\"\n+                       return 1\n+               fi\n+               i=$(expr $i + 1)\n+               shift\n+       done\n+}\n+\n+verify_mergeheads() {\n+       i=1\n+       if ! test -f .git/MERGE_HEAD\n+       then\n+               echo \"[OOPS] MERGE_HEAD is missing\"\n+               false\n+       fi &&\n+       while test $# -gt 0\n+       do\n+               head=$(head -n $i .git/MERGE_HEAD | tail -n 1)\n+               if test \"$1\" != \"$head\"\n+               then\n+                       echo \"[OOPS] MERGE_HEAD $i != $1\"\n+                       return 1\n+               fi\n+               i=$(expr $i + 1)\n+               shift\n+       done\n+}\n+\n+verify_no_mergehead() {\n+       if test -f .git/MERGE_HEAD\n+       then\n+               echo \"[OOPS] MERGE_HEAD exists\"\n+               false\n+       fi\n+}\n+\n+\n+test_expect_success 'setup' '\n+       git add file &&\n+       test_tick &&\n+       git commit -m \"commit 0\" &&\n+       git tag c0 &&\n+       c0=$(git rev-parse HEAD) &&\n+\n+       cp file.1 file &&\n+       git add file &&\n+       test_tick &&\n+       git commit -m \"commit 1\" &&\n+       git tag c1 &&\n+       c1=$(git rev-parse HEAD) &&\n+       test_tick &&\n+\n+       git reset --hard \"$c0\" &&\n+       cp file.5 file &&\n+       git add file &&\n+       git commit -m \"commit 2\" &&\n+       test_tick &&\n+       git tag c2 &&\n+       c2=$(git rev-parse HEAD) &&\n+\n+       git reset --hard \"$c0\" &&\n+       cp file.9 file &&\n+       git add file &&\n+       test_tick &&\n+       git commit -m \"commit 3\" &&\n+       git tag c3 &&\n+       c3=$(git rev-parse HEAD) &&\n+       test_tick &&\n+\n+       git reset --hard \"$c1\" &&\n+       cat extend >>file &&\n+       git add file &&\n+       git commit -m \"commit 4\" &&\n+       git tag x1 &&\n+       x1=$(git rev-parse HEAD) &&\n+       test_tick &&\n+\n+       git reset --hard \"$c1\" &&\n+       git merge \"$c2\" &&\n+       git tag x0 &&\n+       x0=$(git rev-parse HEAD) &&\n+       test_tick &&\n+\n+       git reset --hard \"$c2\" &&\n+       cat extend >>file &&\n+       git add file &&\n+       git commit -m \"commit 5\" &&\n+       git tag x2 &&\n+       x2=$(git rev-parse HEAD) &&\n+       test_tick &&\n+\n+       git reset --hard \"$x1\" &&\n+       git merge \"$x0\" &&\n+       git tag y1 &&\n+       y1=$(git rev-parse HEAD) &&\n+       test_tick &&\n+\n+       git reset --hard \"$x0\" &&\n+       git merge \"$x2\" &&\n+       git tag y2 &&\n+       y2=$(git rev-parse HEAD) &&\n+       test_tick &&\n+\n+       git reset --hard \"$y1\" &&\n+       git merge \"$y2\" &&\n+       git tag y3 &&\n+       y3=$(git rev-parse HEAD) &&\n+       test_tick &&\n+       git reset --hard \"$c0\" &&\n+       create_merge_msgs &&\n+\n+       git reset --hard x1 &&\n+       git clone .git clone &&\n+       git config remote.clone.url clone &&\n+       git config remote.clone.fetch \"+refs/heads/*:refs/remotes/clone/*\" &&\n+\n+       (mkdir new && cd new && git init && cp ../file.9 file2 && git\nadd file2 && test_tick && git commit -m \"commit new\") &&\n+       git config remote.new.url new &&\n+       git config remote.new.fetch \"+refs/heads/*:refs/remotes/new/*\"\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with c1 (--ff=only overrides --no-ff)' '\n+       git reset --hard c0 &&\n+       git config branch.master.mergeoptions \"--no-ff\" &&\n+       git merge --ff=only c1 &&\n+       verify_merge file result.1 &&\n+       verify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with c1 (--ff=only in config)' '\n+       git reset --hard c0 &&\n+       git config branch.master.mergeoptions \"--ff=only\" &&\n+       git merge c1 &&\n+       test_tick &&\n+       verify_merge file result.1 &&\n+       verify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with c0 (--ff=only in config)' '\n+       git reset --hard c1 &&\n+       git config branch.master.mergeoptions \"--ff=only\" &&\n+       git merge c0 &&\n+       verify_merge file result.1 &&\n+       verify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with c2 (--ff=only in config)' '\n+       git reset --hard c1 &&\n+       test_tick &&\n+       git config branch.master.mergeoptions \"--ff=only\" &&\n+       test_must_fail git merge c2 &&\n+       verify_merge file result.1 &&\n+       verify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with c1 (--ff=only)' '\n+       git reset --hard c0 &&\n+       test_tick &&\n+       git merge --ff=only c1 &&\n+       verify_merge file result.1 &&\n+       verify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with c0 (--ff=only)' '\n+       git reset --hard c1 &&\n+       test_tick &&\n+       git merge --ff=only c0 &&\n+       verify_merge file result.1 &&\n+       verify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with c1 and c2 (--ff=only)' '\n+       git reset --hard c0 &&\n+       test_must_fail git merge --ff=only c1 c2 &&\n+       verify_merge file result.0 &&\n+       verify_head $c0\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with c0 (--ff=only)' '\n+       git reset --hard c1 &&\n+       test_tick &&\n+       git merge --ff=only c0 &&\n+       verify_merge file result.1 &&\n+       verify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with c2 (--ff=only overrides --no-ff)' '\n+       git reset --hard c1 &&\n+       git config branch.master.mergeoptions \"--no-ff\" &&\n+       test_tick &&\n+       test_must_fail git merge c2 --ff=only &&\n+       verify_merge file result.1 &&\n+       verify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with c1 (--no-ff overrides --ff=only)' '\n+       git reset --hard c0 &&\n+       git config branch.master.mergeoptions \"--ff=only\" &&\n+       test_tick &&\n+       git merge --no-ff c1 &&\n+       verify_merge file result.1 &&\n+       verify_parents $c0 $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with c2 (--ff owerrides --ff=only)' '\n+       git reset --hard c1 &&\n+       git config branch.master.mergeoptions \"--ff=only\" &&\n+       test_tick &&\n+       git merge --ff c2 &&\n+       verify_merge file result.1-5 &&\n+       verify_parents $c1 $c2\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with c1 and c2' '\n+       git reset --hard c0 &&\n+       git config branch.master.mergeoptions \"\" &&\n+       test_tick &&\n+       git merge c1 c2 &&\n+       verify_merge file result.1-5 &&\n+       verify_parents $c1 $c2\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with c0, c2, c0, and c1' '\n+       git reset --hard c1 &&\n+       git config branch.master.mergeoptions \"\" &&\n+       test_tick &&\n+       git merge c0 c2 c0 c1 &&\n+       verify_merge file result.1-5 &&\n+       verify_parents $c1 $c2\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge y2 with x0, c3, and c0' '\n+       git reset --hard y2 &&\n+       git config branch.master.mergeoptions \"\" &&\n+       test_tick &&\n+       git merge x0 c3 c0 &&\n+       verify_merge file result.1-5-9-13 &&\n+       verify_parents $y2 $c3\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge x0 with y2, c3, and c0' '\n+       git reset --hard x0 &&\n+       git config branch.master.mergeoptions \"\" &&\n+       test_tick &&\n+       git merge y2 c3 c0 &&\n+       verify_merge file result.1-5-9-13 &&\n+       verify_parents $y2 $c3\n+'\n+\n+test_debug 'gitk --all'\n+\n+\n+test_expect_success 'merge c1 with x0 (--squash combined with --ff=only)' '\n+       git reset --hard c1 &&\n+       git config branch.master.mergeoptions \"\" &&\n+       test_tick &&\n+       git merge x0 --squash --ff=only &&\n+       verify_merge file result.1-5 &&\n+       verify_head $c1 &&\n+       git commit &&\n+       verify_parents $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+\n+test_expect_success 'merge x0 with c1 (--squash combined with --ff=only)' '\n+       git reset --hard x0 &&\n+       git config branch.master.mergeoptions \"\" &&\n+       test_tick &&\n+       git merge c1 --squash --ff=only &&\n+       verify_merge file result.1-5 &&\n+       verify_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+       git reset --hard c1 &&\n+       git config branch.master.mergeoptions \"\" &&\n+       test_tick &&\n+       test_must_fail git merge c2 --squash --ff=only &&\n+       verify_merge file result.1 &&\n+       verify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+\n+test_expect_success 'merge c1 with x0 (--squash combined with --ff=never)' '\n+       git reset --hard c1 &&\n+       git config branch.master.mergeoptions \"\" &&\n+       test_tick &&\n+       git merge x0 --squash --ff=never &&\n+       verify_merge file result.1-5 &&\n+       verify_head $c1 &&\n+       git commit &&\n+       verify_parents $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+\n+test_expect_success 'merge x0 with c1 (--squash combined with --ff=never)' '\n+       git reset --hard x0 &&\n+       git config branch.master.mergeoptions \"\" &&\n+       test_tick &&\n+       git merge c1 --squash --ff=never &&\n+       verify_merge file result.1-5 &&\n+       verify_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+       git reset --hard c1 &&\n+       git config branch.master.mergeoptions \"\" &&\n+       test_tick &&\n+       git merge c2 --squash --ff=never &&\n+       verify_merge file result.1-5 &&\n+       verify_head $c1 &&\n+       git commit &&\n+       verify_parents $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+       git reset --hard c1 &&\n+       git config branch.master.mergeoptions \"\" &&\n+       test_tick &&\n+       git merge x0 --no-commit --ff=only &&\n+       verify_merge file result.1-5 &&\n+       verify_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+       git reset --hard x0 &&\n+       git config branch.master.mergeoptions \"\" &&\n+       test_tick &&\n+       git merge c1 --no-commit --ff=only &&\n+       verify_merge file result.1-5 &&\n+       verify_head $x0\n+'\n+\n+test_debug 'gitk --all'\n+\n+\n+test_expect_success 'merge c1 with c2 (--no-commit combined with --ff=only)' '\n+       git reset --hard c1 &&\n+       git config branch.master.mergeoptions \"\" &&\n+       test_tick &&\n+       test_must_fail git merge c2 --no-commit --ff=only &&\n+       verify_merge file result.1 &&\n+       verify_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+       git reset --hard c1 &&\n+       git config branch.master.mergeoptions \"\" &&\n+       test_tick &&\n+       git merge x0 --no-commit --ff=never &&\n+       verify_merge file result.1-5 &&\n+       verify_head $c1 &&\n+       git commit &&\n+       verify_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+       git reset --hard x0 &&\n+       git config branch.master.mergeoptions \"\" &&\n+       test_tick &&\n+       git merge c1 --no-commit --ff=never &&\n+       verify_merge file result.1-5 &&\n+       verify_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+       git reset --hard c1 &&\n+       git config branch.master.mergeoptions \"\" &&\n+       test_tick &&\n+       git merge c2 --no-commit --ff=never &&\n+       verify_merge file result.1-5 &&\n+       verify_head $c1 &&\n+       git commit &&\n+       verify_parents $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+\n+test_expect_success 'merge c1 with x1 (pull --ff=only)' '\n+       git reset --hard c1 &&\n+       test_tick &&\n+       git pull --ff=only clone refs/heads/master &&\n+       verify_merge file result.1-13 &&\n+       verify_head $x1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge x2 with x1 (pull --ff=only)' '\n+       git reset --hard x2 &&\n+       test_tick &&\n+       test_must_fail git pull --ff=only clone refs/heads/master &&\n+       verify_merge file result.5-13 &&\n+       verify_head $x2\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c1 with new repository (pull --ff=only)' '\n+       git reset --hard c1 &&\n+       test_tick &&\n+       test_must_fail git pull --ff=only new refs/heads/master &&\n+       verify_merge file result.1 &&\n+       verify_head $c1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_done\n\n-- \nSverre Hvammen Johansen\n"},{"id":"72355","messageId":"20080318135704.GA11922@kooxoo235","threadId":"12628","inReplyTo":"402c10cd0803172127u480276c9s4f9d716b4912ad5e@mail.gmail.com","subject":"Re: [RFC/PATCH Second draft] Fast forward strategies allow, never, and only","fromName":"Ping Yin","fromEmail":"pkufranky@gmail.com","sentAt":"2008-03-18T13:57:04Z","receivedAt":"2008-03-18T13:57:04Z","isPatch":true,"sender":{"key":"pkufranky@gmail.com","avatar":"https://avatars.githubusercontent.com/u/5346?v=4"},"body":"* Sverre Hvammen Johansen <hvammen@gmail.com> [2008-03-17 20:27:13 -0800]:\n\n> New fast forward strategies, only, is introduced.  This new fast\n> forward strategy prevents real merges.\n> \n> +                               fast_forward=allow ;;\n> +                       *)\n> +                               die \"Available fast-forward strategies\n> are: allow, newer, and only\" ;;\n\nI fail to apply this patch. It seems wrapped here. Do you use \"git send-email\"?\n\n> +                       esac\n> +                       ;;\n> +               --ff=*)\n> +                       fast_forward=${1#--ff=}\n> +                       case \"$fast_forward\" in\n> +                           allow|never|only)\n> +                               ;;\n> +                           *)\n> +                               die \"Available fast-forward strategies\n> are: allow, newer, and only\" ;;\n\nWrapped again.\n"},{"id":"72357","messageId":"47DFCDBC.2010400@freescale.com","threadId":"12628","inReplyTo":"402c10cd0803172127u480276c9s4f9d716b4912ad5e@mail.gmail.com","subject":"Re: [RFC/PATCH Second draft] Fast forward strategies allow, never, and only","fromName":"Jon Loeliger","fromEmail":"jdl@freescale.com","sentAt":"2008-03-18T14:12:12Z","receivedAt":"2008-03-18T14:12:12Z","isPatch":true,"sender":{"key":"jdl@jdl.com","avatar":"https://gravatar.com/avatar/75ce9a10b151acd2c28ec4ab2136dba7b2ff1634530bd04b155981a749d08a64?d=mp&s=160"},"body":"Sverre Hvammen Johansen wrote:\n\n> diff --git a/Documentation/fast-forward-options.txt\n> b/Documentation/fast-forward-options.txt\n> new file mode 100644\n> index 0000000..87fd0ae\n> --- /dev/null\n> +++ b/Documentation/fast-forward-options.txt\n> @@ -0,0 +1,69 @@\n> +FAST FORWARD OPTIONS\n> +--------------------\n>\n\n> +\n> +However, if your workflow require a linear history for the special\n> +branch (\"master\"), topic branches must be rebased before merging them\n> +back to \"master\".  A pull or a merge from the \"master branch of a\n> +topic branch may accidentally introduce a merge commit that was not\n> +already in the topic branch if the topic that were merged was not\n> +properly rebased.  This will creating a none linear history.\n\ns/none /non/\n\njdl\n"},{"id":"72359","messageId":"402c10cd0803180858v5d0e81bamfe86a1b4805eb631@mail.gmail.com","threadId":"12628","inReplyTo":"20080318135704.GA11922@kooxoo235","subject":"Re: [RFC/PATCH Second draft] Fast forward strategies allow, never, and only","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-03-18T15:58:29Z","receivedAt":"2008-03-18T15:58:29Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"On Tue, Mar 18, 2008 at 5:57 AM, Ping Yin <pkufranky@gmail.com> wrote:\n> * Sverre Hvammen Johansen <hvammen@gmail.com> [2008-03-17 20:27:13 -0800]:\n>\n>\n>  > New fast forward strategies, only, is introduced.  This new fast\n>  > forward strategy prevents real merges.\n>  >\n>\n> > +                               fast_forward=allow ;;\n>  > +                       *)\n>  > +                               die \"Available fast-forward strategies\n>  > are: allow, newer, and only\" ;;\n>\n>  I fail to apply this patch. It seems wrapped here. Do you use \"git send-email\"?\n\nI cut and pasted it into gmail.  I will resend the patch as an\nattachment together with a few other changes in about 14 hours.\n\n-- \nSverre Hvammen Johansen\n"},{"id":"72367","messageId":"m363vkvvzb.fsf@localhost.localdomain","threadId":"12628","inReplyTo":"402c10cd0803172127u480276c9s4f9d716b4912ad5e@mail.gmail.com","subject":"Re: [RFC/PATCH Second draft] Fast forward strategies allow, never, and only","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2008-03-18T16:27:20Z","receivedAt":"2008-03-18T16:27:20Z","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> New fast forward strategy \"only\" is introduced.  This new fast\n> forward strategy prevents real merges.\n\nI like this patch, and I think it can be useful.  You have added\nexplanation when each fast-forward option (strategy); very nice.\n\n> FF strategy \"only\" fails if the specified heads and HEAD can not\n> be reduced down to only one real parent.  The only allowed\n> outcome is a fast forward unless HEAD is up to date with\n> the specified heads.\n> \n> This patch also uses the real heads found instead of those\n> specified for real merges.  This means that merge startegies\n> that only take two heads can now accept more than two heads\n> if they can be reduced down to only two real heads.  However,\n> fast-forward of head in combination with a real merge is\n> handled as before.\n\nBut the difference between real heads and specified heads, and\nreduction to one parent is not explained in commit message, nor in\ndocumentation provided.\n\nI'm not sure if this functionality shouldn't be separated into\nanother, separate patch... unless of course this follows cleaning up\nthe code.\n\nI *GUESS* that if you do \"git merge a b\" and branches \"a\" and \"b\" both\npoint to the same commit, the merge (be it fast forward, forced merge,\nor ordinary merge) is like you have specified \"git merge a\"... perhaps\nwith the only difference (but it is not specified) in the summary of\ncommit message (first line of commit message).\n\n> See the documentation for further explanation of this feature.\n\nThanks.\n\nThe documentation is IMVHO in good enough state to be accepted into\ngit; it should be cleaned up a bit, and made better from the point of\nwriting style, but that I think can be done \"in tree\".  It is clean\nenough to use, ebven if it could be better...\n\n> diff --git a/Documentation/fast-forward-options.txt\n> b/Documentation/fast-forward-options.txt\n\nWord wrapped.\n\nIf you use mailer, please uncheck word wrap option. Best would be to\nuse git-send-email to send patches; on my private, single user Linux\nmachine I have configured sendmail to send emails through my GMail\naccount, I'm not sure if git-send-email support for SMTP sending is\nenough...  \n\nYou should avoid sending from web interface, as it usually doesn't\nhave option to turn off word wrapping; additionally copy'n'paste can\nturn tabs into spaces.  If you have to send patch from web interface,\nsend patch as attachement: \"text/plain\" (changing extension to *.txt\nshould help if web nterface tries to use \"application/octet-stream\"),\nand if possible as \"inline\" attachement.\n\n> new file mode 100644\n> index 0000000..87fd0ae\n> --- /dev/null\n> +++ b/Documentation/fast-forward-options.txt\n> @@ -0,0 +1,69 @@\n> +FAST FORWARD OPTIONS\n> +--------------------\n> +\n> +allow::\n> +       Do not generate a merge commit if the merge resolved as a\n> +       fast-forward, only update the branch pointer.  This is the\n> +       default behavior.  This option is equivalent of '--ff' without\n> +       any argument.\n> +\n\nPerhaps the sentence \"This is the default behavior\" should be at the\nend, but I think as it is now is also good.\n\n> +never::\n> +       Generate a merge commit even if the merge resolved as a\n> +       fast-forward.  This option is equivalent of '--no-ff'.\n> +\n> +only::\n> +       Only allow a fast-forward.  The merge will fail unless HEAD is\n> +       up to date or the merge resolved as a fast-forward.\n\nI think s/merge resolved/merge resolves/, but I'm not native English\nspeaker.\n\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\nThe above two paragraphs are good explanation why one would want to\nchose --ff=never, and when to use this fast-forward option.\n\nNevertheless the above description could be written better, and do not\nuse such long sentences.  Additionally perhaps the part about\n--ff=never (or --no-ff) could have its own subsection header.\n\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\nNice diagram.\n\n> +The first merge of topicA or the only merge of topicB would have\n> +resulted in a fast forward without '--ff=never'.  The last merge of\n> +topicA would have failed with '--ff=only'.  Topic A consist of those\n> +commits that can be reached from master^2 without passing through any\n> +of the first-parent ancestries of master.\n> +\n\nhere perhaps another subsection should be started, as the following\npart delas with when to use --ff=only option.\n\n> +However, if your workflow require a linear history for the special\n> +branch (\"master\"), topic branches must be rebased before merging them\n> +back to \"master\".  A pull or a merge from the \"master branch of a\n> +topic branch may accidentally introduce a merge commit that was not\n> +already in the topic branch if the topic that were merged was not\n> +properly rebased.  This will creating a none linear history.\n> +\n> +Using \"only fast forward\" policy ensures that whenever a pull or a\n> +merge is performed it will fail unless the merge can be resolved as a\n> +fast forward.  This will however not guarantee a linear history since\n> +the topic branches that are merged in may have merge commits recorded.\n> +You may therefor need to use this policy on the topic branches as\n> +well.\n> +\n\nI'd mention here receive.denyNonFastForward option as a way to set\nthis globally for all branches, for public bare publishing\nrepositories; AFAIK for push and I think also for fetch.\n\nI'm not sure if it is really needed.  As is is good enough.\n\n> +\"Only fast forward\" policy on the \"master\" branch can be enforced by\n> +setting the mergeoptions for that branch using git config:\n> +\n> +------------\n> +% git config branch.master.mergeoptions --ff=only\n> +------------\n\nIsn't the above section generic, i.e. you can use above tip both to\nforce --ff=only (only fast forward for linear history), or --ff=never\n(always mark merge/end of topic branch by merge commit)?\n\n\n> diff --git a/t/t7601-merge-ff-options.sh b/t/t7601-merge-ff-options.sh\n\nVery good!\n\n-- \nJakub Narebski\nPoland\nShadeHawk on #git\n"},{"id":"72391","messageId":"402c10cd0803182320k134116cas5f62389482f2650a@mail.gmail.com","threadId":"12628","inReplyTo":"m363vkvvzb.fsf@localhost.localdomain","subject":"Re: [RFC/PATCH Second draft] Fast forward strategies allow, never, and only","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-03-19T06:20:56Z","receivedAt":"2008-03-19T06:20:56Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"Thanks to you and everyone else for useful input.  Some of this input\nended up in the documentation.  I will provide an updated patch\ntomorrow.  I expect to have a final patch for submission some time\nnext week.\n\nI have added some more documentation:\n\n\ndiff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt\nindex 2af33d8..5af5436 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 always including `HEAD`.  The real\n+parents (only including `HEAD` if it is real) are the parents recorded\n+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 co 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.\n\nOn Tue, Mar 18, 2008 at 8:27 AM, Jakub Narebski <jnareb@gmail.com> wrote:\n>  I'd mention here receive.denyNonFastForward option as a way to set\n>  this globally for all branches, for public bare publishing\n>  repositories; AFAIK for push and I think also for fetch.\n\nThe denyNonFastForward option have nothing to do with merges.  It only\napplies to push on the server side.  A merge is not involved when\ndoing a push.\n\n-- \nSverre Hvammen Johansen\n"},{"id":"72400","messageId":"7vskym310l.fsf@gitster.siamese.dyndns.org","threadId":"12628","inReplyTo":"402c10cd0803172127u480276c9s4f9d716b4912ad5e@mail.gmail.com","subject":"Re: [RFC/PATCH Second draft] Fast forward strategies allow, never, and only","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-03-19T20:35:38Z","receivedAt":"2008-03-19T20:35:38Z","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> New fast forward strategies, only, is introduced.  This new fast\n> forward strategy prevents real merges.\n>\n> FF strategy \"only\" fails if the specified heads and HEAD can not\n> be reduced down to only one real parent.  The only allowed\n> outcome is a fast forward unless HEAD is up to date with\n> the specified heads.\n>\n> This patch also uses the real heads found instead of those\n> specified for real merges.  This means that merge startegies\n> that only take two heads can now accept more than two heads\n> if they can be reduced down to only two real heads.  However,\n> fast-forward of head in combination with a real merge is\n> handled as before.\n\nThis might be easier to review if split into two parts.  Code suffling to\ndo --ff/--no-ff => ff={allow,never} and documentation updates to improve\nthe description of these two options in the first patch, and addition of\n\"only\" to code and the updated docuemntation in the second.\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'.  The last merge of\n> +topicA would have failed with '--ff=only'.  Topic A consist of those\n> +commits that can be reached from master^2 without passing through any\n> +of the first-parent ancestries of master.\n\nIf you remove one sentence \"The last merge ... =only'.\" from it, this part\nis a very good write-up on how \"never fast forward\" could be useful, and\nmight even be a worthy addition to the current documentation.  However it\nlacks a crucial bit of information: it is _not enough_ to just use --no-ff\nto maintain the \"special status\" of \"master\".  You also need to prevent\ndirect committing to it.  So while --no-ff is an ingredient you can\nconstruct such a workflow out of, it by itself is not the whole solution.\nYou need additional discipline.\n\n> +However, if your workflow require a linear history for the special\n> +branch (\"master\"), topic branches must be rebased before merging them\n> +back to \"master\".  A pull or a merge from the \"master branch of a\n> +topic branch may accidentally introduce a merge commit that was not\n> +already in the topic branch if the topic that were merged was not\n> +properly rebased.  This will creating a none linear history.\n\nThis,...\n\n> +Using \"only fast forward\" policy ensures that whenever a pull or a\n> +merge is performed it will fail unless the merge can be resolved as a\n> +fast forward.  This will however not guarantee a linear history since\n> +the topic branches that are merged in may have merge commits recorded.\n>\n> +You may therefor need to use this policy on the topic branches as\n> +well.\n\ncombined with the above, would make \"only\" an incomplete implementation of\nthe goal you stated earlier, i.e. \"to force a completely linear history\",\nbut I think you can trivially fix this by making sure that there is no\nmerge commit in ORIG_HEAD..MERGE_HEAD and refusing if you find one.  And\nby fixing the implementation, you do not have to make excuses like the\nabove two and half paragraphs.\n\nHaving said that, I doubt what you are trying to achieve is to force a\ntotally linear history, as that is quite useless policy unless you are\ntalking about a one-man project.\n\nWhen your project has gained meaningful number of developers, there will\nalways be more than one nontrivial changes outstanding.  Forcing a linear\nhistory everywhere means whenever somebody wants his changes accepted, he\nneeds to fetch + rebase whenever anybody else's change is accepted to the\nspecial \"master\".  The more developers you have, the narrower window\nbetween acceptance of changes to the \"master\" becomes, and eventually the\nwindow will become shorter than the time needed to rebase and retest any\nnontrivial change on top of the tip of \"master\".  At that point, nobody\ncan get anything but a trivially rebasable and untested series accepted.\nSuch a policy would encourage people to merge only \"early half\" of their\nseries (because they do not have enough time to rebase the full series) in\na poorly tested shape.  So in that sense it is not just \"practicaly\nuseless\", but can be actively harmful by encouraging a bad workflow.\n\nBut your \"merge has to fast-forward, but the merged branch can have\nnonlinear history itself\" semantics is different from \"require linear\nhistory\".  For example, if you want the top-level integrator to do\nabsolutely _nothing_ (not even a trivial merge), in order to shift the\nburden of integration testing to contributors, then use of such semantics\n(which is _different_ from \"completely linear\") by the top-level\nintegrator could achieve that.  Nobody can ask the top-level to pull which\nwould result in a tree that he built and tested himself.  The top-level\nintegrator would say \"Your tree is not a fast-forward from mine, because I\nmerged with somebody else between the time you prepared your tree and I\nlearned about it.  Please try again\", and in response, the contributor can\npull from the top-level integrator and re-test the merge result and ask\nagain.  With luck, the top-level integrator may not have merged with\nanybody else and a pull from that contributor would fast-forward.\n\nSo if that is what you are trying to achieve, you need to update your\ndescription.  If you aim for \"Totally linear\", I think many people will\nfind it is practically useless, but if you are aiming for something\ndifferent, you should advertise it as such.\n\nI think the scaling issue (iow \"narrowing window\") is the same either way,\nand the documentaion should warn about it to the users.  Unclueful people\nmay think, without really thinking ;-), that it is a good thing in any\noccassions to require totally linear history, unless downsides are also\nexplained.\n\n> diff --git a/git-merge.sh b/git-merge.sh\n> index 7dbbb1d..a98cd77 100755\n> --- a/git-merge.sh\n> +++ b/git-merge.sh\n> @@ -12,7 +12,7 @@ summary              show a diffstat at the end of the merge\n>  n,no-summary         don't show a diffstat at the end of the merge\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> @@ -35,7 +35,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=\n>\n> @@ -153,8 +153,6 @@ parse_config () {\n>                 --summary)\n>                         show_diffstat=t ;;\n>                 --squash)\n> -                       test \"$allow_fast_forward\" = t ||\n> -                               die \"You cannot combine --squash with --no-ff.\"\n\nI do not think you defended why it is good idea to drop this sanity check.\n\n>                         squash=t no_commit=t ;;\n>                 --no-squash)\n>                         squash= no_commit= ;;\n> @@ -163,18 +161,33 @@ parse_config () {\n>                 --no-commit)\n>                         no_commit=t ;;\n>                 --ff)\n> -                       allow_fast_forward=t ;;\n> +                       case \"$2\" in\n> +                       allow|never|only)\n> +                               fast_forward=$2; shift ;;\n> +                       -*)\n> +                               fast_forward=allow ;;\n> +                       *)\n> +                               die \"Available fast-forward strategies\n> are: allow, newer, and only\" ;;\n\nStrategies?  \"fast forward options\" you used in the option description is\na reasonable one, and it would be better to keep it consistent.\n\nI didn't look at the rest of the patch (yet).\n"},{"id":"72413","messageId":"200803192220.59035.jnareb@gmail.com","threadId":"12628","inReplyTo":"402c10cd0803182320k134116cas5f62389482f2650a@mail.gmail.com","subject":"Re: [RFC/PATCH Second draft] Fast forward strategies allow, never, and only","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2008-03-19T21:20:58Z","receivedAt":"2008-03-19T21:20:58Z","isPatch":true,"sender":{"key":"jnareb@gmail.com","avatar":"https://avatars.githubusercontent.com/u/2706?v=4"},"body":"On Wed, 19 Mar 2008, Sverre Hvammen Johansen wrote:\n\n> Thanks to you and everyone else for useful input.  Some of this input\n> ended up in the documentation.  I will provide an updated patch\n> tomorrow.  I expect to have a final patch for submission some time\n> next week.\n> \n> I have added some more documentation:\n \nThis updated documentation made it clear that this commit introduces\ntwo (or even three accoridng to Junio) independent features, which in\nmy opinion should be split into separate patch. Having separate commits\nfor separate features helps reduce commit size and should help\nreadibility of patch, thus making review process easier. It also makes\nbisecting easier.\n\nFirst feature is introducing fast forward options (I'd rather not call\nthem \"strategies\") 'allow', 'never' and new option 'only'. This is\nindependent on the head reduction feature; actually the second feature\nwas sent as separate patch in first draft, I wonder why you have\ndecided to join them.\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 always including `HEAD`.  The real\n> +parents (only including `HEAD` if it is real) are the parents recorded\n> +in the merge commit object.\n\nIMHO this should be (besides having this as separate commit) optional.\nI'm not sure if always heads reduction is always desirable.\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\nI'd provide first simpler example without 'TopicC'.\n\nIf I understand correctly you have implemented here always using\n\"parent\" (or \"dependent\") reduction of merge heads. IMHO this reduction\ncontradict stated idea of using --ff=never (--no-ff) to always mark\nwhere topic branch has ended.\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 co 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\nThis... is a bit unexpected. I thought that there should be line where\nI have added dotted line.\n\nThe example above mixes fast-forward options with automatic reduction\nof heads in a merge.\n\nI'd really prefer if you would resurrect merge head reduction options\n(strategies?) as it was, i.e. as separate patch. And of course talk\nabout reducing heads, not fast-forward options/strategies... this issue\nis IMVHO orthogonal to options for allowing/forcing/denying fast-forward.\n\n> On Tue, Mar 18, 2008 at 8:27 AM, Jakub Narebski <jnareb@gmail.com> wrote:\n>>\n>>  I'd mention here receive.denyNonFastForward option as a way to set\n>>  this globally for all branches, for public bare publishing\n>>  repositories; AFAIK for push and I think also for fetch.\n> \n> The denyNonFastForward option have nothing to do with merges.  It only\n> applies to push on the server side.  A merge is not involved when\n> doing a push.\n\nFact. My mistake. The result might be similar (linear history), but the\nissue is different.\n\n\nP.S. I think git is now in feature freeze... which is good time for\nsending patches for discussion, not good for sending patches to be\naccepted.\n\n-- \nJakub Narebski\nPoland\n"},{"id":"72480","messageId":"402c10cd0803192144n716cf66dgd4d660f4917a80fc@mail.gmail.com","threadId":"12628","inReplyTo":"200803192220.59035.jnareb@gmail.com","subject":"Re: [RFC/PATCH Second draft] Fast forward strategies allow, never, and only","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-03-20T04:44:27Z","receivedAt":"2008-03-20T04:44:27Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"On Wed, Mar 19, 2008 at 1:20 PM, Jakub Narebski <jnareb@gmail.com> wrote:\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>  I'd provide first simpler example without 'TopicC'.\n\nIf we are to explain how this is recorded this is how simple we can\nmake it without leaving anything out.  However, I am not sure we\nshould have this in the documentation at all.  Most users probably\ndon't care exactly how this is recorded as long as the history down\nthe road is not to complicated.\n\n>  If I understand correctly you have implemented here always using\n>  \"parent\" (or \"dependent\") reduction of merge heads. IMHO this reduction\n>  contradict stated idea of using --ff=never (--no-ff) to always mark\n>  where topic branch has ended.\n\nWhen using --ff=never this reduction will not be done and that is also\nhow current git works (except that you need to say --no-ff).\n\n>  > +         % git co 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>  This... is a bit unexpected. I thought that there should be line where\n>  I have added dotted line.\n\nThe graph also describes how current git record this.  Try the example\nand see for yourself.  The main difference between the patch and\ncurrent git is that the patch is trying to be smarter about how it\nselects the merge algorithm, and which commits are passed on to the\nreal merge algorithm.  In the example above it makes no difference\nsince octopus is used and whether octopus is getting three or four\nbranches does not matter at all since the octopus merge is able to do\nthis reduction internally.  But in the case where we end up with two\nbranches it makes a huge difference since we then can use the more\nsmarter merge algorithms, and more cases will be merged automatically.\n However, all this does not make much of a difference in most cases.\nGit rocks whether we decide to do this or not.\n\n>  I'd really prefer if you would resurrect merge head reduction options\n>  (strategies?) as it was, i.e. as separate patch. And of course talk\n>  about reducing heads, not fast-forward options/strategies... this issue\n>  is IMVHO orthogonal to options for allowing/forcing/denying fast-forward.\n\nThe first patch had the same features, was implemented slightly\ndifferently, but lacked a lot of documentation.\n\n-- \nSverre Hvammen Johansen\n"},{"id":"72490","messageId":"402c10cd0803192347q7b4a3fb0s35737f361d53a86a@mail.gmail.com","threadId":"12628","inReplyTo":"7vskym310l.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFC/PATCH Second draft] Fast forward strategies allow, never, and only","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-03-20T06:47:01Z","receivedAt":"2008-03-20T06:47:01Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"On Wed, Mar 19, 2008 at 12:35 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> > ...\n>  This might be easier to review if split into two parts.  Code suffling to\n>  do --ff/--no-ff => ff={allow,never} and documentation updates to improve\n>  the description of these two options in the first patch, and addition of\n>  \"only\" to code and the updated docuemntation in the second.\n\nWhat I would like to do is to split it in three like this:\n\n1. Head reduction\n\n2. --ff/--no-ff => ff={allow,never} and documentation updates.\n\n3. --ff=only\n\nIf you would like me to do this please tell me.\n\n> ...\n>  might even be a worthy addition to the current documentation.  However it\n>  lacks a crucial bit of information: it is _not enough_ to just use --no-ff\n>  to maintain the \"special status\" of \"master\".  You also need to prevent\n>  direct committing to it.\n\nTrue, but the special case where you have a topic that only consists\nof one commit you might as well apply it directly on master.  In any\ncase, when you commit something directly on the special branch master\nyou usually know what you are doing. It is perfectly OK to combine the\ntwo.  I am not sure we need to explain this.\n\n>  > +You may therefor need to use this policy on the topic branches as\n>  > +well.\n>\n>  combined with the above, would make \"only\" an incomplete implementation of\n>  the goal you stated earlier, i.e. \"to force a completely linear history\",\n\nActually that is not my goal for this implementation, I just tried to\ndescribe a useful use case, but failed.  Let me try again.\n\nI actually need this for the integration between accurev and git I am\nusing/maintaining/developing (at some point I intend to release it).\nAt work I am forced to use accurev, but the user interface for accurev\nis horrible and it is slow.  I therefor have complete history of\naccurev streams in git and are doing all my work in git with branches\nand everything.  The git-accurev integrator creates one merge commit\nobject in git for each time i check something into accurev, .   This\nmerge commit object ties the content in accurev that was committed\ninto accurev with the corresponding content in git.  It is important\nthat further work I do is based on this special merge commit object.\nIt works if I don't, but  the history gets really messy, and for this\nI need the --ff=only so I don't forget to pull or rebase before the\nnext commit I make into accurev.\n\n>  but I think you can trivially fix this by making sure that there is no\n>  merge commit in ORIG_HEAD..MERGE_HEAD and refusing if you find one.  And\n>  by fixing the implementation, you do not have to make excuses like the\n>  above two and half paragraphs.\n\nI don't intend to do that, simply because I don't need it and it would\nactually not work for my workflow.\n\n>  So if that is what you are trying to achieve, you need to update your\n>  description.  If you aim for \"Totally linear\", I think many people will\n>  find it is practically useless, but if you are aiming for something\n>  different, you should advertise it as such.\n\nYou are right, I will try to come up with something better.\n\n>  > @@ -153,8 +153,6 @@ parse_config () {\n>  >                 --summary)\n>  >                         show_diffstat=t ;;\n>  >                 --squash)\n>  > -                       test \"$allow_fast_forward\" = t ||\n>  > -                               die \"You cannot combine --squash with --no-ff.\"\n>\n>  I do not think you defended why it is good idea to drop this sanity check.\n\nI don't see any good idea for having this check.  Nothing bad happens\nby allowing to combine these options the way I currently implement it.\n\n-- \nSverre Hvammen Johansen\n"},{"id":"72697","messageId":"7vbq56ilnj.fsf@gitster.siamese.dyndns.org","threadId":"12628","inReplyTo":"402c10cd0803192347q7b4a3fb0s35737f361d53a86a@mail.gmail.com","subject":"Re: [RFC/PATCH Second draft] Fast forward strategies allow, never, and only","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-03-22T19:49:52Z","receivedAt":"2008-03-22T19:49:52Z","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> On Wed, Mar 19, 2008 at 12:35 PM, Junio C Hamano <gitster@pobox.com> wrote:\n>> > ...\n>>  This might be easier to review if split into two parts.  Code suffling to\n>>  do --ff/--no-ff => ff={allow,never} and documentation updates to improve\n>>  the description of these two options in the first patch, and addition of\n>>  \"only\" to code and the updated docuemntation in the second.\n>\n> What I would like to do is to split it in three like this:\n>\n> 1. Head reduction\n>\n> 2. --ff/--no-ff => ff={allow,never} and documentation updates.\n>\n> 3. --ff=only\n>\n> If you would like me to do this please tell me.\n\nYeah, making head reduction into its own separate patch would make things\nclearer, I guess.\n\nBut if you are going to do that, then the order should be 2/1/3 from the\nabove list.  In a series of patches, restructuring without changing\nsemantics should come first to make existing logic cleaner and later\nenhancements on top of it easier to follow.  Then you build new features\nand enhancements on top of that solidified base.\n\nBecause \"head reduction\" changes the semantics (making it better or worse\ndoes not matter --- \"changes\" is what matters), it should come after #2\nabove, I think.\n"},{"id":"73104","messageId":"402c10cd0803252050u582111cag18674e0257ac2884@mail.gmail.com","threadId":"12628","inReplyTo":"7vbq56ilnj.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFC/PATCH Second draft] Fast forward strategies allow, never, and only","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-03-26T03:50:41Z","receivedAt":"2008-03-26T03:50:41Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"I have attached four patches to this email (to avoid line-wrapping\nissues if someone needs them for testing).  They are also posted\ninlined for comments.\n\nOn Sat, Mar 22, 2008 at 11:49 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>  In a series of patches, restructuring without changing\n>  semantics should come first to make existing logic cleaner and later\n>  enhancements on top of it easier to follow.\n\nThe patch series consists of the following four patches:\n\n   0001-Introduce-ff-fast-forward-option.patch\n   0002-Restructuring-git-merge.sh.patch\n   0003-Head-reduction-before-selecting-merge-strategy.patch\n   0004-Introduce-fast-forward-option-only.patch\n\nThe first and the last one is trivial.  Head reduction is more\ncomplicated.   I have split this one in two as you suggested except\nthat the one for restructuring does not come as the first on in this\nseries of patches.\n\n-- \nSverre Hvammen Johansen\n\n\nFrom 89fbd87b93017d8a65afd6fd27796c0e8f204c22 Mon Sep 17 00:00:00 2001\nFrom: Sverre Hvammen Johansen <hvammen@gmail.com>\nDate: Sun, 23 Mar 2008 19:15:52 -0800\nSubject: [PATCH 1/4] 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            |  639 ++++++++++++++++++++++++++++++++\n 7 files changed, 728 insertions(+), 23 deletions(-)\n create mode 100644 Documentation/fast-forward-options.txt\n create mode 100755 t/t7601-merge-ff-options.sh\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 c136b10..2af33d8 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] [--summary] [--no-commit] [--squash] [-s <strategy>]...\n+'git-merge' [-n] [--summary] [--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 3405ca0..e4e013c 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 9f1fc82..cf4881b 100644\n--- a/Documentation/merge-options.txt\n+++ b/Documentation/merge-options.txt\n@@ -29,12 +29,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 7dbbb1d..17f40f2 100755\n--- a/git-merge.sh\n+++ b/git-merge.sh\n@@ -12,7 +12,7 @@ summary              show a diffstat at the end of the merge\n n,no-summary         don't show a diffstat at the end of the merge\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@@ -35,7 +35,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=\n \n@@ -153,8 +153,6 @@ parse_config () {\n \t\t--summary)\n \t\t\tshow_diffstat=t ;;\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@@ -163,11 +161,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@@ -189,6 +202,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@@ -308,7 +323,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@@ -334,17 +349,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@@ -359,11 +374,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@@ -481,7 +496,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 3ce32b5..2d7293a 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-summary] [--[no-]commit] [--[no-]squash] [--[no-]ff] [-s strategy]... [<fetch-options>] <repo> <head>...'\n+USAGE='[-n | --no-summary] [--[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\nnew file mode 100755\nindex 0000000..636e71e\n--- /dev/null\n+++ b/t/t7601-merge-ff-options.sh\n@@ -0,0 +1,639 @@\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+\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 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+\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.3.3\n\n\n\nFrom 55d0664258c1053309514b192effd33d1db4c7a0 Mon Sep 17 00:00:00 2001\nFrom: Sverre Hvammen Johansen <hvammen@gmail.com>\nDate: Sun, 23 Mar 2008 23:19:37 -0800\nSubject: [PATCH 2/4] Restructuring git-merge.sh\n\nfor preparation of new feature:\n\n   Head reduction before selecting merge strategy\n\nSigned-off-by: Sverre Hvammen Johansen <hvammen@gmail.com>\n---\n git-merge.sh |  166 ++++++++++++++++++++++++++++++----------------------------\n 1 files changed, 85 insertions(+), 81 deletions(-)\n\ndiff --git a/git-merge.sh b/git-merge.sh\nindex 17f40f2..2acd2cc 100755\n--- a/git-merge.sh\n+++ b/git-merge.sh\n@@ -207,6 +207,29 @@ parse_config () {\n \targs_left=$#\n }\n \n+# Find real parents\n+# Set the following variables as followd:\n+#   real_parents: The parents specified on the command line\n+#   common:       All common ancestors or not_queried\n+#   ff_head:      Fast forward of head\n+find_real_parents () {\n+\treal_parents=$(git rev-parse \"$@\")\n+\treal_parents=${real_parents#$LF}\n+\tif test $# = 1\n+\tthen\n+\t\tcommon=$(git merge-base --all $head \"$@\")\n+\t\tif test \"$common\" = $head\n+\t\tthen\n+\t\t\tff_head=$1\n+\t\telse\n+\t\t\tff_head=$head\n+\t\tfi\n+\telse\n+\t\tcommon=not_queried\n+\t\tff_head=$head\n+\tfi\n+}\n+\n test $# != 0 || usage\n \n have_message=\n@@ -294,24 +317,26 @@ do\n done\n set x $remoteheads ; shift\n \n+find_real_parents \"$@\"\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?*\"$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@@ -339,87 +364,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 true\n+then\n+\tif test $head = $ff_head -a \"$common\" = \"$real_parents\"\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 -a $ff_head = \"$real_parents\"\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 \"$real_parents\" in\n+?*\"$LF\"?*)\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+\ttest \"$common\" != not_queried || common=$(git merge-base --all $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@@ -460,7 +464,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@@ -530,7 +534,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 \n-- \n1.5.3.3\n\n\n\nFrom 2227b803ecfd47b2d5586ec923cb887f017f3b67 Mon Sep 17 00:00:00 2001\nFrom: Sverre Hvammen Johansen <hvammen@gmail.com>\nDate: Sun, 23 Mar 2008 23:23:52 -0800\nSubject: [PATCH 3/4] Head reduction before selecting merge strategy\n\nSee 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 \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@@ -133,6 +133,47 @@ merge (which is typically a fraction of the whole 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.  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-\treal_parents=$(git rev-parse \"$@\")\n-\treal_parents=${real_parents#$LF}\n-\tif test $# = 1\n+\tif test $fast_forward = never\n \tthen\n-\t\tcommon=$(git merge-base --all $head \"$@\")\n-\t\tif test \"$common\" = $head\n+\t\treal_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\tff_head=$1\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\treal_parents=\n+\t\t\t\tff_head=$1\n+\t\t\telif test \"$common\" = \"$1\"\n+\t\t\tthen\n+\t\t\t\treal_parents=\n+\t\t\t\tff_head=$head\n+\t\t\telse\n+\t\t\t\treal_parents=$1\n+\t\t\t\tff_head=$head\n+\t\t\t    \n+\t\t\tfi\n \t\telse\n-\t\t\tff_head=$head\n+\t\t\treal_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=${real_parents%%$LF*}\n+\t\t\treal_parents=${real_parents#$ff_head}\n+\t\t\treal_parents=${real_parents#$LF}\n+\t\t\tcommon=not_queried\n \t\tfi\n-\telse\n-\t\tcommon=not_queried\n-\t\tff_head=$head\n \tfi\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+\ttest $head = $ff_head ||\n+\t\treal_parents=\"$ff_head$LF$real_parents\"\n+fi\n+\n case \"$use_strategies\" in\n '')\n \tcase \"$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-\tif test $head = $ff_head -a \"$common\" = \"$real_parents\"\n+\tif test $head = $ff_head\n \tthen\n \t\tfinish_up_to_date \"Already up-to-date.\"\n \t\texit 0\n-\telif test $fast_forward != never -a $ff_head = \"$real_parents\"\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@@ -386,6 +409,14 @@ then\n \t\tfinish \"$new_head\" \"$msg\" || exit\n \t\tdropsave\n \t\texit 0\n+\telse\n+\t\treal_parents=\"$ff_head\"\n+\t\tff_head=$head\n+\tfi\n+else\n+\tif test $head != $ff_head -a $fast_forward = never\n+\tthen\n+\t\treal_parents=\"$ff_head$LF$real_parents\"\n \tfi\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 $result_tree $parents) || exit\n-    finish \"$result_commit\" \"Merge made by $wt_strategy.\"\n-    dropsave\n-    exit 0\n+\ttest $head = $ff_head && real_parents=\"$head$LF$real_parents\"\n+\tparents=$(echo \"$real_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-- \n1.5.3.3\n\n\n\nFrom df159d4275d25a57898b757489f3d675e715efa3 Mon Sep 17 00:00:00 2001\nFrom: Sverre Hvammen Johansen <hvammen@gmail.com>\nDate: Sun, 23 Mar 2008 19:02:39 -0800\nSubject: [PATCH 4/4] Introduce fast forward option only\n\nThis feature is needed for git integration with accurev.\nSee the documentation for an explanation of this feature.\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 5398606..b6c428f 100755\n--- a/git-merge.sh\n+++ b/git-merge.sh\n@@ -162,21 +162,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@@ -203,7 +203,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@@ -338,6 +338,8 @@ find_real_parents \"$@\"\n \n if test -n \"$real_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\treal_parents=\"$ff_head$LF$real_parents\"\n fi\ndiff --git a/git-pull.sh b/git-pull.sh\nindex 2d7293a..5bc84a6 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 636e71e..ca4cc67 100755\n--- a/t/t7601-merge-ff-options.sh\n+++ b/t/t7601-merge-ff-options.sh\n@@ -636,4 +636,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.3.3\n\n"},{"id":"73403","messageId":"402c10cd0803302119r251b3a43te69ce2a52e121ba5@mail.gmail.com","threadId":"12628","inReplyTo":"402c10cd0803252050u582111cag18674e0257ac2884@mail.gmail.com","subject":"Re: [RFC/PATCH Second draft] Fast forward strategies allow, never, and only","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-03-31T04:19:29Z","receivedAt":"2008-03-31T04:19:29Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"I have attached a new set of patches to this email (to avoid\nline-wrapping issues if someone needs them for testing).  They are\nalso posted inlined for comments.\n\nThe patch series consists of the following five patches:\n\n   0001-New-merge-tests.patch\n   0002-Introduce-ff-fast-forward-option.patch\n   0003-Restructure-git-merge.sh.patch\n   0004-Head-reduction-before-selecting-merge-strategy.patch\n   0005-Introduce-fast-forward-option-only.patch\n\nThe first patch add some tests.  The second, fourth, and fifth adds\nnew features and they are all trivial.  I was able to make the fourth\npatch trivial as well by actually doing the real work of finding the\nreduced parents in the third patch.  The third patch computes the\nreduced parents but uses it only to determine whether we are\nup-to-date or do a fast forward.\n\nThere are probably some minor adjustments to the documentation we\nshould do.  The third patch doesn't do much but it is still  a pretty\nbig step for code change for git-merge.sh.  I would therefor like more\neyes to look at this.  More tests is also needed.  I would like the\nfollowing tests to be added:\n\n   -  A test where a recursive merge is required for the merge\n      to succeed, where an octopus would fail.\n\n   - A test where a recursive merge gets more than one commit as the\n     merge base.  A case where \"git merge-base --all\" returns at least\ntwo commits.\n\nIs there anyone that can give me some tips for such tests?  What is\nthe simplest case where a recursive merge is required?.  How can I\nconstruct a history where we have more than one common ancestor for\ntwo commit objects?\n\nI have looked at the current tests and there does not seem to be\nanything where \"git merge-base --all\" return more than one ancestor.\n\n-- \nSverre Hvammen Johansen\n\n\nFrom 40068ea878fc0561e44e31d002bd0870ff1ac5fa Mon Sep 17 00:00:00 2001\nFrom: Sverre Hvammen Johansen <hvammen@gmail.com>\nDate: Sat, 29 Mar 2008 18:37:51 -0800\nSubject: [PATCH 1/5] 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\nSigned-off-by: Sverre Hvammen Johansen <hvammen@gmail.com>\n---\n t/t7601-merge-ff-options.sh |  461 +++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 461 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..408122e\n--- /dev/null\n+++ b/t/t7601-merge-ff-options.sh\n@@ -0,0 +1,461 @@\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+\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.3.3\n\n\n\nFrom 0c8fc6cd4bb097746ecdbc96cecfa774cc5e478c Mon Sep 17 00:00:00 2001\nFrom: Sverre Hvammen Johansen <hvammen@gmail.com>\nDate: Sat, 29 Mar 2008 17:27:28 -0800\nSubject: [PATCH 2/5] 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 c136b10..2af33d8 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] [--summary] [--no-commit] [--squash] [-s <strategy>]...\n+'git-merge' [-n] [--summary] [--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 3405ca0..e4e013c 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 9f1fc82..cf4881b 100644\n--- a/Documentation/merge-options.txt\n+++ b/Documentation/merge-options.txt\n@@ -29,12 +29,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 7dbbb1d..17f40f2 100755\n--- a/git-merge.sh\n+++ b/git-merge.sh\n@@ -12,7 +12,7 @@ summary              show a diffstat at the end of the merge\n n,no-summary         don't show a diffstat at the end of the merge\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@@ -35,7 +35,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=\n \n@@ -153,8 +153,6 @@ parse_config () {\n \t\t--summary)\n \t\t\tshow_diffstat=t ;;\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@@ -163,11 +161,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@@ -189,6 +202,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@@ -308,7 +323,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@@ -334,17 +349,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@@ -359,11 +374,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@@ -481,7 +496,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 3ce32b5..2d7293a 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-summary] [--[no-]commit] [--[no-]squash] [--[no-]ff] [-s strategy]... [<fetch-options>] <repo> <head>...'\n+USAGE='[-n | --no-summary] [--[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 408122e..2749f4f 100755\n--- a/t/t7601-merge-ff-options.sh\n+++ b/t/t7601-merge-ff-options.sh\n@@ -458,4 +458,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.3.3\n\n\n\nFrom f20dcfce6dd6af176f4f8b91822bc0a32ddc563e Mon Sep 17 00:00:00 2001\nFrom: Sverre Hvammen Johansen <hvammen@gmail.com>\nDate: Sun, 30 Mar 2008 12:55:23 -0800\nSubject: [PATCH 3/5] 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 17f40f2..7c34b6c 100755\n--- a/git-merge.sh\n+++ b/git-merge.sh\n@@ -207,6 +207,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@@ -294,24 +335,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@@ -339,87 +384,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@@ -460,7 +484,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@@ -530,7 +554,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.3.3\n\n\n\nFrom 179c59ec8c06e3dbee251fa510267b6f2eb52b6e Mon Sep 17 00:00:00 2001\nFrom: Sverre Hvammen Johansen <hvammen@gmail.com>\nDate: Sun, 30 Mar 2008 00:01:33 -0800\nSubject: [PATCH 4/5] Head reduction before selecting merge strategy\n\nSee 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                |   50 ++++++++++++++++++++++++-------------------\n 2 files changed, 70 insertions(+), 23 deletions(-)\n\ndiff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt\nindex 2af33d8..f6bc96f 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@@ -133,6 +133,47 @@ merge (which is typically a fraction of the whole 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 on the command line, git will\n+try to reduce the number of commits used (reduced parents) by\n+eliminating commits than can be reached from other commits.  The\n+commit message will reflect the commits specified on the command line\n+but the merge strategy will be selected based on the reduced parents\n+including `HEAD`.  The reduced parents are the parents recorded in the\n+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 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+Merging topicA, B and C to the master branch will select the merge\n+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 reduced 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 7c34b6c..7c70c56 100755\n--- a/git-merge.sh\n+++ b/git-merge.sh\n@@ -337,11 +337,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@@ -406,17 +411,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@@ -429,13 +440,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@@ -484,7 +495,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@@ -520,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 $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@@ -554,7 +560,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.3.3\n\n\n\nFrom bd97c8c32bbac389eba5bfbb25b4d0219bd3dd04 Mon Sep 17 00:00:00 2001\nFrom: Sverre Hvammen Johansen <hvammen@gmail.com>\nDate: Sat, 29 Mar 2008 23:01:30 -0800\nSubject: [PATCH 5/5] Introduce fast forward option only\n\nThis feature is needed for git integration with accurev.\nSee the documentation for an explanation of this feature.\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 7c70c56..68f627d 100755\n--- a/git-merge.sh\n+++ b/git-merge.sh\n@@ -162,21 +162,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@@ -203,7 +203,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@@ -340,6 +340,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 2d7293a..5bc84a6 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 2749f4f..ea36549 100755\n--- a/t/t7601-merge-ff-options.sh\n+++ b/t/t7601-merge-ff-options.sh\n@@ -646,4 +646,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.3.3\n\n"},{"id":"74790","messageId":"402c10cd0804191806h5460eb82y2442517343734b8e@mail.gmail.com","threadId":"12628","inReplyTo":"402c10cd0803302119r251b3a43te69ce2a52e121ba5@mail.gmail.com","subject":"[PATCH] Fast forward strategies allow, never, and only","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-04-20T01:06:00Z","receivedAt":"2008-04-20T01:06:00Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"On Sun, Mar 30, 2008 at 9:19 PM, Sverre Hvammen Johansen\n<hvammen@gmail.com> wrote:\n> I have attached a new set of patches to this email (to avoid\n>\n> line-wrapping issues if someone needs them for testing).  They are\n>  also posted inlined for comments.\n>\n>  The patch series consists of the following five patches:\n>\n>    0001-New-merge-tests.patch\n>    0002-Introduce-ff-fast-forward-option.patch\n>    0003-Restructure-git-merge.sh.patch\n>    0004-Head-reduction-before-selecting-merge-strategy.patch\n>    0005-Introduce-fast-forward-option-only.patch\n\nI consider myself finished with this and as far as I am concerned it\ncan be applied to git.git.  I am currently using this on top of 1.5.5\nin production and assume it to be good.  All tests passes.\n\n-- \nSverre Hvammen Johansen\n"},{"id":"74922","messageId":"7vhcdu2uu5.fsf@gitster.siamese.dyndns.org","threadId":"12628","inReplyTo":"402c10cd0804191806h5460eb82y2442517343734b8e@mail.gmail.com","subject":"Re: [PATCH] Fast forward strategies allow, never, and only","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-04-22T07:48:50Z","receivedAt":"2008-04-22T07:48:50Z","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> On Sun, Mar 30, 2008 at 9:19 PM, Sverre Hvammen Johansen\n> <hvammen@gmail.com> wrote:\n>> I have attached a new set of patches to this email (to avoid\n>>\n>> line-wrapping issues if someone needs them for testing).  They are\n>>  also posted inlined for comments.\n>>\n>>  The patch series consists of the following five patches:\n>>\n>>    0001-New-merge-tests.patch\n>>    0002-Introduce-ff-fast-forward-option.patch\n>>    0003-Restructure-git-merge.sh.patch\n>>    0004-Head-reduction-before-selecting-merge-strategy.patch\n>>    0005-Introduce-fast-forward-option-only.patch\n>\n> I consider myself finished with this and as far as I am concerned it\n> can be applied to git.git.  I am currently using this on top of 1.5.5\n> in production and assume it to be good.  All tests passes.\n\nSorry, but I am with a rather big backlog and am reluctant to go back the\narchive a looong way to pick up and comment on a series when not many\npeople are wondering what happened to the wonderful series ;-)  Care to\nresend and ask for comments from people?\n"},{"id":"75129","messageId":"402c10cd0804232239p3e49a6d0vdc1ff9acb3636a17@mail.gmail.com","threadId":"12628","inReplyTo":"7vhcdu2uu5.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH 0/5] Fast forward strategies allow, never, and only","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-04-24T05:39:48Z","receivedAt":"2008-04-24T05:39:48Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"On Tue, Apr 22, 2008 at 12:48 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>  Sorry, but I am with a rather big backlog and am reluctant to go back the\n>  archive a looong way to pick up and comment on a series when not many\n>  people are wondering what happened to the wonderful series ;-)  Care to\n>  resend and ask for comments from people?\n\n I resending these patches for you to coment.  The patch series\nconsists of the following five patches:\n\n  0001-New-merge-tests.patch\n  0002-Introduce-ff-fast-forward-option.patch\n  0003-Restructure-git-merge.sh.patch\n  0004-Head-reduction-before-selecting-merge-strategy.patch\n  0005-Introduce-fast-forward-option-only.patch\n\nThe first patch add some tests.  The second, fourth, and fifth adds\nnew features and they are all trivial.  I was able to make the fourth\npatch trivial as well by actually doing the real work of finding the\nreduced parents in the third patch.  The third patch computes the\nreduced parents but uses it only to determine whether we are\nup-to-date or do a fast forward.\n\nThere are probably some minor adjustments to the documentation we\nshould do. The patch series uses the term actual-parents and\nreduced-parents in the code and the documentation.  Maybe we should\nuse the term actual-heads and reduced-heads instead?  I am not sure\nthat all the documentation for 0004 should be included.  Please give\nme some advise regarding this.\n\n-- \nSverre Hvammen Johansen\n"}]}