{"thread":{"id":"11862","subject":"[RFC/PATCH] Fast forward strategies only, common, fork and path","startedAt":"2008-02-04T00:54:06Z","lastAt":"2008-02-06T03:46:35Z","messageCount":14,"participants":["Sverre Hvammen Johansen","Stefan (metze) Metzmacher","Junio C Hamano","Jakub Narebski"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"67295","messageId":"402c10cd0802031654r3e0275a8s1d2163af9525e7d2@mail.gmail.com","threadId":"11862","inReplyTo":null,"subject":"[RFC/PATCH] Fast forward strategies only, common, fork and path","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-02-04T00:54:06Z","receivedAt":"2008-02-04T00:54:06Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"-- \nSverre Hvammen Johansen\n\n\nFrom 5a156f0719f03fbeb4fffb7a22847739dd47ce2a Mon Sep 17 00:00:00 2001\nFrom: Sverre Hvammen Johansen <sj@black.local>\nDate: Sun, 3 Feb 2008 16:39:29 -0800\nSubject: [PATCH] Fast forward strategies only, common, fork and path\n\nNew fast forward strategies, only, common, fork, and path is introduced.\nThese new fast forward strategies allows additional work flows.\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\nFF strategy \"common\" does a fast-forward to the common ancestor\nof the specified heads.  The merge will fail unless HEAD is the\ncommon ancestor or HEAD can be fast-forwarded to the common ancestor.\n\nFF strategy \"fork\" does a fast-forward to the common ancestor\nof the real heads.  The merge will fail unless HEAD is the\ncommon ancestor of these heads or HEAD can be fast-forwarded\nto the common ancestor of the real heads.\n\nFF strategy \"path\" does a fast-forward to the first possible\nbranch that no other branches are ahead of.  HEAD will be\nfast-forwarded to such a branch if it exist.  If no such branch\nexist, HEAD is considered to be up to date.\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 <hvammen@gmail.com>\n---\n Documentation/fast-forward-strategies.txt |   36 ++\n Documentation/git-merge.txt               |    5 +-\n Documentation/git-pull.txt                |    2 +\n Documentation/merge-options.txt           |   11 +-\n git-merge.sh                              |  326 ++++++++----\n git-pull.sh                               |    4 +-\n t/t7601-merge-ff-strategies.sh            |  870 +++++++++++++++++++++++++++++\n 7 files changed, 1151 insertions(+), 103 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 b/Documentation/fast-forward-strategies.txt\nnew file mode 100644\nindex 0000000..e0da05c\n--- /dev/null\n+++ b/Documentation/fast-forward-strategies.txt\n@@ -0,0 +1,36 @@\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.\n+\n+common::\n+\tFast-forward to the common ancestor of the specified\n+\tbranches if possible.  The merge will fail unless\n+\tHEAD is the common ancestor or HEAD can be fast-\n+\tforwarded to the common ancestor.\n+\n+fork::\n+\tFast-forward to the earliest fork if possible.\n+\tThe earliest fork is defined as the common ancestor\n+\tof the real branches.  The merge will fail unless\n+\tHEAD is the earliest fork or HEAD can be fast-\n+\tforwarded to the earliest fork.\n+\n+path::\n+\tFast-forward to the first possible branch that\n+\tno other branches are ahead of.  HEAD will be\n+\tfast-forwarded to such a branch if it exist.\n+\tIf no such branch exist, HEAD is considered to be\n+\tup to date.\ndiff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt\nindex 0c9ad7f..47fcd9e 100644\n--- a/Documentation/git-merge.txt\n+++ b/Documentation/git-merge.txt\n@@ -10,7 +10,7 @@ SYNOPSIS\n --------\n [verse]\n 'git-merge' [-n] [--summary] [--no-commit] [--squash] [-s <strategy>]...\n-\t[-m <msg>] <remote> <remote>...\n+\t[--ff[=<fast forward strategy>]][-m <msg>] <remote> <remote>...\n 'git-merge' <msg> HEAD <remote>...\n \n DESCRIPTION\n@@ -37,6 +37,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 4cc633a..4388150 100644\n--- a/Documentation/git-pull.txt\n+++ b/Documentation/git-pull.txt\n@@ -32,6 +32,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 \\--rebase::\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 1c123a3..078f522 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 \n dropsave() {\n@@ -152,17 +152,34 @@ parse_config () {\n \t\t--summary)\n \t\t\tshow_diffstat=t ;;\n \t\t--squash)\n-\t\t\tallow_fast_forward=t squash=t no_commit=t ;;\n+\t\t\tfast_forward=allow squash=t no_commit=t ;;\n \t\t--no-squash)\n-\t\t\tallow_fast_forward=t squash= no_commit= ;;\n+\t\t\tfast_forward=allow squash= no_commit= ;;\n \t\t--commit)\n-\t\t\tallow_fast_forward=t squash= no_commit= ;;\n+\t\t\tfast_forward=allow squash= no_commit= ;;\n \t\t--no-commit)\n-\t\t\tallow_fast_forward=t squash= no_commit=t ;;\n+\t\t\tfast_forward=allow squash= no_commit=t ;;\n \t\t--ff)\n-\t\t\tallow_fast_forward=t squash= no_commit= ;;\n+\t\t\tcase \"$2\" in\n+\t\t\tallow|never|only|common|fork|path)\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 strategies are: allow, newer, only, common, path\" ;;\n+\t\t\tesac\n+\t\t\t;;\n+\t\t--ff=*)\n+\t\t\tfast_forward=$(echo $1 |cut -d = -f 2)\n+\t\t\tcase $fast_forward in\n+\t\t\t    allow|never|only|common|fork|path) \n+\t\t\t\t;;\n+\t\t\t    *)\n+\t\t\t\tdie \"available fast-forward strategies are: allow, newer, only, common, path\" ;;\n+\t\t\tesac\n+\t\t\t;;\n \t\t--no-ff)\n-\t\t\tallow_fast_forward=false squash= no_commit= ;;\n+\t\t\tfast_forward=never squash= no_commit= ;;\n \t\t-s|--strategy)\n \t\t\tshift\n \t\t\tcase \" $all_strategies \" in\n@@ -274,24 +291,156 @@ 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 --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 rev-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 $fast_forward = common\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+\tpath)\n+\t\techo \"Ignoring branches $real_parents\"\n+\t\treal_parents=\n+\t\t;;\n+\tcommon|fork)\n+\t\tif test $fast_forward = fork\n+\t\tthen\n+\t\t\tcommon=$(git show-branch --merge-base $ff_head $real_parents)\n+\t\telse\n+\t\t\tcommon=$(git show-branch --merge-base $real_parents)\n+\t\tfi\n+\t\tif test -n \"$common\"\n+\t\tthen\n+\t\t\tcommon_h=$(git show-branch --merge-base $common $head)\n+\t\t\tif test \"$common_h\" = $head\n+\t\t\tthen\n+\t\t\t\tif test $common = $head\n+\t\t\t\tthen\n+\t\t\t\t\t\techo \"Ignoring all branches\"\n+\t\t\t\telse\n+\t\t\t\t\techo \"Ignoring all branches except for $common\"\n+\t\t\t\tfi\n+\t\t\t\tff_head=$common\n+\t\t\telse\n+\t\t\t\tdie \"HEAD is ahead of the common anchestor\"\n+\t\t\tfi\n+\t\telse\n+\t\t\tdie \"The specified branches does not have any common anchestor\"\n+\t\tfi\n+\t\treal_parents=\n+\t\t;;\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@@ -303,7 +452,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@@ -318,88 +467,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 --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+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@@ -440,7 +574,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@@ -476,11 +610,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 $result_tree $parents) || exit\n@@ -510,7 +644,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 46da0f4..6cbb0fe 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=*)\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..28fca88\n--- /dev/null\n+++ b/t/t7601-merge-ff-strategies.sh\n@@ -0,0 +1,870 @@\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 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 (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_expect_success 'merge y2 with x0, c3, and c0 (--ff=path)' '\n+\tgit reset --hard y2 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge --ff=path x0 c3 c0 &&\n+\tverify_merge file result.1-5-13 &&\n+\tverify_head $y2\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge x0 with y2, c3, and c0 (--ff=path)' '\n+\tgit reset --hard x0 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge --ff=path y2 c3 c0 &&\n+\tverify_merge file result.1-5-13 &&\n+\tverify_head $y2\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with x0, c3, and c0 (--ff=path)' '\n+\tgit reset --hard c0 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge --ff=path x0 c3 c0 &&\n+\tverify_merge file result.1-5 &&\n+\tverify_head $x0\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with y2, c3, and c0 (--ff=path)' '\n+\tgit reset --hard c0 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge --ff=path y2 c3 c0 &&\n+\tverify_merge file result.1-5-13 &&\n+\tverify_head $y2\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with x0, y3, and c0 (--ff=path)' '\n+\tgit reset --hard c0 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge --ff=path x0 y3 c0 &&\n+\tverify_merge file result.1-5-13 &&\n+\tverify_head $y3\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with x0, c3, and y3 (--ff=path)' '\n+\tgit reset --hard c0 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge --ff=path x0 c3 y3 &&\n+\tverify_merge file result.1-5-13 &&\n+\tverify_head $y3\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with y1, y2, and y3 (--ff=path)' '\n+\tgit reset --hard c0 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge --ff=path y1 y2 y3 &&\n+\tverify_merge file result.1-5-13 &&\n+\tverify_head $y3\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with y1 (--ff=common)' '\n+\tgit reset --hard c0 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge --ff=common y1 &&\n+\tverify_merge file result.1-5-13 &&\n+\tverify_head $y1\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with y1, y2, and y3 (--ff=common)' '\n+\tgit reset --hard c0 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge --ff=common y1 y2 y3 &&\n+\tverify_merge file result.1-5 &&\n+\tverify_head $x0\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with c1 and c2 (--ff=common)' '\n+\tgit reset --hard c0 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge --ff=common 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 c0 with y1 and y2 (--ff=common)' '\n+\tgit reset --hard c0 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge --ff=common y1 y2 &&\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 y1 and y2 (--ff=common)' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge --ff=common y1 y2 &&\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 y1 and y2 c0 (--ff=common)' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tif git merge --ff=common y1 y2 c0\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 c1 with x1 and c2 (--ff=common)' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tif git merge --ff=common x1 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 x0 (--ff=fork)' '\n+\tgit reset --hard c0 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge --ff=fork x0 &&\n+\tverify_merge file result.1-5 &&\n+\tverify_head $x0\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with y1, y2, and y3 (--ff=fork)' '\n+\tgit reset --hard c0 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge --ff=fork y1 y2 y3 &&\n+\tverify_merge file result.1-5-13 &&\n+\tverify_head $y3\n+'\n+\n+test_debug 'gitk --all'\n+\n+test_expect_success 'merge c0 with c1 and c2 (--ff=fork)' '\n+\tgit reset --hard c0 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge --ff=fork 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 c0 with y1 and y2 (--ff=fork)' '\n+\tgit reset --hard c0 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge --ff=fork y1 y2 &&\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 y1 and y2 (--ff=fork)' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge --ff=fork y1 y2 &&\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 y1 and y2 c0 (--ff=fork)' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tgit merge --ff=fork y1 y2 c0 &&\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 x1 and c2 (--ff=fork)' '\n+\tgit reset --hard c1 &&\n+\tgit config branch.master.mergeoptions \"\" &&\n+\ttest_tick &&\n+\tif git merge --ff=fork x1 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 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+\tif git pull --ff=only clone refs/heads/master\n+\tthen\n+\t\tfalse\n+\telse\n+\t\tverify_merge file result.5-13 &&\n+\t\tverify_head $x2\n+\tfi\n+'\n+\n+test_debug 'gitk --all'\n+\n+\n+\n+test_expect_success 'merge c1 with new repository (pull --ff=only)' '\n+\tgit reset --hard c1 &&\n+\ttest_tick &&\n+\tif git pull --ff=only new refs/heads/master\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 c1 with new repository (pull --ff=common)' '\n+\tgit reset --hard c1 &&\n+\ttest_tick &&\n+\tif git pull --ff=common new refs/heads/master\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 c1 with new repository (pull --ff=fork)' '\n+\tgit reset --hard c1 &&\n+\ttest_tick &&\n+\tif git pull --ff=fork new refs/heads/master\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 c1 with new repository (pull --ff=path)' '\n+\tgit reset --hard c1 &&\n+\ttest_tick &&\n+\tgit pull --ff=path 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":"67318","messageId":"47A69956.7030107@samba.org","threadId":"11862","inReplyTo":"402c10cd0802031654r3e0275a8s1d2163af9525e7d2@mail.gmail.com","subject":"Re: [RFC/PATCH] Fast forward strategies only, common, fork and path","fromName":"Stefan (metze) Metzmacher","fromEmail":"metze@samba.org","sentAt":"2008-02-04T04:49:26Z","receivedAt":"2008-02-04T04:49:26Z","isPatch":true,"sender":{"key":"metze@samba.org","avatar":null},"body":"Hi Sverre,\n\n> New fast forward strategies, only, common, fork, and path is introduced.\n> These new fast forward strategies allows additional work flows.\n\nI really like that concept! I was always looking for a way to force\na fast-forward.\n\nmetze\n\n"},{"id":"67321","messageId":"402c10cd0802032251y626f373eke66c35b200ccf5b1@mail.gmail.com","threadId":"11862","inReplyTo":"402c10cd0802031654r3e0275a8s1d2163af9525e7d2@mail.gmail.com","subject":"Re: [RFC/PATCH] Fast forward strategies only, common, fork and path","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-02-04T06:51:30Z","receivedAt":"2008-02-04T06:51:30Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"On Feb 3, 2008 4:54 PM, Sverre Hvammen Johansen <hvammen@gmail.com> wrote:\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\nI intend to also submit a patch that does fast forward in combination\nwith a real merge.  This means that some case can be reduced down to\nonly two real parents and we can select a  twohead strategy instead of\nan octopus strategy.  In cases where we have at least three real\nparents there is no point in doing this.  We only need to specify the\nfast forwarded head right after head to allow git-merge-octopus to do\nits best.\n\nI need some advise how to implement fast-forward in combination with a\nreal merge.  I know how to do this with an update of the directory\ntree and the index with the fast forward before the real merge is\ndone.  But, how canweI do this without updating the directory tree\nwith the fast forward?  Or is it OK to always update the directory\nwith the intermediate state we get from the fast forward?\n\n> --\n> Sverre Hvammen Johansen\n>\n\n\n\n-- \nSverre Hvammen Johansen\n"},{"id":"67324","messageId":"402c10cd0802032313la7d3a8cqa4ec34e100385fb4@mail.gmail.com","threadId":"11862","inReplyTo":"402c10cd0802031654r3e0275a8s1d2163af9525e7d2@mail.gmail.com","subject":"Re: [RFC/PATCH] Fast forward strategies only, common, fork and path","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-02-04T07:13:50Z","receivedAt":"2008-02-04T07:13:50Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"git-pull only accepts one repository.  With this patch it makes sense\nto accept more than one repository.  I would like to rewrite git-pull\nto accept more than one repository.  This might break compatibility\nwith existing git-pull.  One solution could be to introduce a new\ncommand that does the same as git-pull and more.  What about naming\nsuch a command git-update and deprecate git-pull.\n\nI believe such an command should not specify the repository and the\nrefspec but should instead specify the remote tracking branch or a\nlocal branch if descired.  I believe this would be more natural to use\nand just as easier to implement.\n\n-- \nSverre Hvammen Johansen\n"},{"id":"67325","messageId":"402c10cd0802032319j757e6f9arddca84255a9f9fad@mail.gmail.com","threadId":"11862","inReplyTo":"402c10cd0802031654r3e0275a8s1d2163af9525e7d2@mail.gmail.com","subject":"Re: [RFC/PATCH] Fast forward strategies only, common, fork and path","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-02-04T07:19:54Z","receivedAt":"2008-02-04T07:19:54Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"On Feb 3, 2008 4:54 PM, Sverre Hvammen Johansen <hvammen@gmail.com> wrote:\n> find_real_parents ()\n\nWe should probably implement this using \"git show-branch --independent\n<heads>\".  Will this command do the same job (except showing which\nbranch is ff_head)?\n\n-- \nSverre Hvammen Johansen\n"},{"id":"67326","messageId":"7vwsplkwuq.fsf@gitster.siamese.dyndns.org","threadId":"11862","inReplyTo":"402c10cd0802032251y626f373eke66c35b200ccf5b1@mail.gmail.com","subject":"Re: [RFC/PATCH] Fast forward strategies only, common, fork and path","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-02-04T07:24:29Z","receivedAt":"2008-02-04T07:24:29Z","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> I intend to also submit a patch that does fast forward in combination\n> with a real merge.\n\nPlease make the next round an in-line patch.  Attachments cannot\nbe commented on, and an RFC patch is all about getting comments,\nnot about being included.  Whitespace breakages do not matter as\nmuch as the final submissions; readability and commentability\nmatters more.\n\nInstead of adding many new sub-strategies at once, I think it\nwould make it easier to review to split the patch into (1) code\nmovement without adding any functionality changes to make your\nfurther changes easier, if such a change is needed in your work\n(I did not really look at the attachment carefully), (2) add\nlogic to find out the set of independent parents to remove\nredundant parents (perhaps using show-branch --independent? I\ndunno) and conditionally use it, (3) add infrastructure to allow\nadding different --ff=<what-to-do>, and then finally (4) a\nseparate patch for each of <what-to-do>.\n\nI suspect (2) is controversial if made unconditional.  Some\npeople do not even like the fast-forward \"merges\" we have\ntraditionally done.\n"},{"id":"67327","messageId":"7vsl09kwjg.fsf@gitster.siamese.dyndns.org","threadId":"11862","inReplyTo":"402c10cd0802032313la7d3a8cqa4ec34e100385fb4@mail.gmail.com","subject":"Re: [RFC/PATCH] Fast forward strategies only, common, fork and path","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-02-04T07:31:15Z","receivedAt":"2008-02-04T07:31:15Z","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> git-pull only accepts one repository.  With this patch it makes sense\n> to accept more than one repository.  I would like to rewrite git-pull\n> to accept more than one repository.  This might break compatibility\n> with existing git-pull.  One solution could be to introduce a new\n> command that does the same as git-pull and more.  What about naming\n> such a command git-update and deprecate git-pull.\n\nJust add contrib/multi-pull/ directory and put your shell script\nthere, something like:\n\n\t#!/bin/sh\n\tappend=\n\tfor repo\n        do\n        \tgit fetch $append $repo\n                append=--append\n\tdone\n\tmerge_name=$(git fmt-merge-msg <\"$GIT_DIR/FETCH_HEAD\")\n        git merge -m \"$merge_name\" $(sed -e '/\tnot-for-merge\t/d'\n        \t\t-e 's/\t.*//' \"$GIT_DIR/FETCH_HEAD\")\n\nIf it turns out to be useful for many people, it may become part\nof the main Porcelain.  It's too early to talk about touching\ngit-pull at all.\n"},{"id":"67329","messageId":"402c10cd0802032343k60b78796ycdb36e2a74594a27@mail.gmail.com","threadId":"11862","inReplyTo":"7vsl09kwjg.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFC/PATCH] Fast forward strategies only, common, fork and path","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-02-04T07:43:54Z","receivedAt":"2008-02-04T07:43:54Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"> Just add contrib/multi-pull/ directory and put your shell script\n> there, something like: ...\n\nThanks will do.\n\n> If it turns out to be useful for many people, it may become part\n> of the main Porcelain.  It's too early to talk about touching\n> git-pull at all.\n\nI agree.\n\n-- \nSverre Hvammen Johansen\n"},{"id":"67332","messageId":"402c10cd0802040006yb654688l8dfc7140c507bc26@mail.gmail.com","threadId":"11862","inReplyTo":"7vwsplkwuq.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFC/PATCH] Fast forward strategies only, common, fork and path","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-02-04T08:06:09Z","receivedAt":"2008-02-04T08:06:09Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"On Feb 3, 2008 11:24 PM, Junio C Hamano <gitster@pobox.com> wrote:\n> Please make the next round an in-line patch.  Attachments cannot\n> be commented on, and an RFC patch is all about getting comments,\n> not about being included.  Whitespace breakages do not matter as\n> much as the final submissions; readability and commentability\n> matters more.\n\nI will post an update in a few days, with a few bug-fixes.\n\n> Instead of adding many new sub-strategies at once, I think it\n> would make it easier to review to split the patch into (1) code\n> movement without adding any functionality changes to make your\n> further changes easier, if such a change is needed in your work\n> (I did not really look at the attachment carefully), (2) add\n> logic to find out the set of independent parents to remove\n> redundant parents (perhaps using show-branch --independent? I\n> dunno) and conditionally use it, (3) add infrastructure to allow\n> adding different --ff=<what-to-do>, and then finally (4) a\n> separate patch for each of <what-to-do>.\n\nThe patch is not easy to read for git-merge.sh.  You really need to\napply the patch and then review the code.  If I follow your suggestion\nabove it might be easier to read the patches.  I will do if tthere is\na demand for a split.  However, it might take some time.  What is the\ntime-frame for inclusion in 1.5.5?\n\n> I suspect (2) is controversial if made unconditional.  Some\n> people do not even like the fast-forward \"merges\" we have\n> traditionally done.\n\n--ff=never will turn this off together with fast forward.  Maybe we\nshould have --ff=traditional that is the old behavior.\n\n-- \nSverre Hvammen Johansen\n"},{"id":"67335","messageId":"7v8x21ku6b.fsf@gitster.siamese.dyndns.org","threadId":"11862","inReplyTo":"402c10cd0802040006yb654688l8dfc7140c507bc26@mail.gmail.com","subject":"Re: [RFC/PATCH] Fast forward strategies only, common, fork and path","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-02-04T08:22:20Z","receivedAt":"2008-02-04T08:22:20Z","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> ...  However, it might take some time.  What is the\n> time-frame for inclusion in 1.5.5?\n\nThe 1.5.4 cycle was too long (5 months).  A regular interval\nought to be about 3 months but I'd really like to keep 1.5.5\nfocused on obvious and unanimously supported changes that do not\nimpact the existing semantics deeply, and keep it shorter than\nthat.\n\nI think it is too early to tell if this topic falls into that\ncategory.  The patch is only a few day old, and there has been\nno real discussion nor comments on it yet.  I also felt that the\nbackstory was lacking and it would be hard to judge for people\nother than yourself how useful the new ff substrategies are in\nwhat context.\n\nThe documentation updates talked about what the options do, but\nit was unclear why they could be useful in what situations and\nworkflows.  At least it was not apparent to me on my cursory\nread.\n\n> --ff=never will turn this off together with fast forward.  Maybe we\n> should have --ff=traditional that is the old behavior.\n\nSure, and I mildly suspect that it should be the default.\n"},{"id":"67488","messageId":"402c10cd0802042332i4e49cdaxf1fa1a7fc09c15b9@mail.gmail.com","threadId":"11862","inReplyTo":"7v8x21ku6b.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFC/PATCH] Fast forward strategies only, common, fork and path","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-02-05T07:32:45Z","receivedAt":"2008-02-05T07:32:45Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"On Feb 4, 2008 12:22 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>\n> The documentation updates talked about what the options do, but\n> it was unclear why they could be useful in what situations and\n> workflows.  At least it was not apparent to me on my cursory\n> read.\n\nCommon, fork, and path only make sense where there are at least three\nrepositories or two plus an observer involved.\n\nLets explain the observer cases.\n\nThe observer is interested in changes that X, Y and Z agree upon.  He\ncan merge as follows:\n\n  git merge --ff=common X Y Z\n\nThe observer is interested in changes up to the point where someone is\nknown to disagree.  He can merge as follows:\n\n   git merge --ff=fork X Y Z\n\n The observer is interested in any give path up to one of the true\nparents.  He can merge as follows:\n\n  git merge --ff=path X Y Z\n\nThis will give priority to X then Y.\n\nThis + only are all the interesting cases for fast forward.  Some work\nflows between more than two repositories in the general case would\nrequire additional features for rebase:  Rebase on any patch, the fork\npoint, or common ancestor of the remote branches.  This is something I\nwould like to discuss at some later time.\n\n> > --ff=never will turn this off together with fast forward.  Maybe we\n> > should have --ff=traditional that is the old behavior.\n>\n> Sure, and I mildly suspect that it should be the default.\n\nI would argue that it should not be the default, simply because we\nalready use the real parents when only two branches are involved, This\nis most convenient for most users.   Exactly the same argument holds\nwhen there are more than two branches involved.  The history may be\nsimplified in a similar way.   Most projects would however not care\nsimply because they never merge more than two branches.   It is quite\ncommon to have one topic branch that is based on the tip of another\ntopic branch and if those are merged back to the main branch you would\nlike it to be reduced down to two real heads (or even a fast forward),\nexactly as one branch would result in a fast forward if it happened to\nbe based on the tip of master.\n\n-- \nSverre Hvammen Johansen\n"},{"id":"67492","messageId":"m38x1z692o.fsf@localhost.localdomain","threadId":"11862","inReplyTo":"402c10cd0802042332i4e49cdaxf1fa1a7fc09c15b9@mail.gmail.com","subject":"Re: [RFC/PATCH] Fast forward strategies only, common, fork and path","fromName":"Jakub Narebski","fromEmail":"jnareb@gmail.com","sentAt":"2008-02-05T09:34:15Z","receivedAt":"2008-02-05T09:34:15Z","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> On Feb 4, 2008 12:22 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> >\n> > The documentation updates talked about what the options do, but\n> > it was unclear why they could be useful in what situations and\n> > workflows.  At least it was not apparent to me on my cursory\n> > read.\n> \n> Common, fork, and path only make sense where there are at least three\n> repositories or two plus an observer involved.\n> \n> Lets explain the observer cases.\n> \n> The observer is interested in changes that X, Y and Z agree upon.  He\n> can merge as follows:\n> \n>   git merge --ff=common X Y Z\n> \n> The observer is interested in changes up to the point where someone is\n> known to disagree.  He can merge as follows:\n> \n>    git merge --ff=fork X Y Z\n> \n>  The observer is interested in any give path up to one of the true\n> parents.  He can merge as follows:\n> \n>   git merge --ff=path X Y Z\n> \n> This will give priority to X then Y.\n\nCould you please provide ascii-art diagrams for above explanations\n(above cases), such as the following diagrams for fast-forward, and\nfor forced merge (no fast-forward)? This would make your explanations\nmuch easier to follow, I think.\n\n\n1. Fast-forward (\"traditional\", 2 heads)\n\n   before merge\n\n   a---b---c---d               <-- main\n                \\\n                 \\-E---F---G  <-- side\n\n\n   after merge\n\n   a---b---c---d---E---F---G  <-- main\n                           ^\n                            \\----- side\n\n2. Forced merge commits (\"never\", 2 heads)\n\n   before merge\n\n   a---b---c---d               <-- main\n                \\\n                 \\-E---F---G  <-- side\n\n\n   after merge\n\n   a---b---c---d-------------*  <-- main\n                \\           /\n                 \\-E---F---G    <-- side\n\n\n-- \nJakub Narebski\nPoland\nShadeHawk on #git\n"},{"id":"67494","messageId":"7v7ihjd9lq.fsf@gitster.siamese.dyndns.org","threadId":"11862","inReplyTo":"402c10cd0802042332i4e49cdaxf1fa1a7fc09c15b9@mail.gmail.com","subject":"Re: [RFC/PATCH] Fast forward strategies only, common, fork and path","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-02-05T09:40:49Z","receivedAt":"2008-02-05T09:40:49Z","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 Feb 4, 2008 12:22 AM, Junio C Hamano <gitster@pobox.com> wrote:\n>>\n>> The documentation updates talked about what the options do, but\n>> it was unclear why they could be useful in what situations and\n>> workflows.  At least it was not apparent to me on my cursory\n>> read.\n>\n> Common, fork, and path only make sense where there are at least three\n> repositories or two plus an observer involved.\n>\n> Lets explain the observer cases.\n\nThis is a good material.  The information here should reach the\nend users who will be exposed to these new features in the\ndocumentation patch before the final submission.\n\n> The observer is interested in changes that X, Y and Z agree upon.  He\n> can merge as follows:\n>\n>   git merge --ff=common X Y Z\n\nJust to avoid confusion by making sure I understand.  Earlier\nyou said two plus an observer above but the example is about one\nobserver and three other repositories, and the \"merge\" operation\nhappens inside the observer's one.\n\nThis reminds me of an ancient message I sent to darcs list,\nbefore or soon after I took git.git over, with a topic (iirc)\n\"mutually supervised developer workflow\".  Each developer may\nmake excellent and crap changes, but they communicate with each\nother.  The \"consensus\" can be made by picking the changes that\nappear in all repositories (or majority of repositories).\n\nThat is unfortunately hard to arrange in git workflow if you do\nnot use topic branches and/or if you rebase often (but would be\nmore natural match to darcs's world view).  Even if you have a\nset of changes in the same spirit (i.e. \"the same patch text\"),\nthe committer and the author information would make the actual\ncommit objects different, so you would need to identify\ndifferent commits that introduce the same change for this to be\nreally useful.\n\nAnd it may not work well in practice, even if we somehow solved\nthat \"changes in the same spirit are often made into different\ncommits\" issue.  If the place they agree last is a dud, and some\nhave fix-ups that others lack, the observer will end up getting\nthat common dud commit.\n\n> The observer is interested in changes up to the point where someone is\n> known to disagree.  He can merge as follows:\n>\n>   git merge --ff=fork X Y Z\n\nIf --ff=common fast-forwards to the commit all of X, Y, and Z\nhave in common, that means commits on X, Y or Z that are beyond\nthat point are the ones these three do not agree with.  How's\nthat different from --ff=fork?\n\n>  The observer is interested in any give path up to one of the true\n> parents.  He can merge as follows:\n>\n>   git merge --ff=path X Y Z\n>\n> This will give priority to X then Y.\n\n\"Any given path up to one of the true parents?\"  Path from\nwhere?  How is \"true parent\" (as opposed to untrue ones?)\ndefined?\n\n> This + only are all the interesting cases for fast forward.  Some work\n> flows between more than two repositories in the general case would\n> require additional features for rebase:  Rebase on any patch, the fork\n> point, or common ancestor of the remote branches.  This is something I\n> would like to discuss at some later time.\n>\n>> > --ff=never will turn this off together with fast forward.  Maybe we\n>> > should have --ff=traditional that is the old behavior.\n>>\n>> Sure, and I mildly suspect that it should be the default.\n>\n> I would argue that it should not be the default, simply because we\n> already use the real parents when only two branches are involved, This\n> is most convenient for most users.\n\nSorry, I referred --ff=traditional by \"it\".  Are we talking\nabout the same thing?\n"},{"id":"67609","messageId":"402c10cd0802051946l4e6a7a97qd2231a92c154e1fc@mail.gmail.com","threadId":"11862","inReplyTo":"7v7ihjd9lq.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFC/PATCH] Fast forward strategies only, common, fork and path","fromName":"Sverre Hvammen Johansen","fromEmail":"hvammen@gmail.com","sentAt":"2008-02-06T03:46:35Z","receivedAt":"2008-02-06T03:46:35Z","isPatch":true,"sender":{"key":"hvammen@gmail.com","avatar":"https://gravatar.com/avatar/d1fc25ec327eea135af60fe7b56b2a2e21f711aa32167e7f505014ec88aa24bc?d=mp&s=160"},"body":"On Feb 5, 2008 1:40 AM, Junio C Hamano <gitster@pobox.com> wrote:\n> Just to avoid confusion by making sure I understand.  Earlier\n> you said two plus an observer above but the example is about one\n> observer and three other repositories, and the \"merge\" operation\n> happens inside the observer's one.\n\nThere need to be at least 2 + 1.  There is no difference between\n--ff=common and --ff=path when there are 2+1.  I used 3+1 in the\nexamples.\n\n> That is unfortunately hard to arrange in git workflow if you do\n> not use topic branches and/or if you rebase often (but would be\n> more natural match to darcs's world view).  Even if you have a\n> set of changes in the same spirit (i.e. \"the same patch text\"),\n> the committer and the author information would make the actual\n> commit objects different, so you would need to identify\n> different commits that introduce the same change for this to be\n> really useful.\n\nAnd I  have no intention of doing that.  This patch will keep it\nsimple.  I imagine that these features will not be used by most users\nand we should probably not document them in the tutorials.\n\n> > The observer is interested in changes up to the point where someone is\n> > known to disagree.  He can merge as follows:\n> >\n> >   git merge --ff=fork X Y Z\n>\n> If --ff=common fast-forwards to the commit all of X, Y, and Z\n> have in common, that means commits on X, Y or Z that are beyond\n> that point are the ones these three do not agree with.  How's\n> that different from --ff=fork?\n\nLets say X is on the path before the fork point before Y and Z.  X is\nthen the common ancestor, otherwise there will not be any difference.\n\n> >  The observer is interested in any give path up to one of the true\n> > parents.  He can merge as follows:\n> >\n> >   git merge --ff=path X Y Z\n> >\n> > This will give priority to X then Y.\n>\n> \"Any given path up to one of the true parents?\"  Path from\n> where?  How is \"true parent\" (as opposed to untrue ones?)\n> defined?\n\nThe path from HEAD.\n\nConsider a number of distinct parents, then we can define it as\nfollows.  An untrue parent is a parent that can be fast forwarded to\nat least one other parent.  A true (or real) parent is a parent that\ncan not be fast forwarded to any other parent.\n\n> > This + only are all the interesting cases for fast forward.  Some work\n> > flows between more than two repositories in the general case would\n> > require additional features for rebase:  Rebase on any patch, the fork\n> > point, or common ancestor of the remote branches.  This is something I\n> > would like to discuss at some later time.\n> >\n> >> > --ff=never will turn this off together with fast forward.  Maybe we\n> >> > should have --ff=traditional that is the old behavior.\n> >>\n> >> Sure, and I mildly suspect that it should be the default.\n> >\n> > I would argue that it should not be the default, simply because we\n> > already use the real parents when only two branches are involved, This\n> > is most convenient for most users.\n>\n> Sorry, I referred --ff=traditional by \"it\".  Are we talking\n> about the same thing?\n\nWhat I mean is that the commit should only record the true parents.\nThat may already be the case by use of git show-branch --independent.\nI am not sure what that option does.  However, the merge strategy\nitself is picked based on the parents specified.  That part need to\nchange.  It could be that the patch could use the above option to find\nthe true parents.  Someone need to explain to me exactly what that\noption does.\n\nI am not exactly sure how a work flow between commiters would be.\nWith one coordinator it would be as follows.  The coordinator would\nmerge using any of the --ff options.  He would use --ff=same,\n--ff=only, or --ff=common if everyone needs to agree.  He would use\n--ff=fork when less care needs to be taken and --ff=path when he\nessentially don't  care.  Everyone else needs to rebase on his work or\nsomething else based on his work.  --ff=same is not included in the\npatch.  It requires everyone except for HEAD to point to the same\ncommit.\n\nI  am really busy for about three weeks and will not be able to do\nmuch before beginning of March.\n\n-- \nSverre Hvammen Johansen\n"}]}