-- Sverre Hvammen Johansen
From 5a156f0719f03fbeb4fffb7a22847739dd47ce2a Mon Sep 17 00:00:00 2001 From: Sverre Hvammen Johansen <sj@xxxxxxxxxxx> Date: Sun, 3 Feb 2008 16:39:29 -0800 Subject: [PATCH] Fast forward strategies only, common, fork and path New fast forward strategies, only, common, fork, and path is introduced. These new fast forward strategies allows additional work flows. FF strategy "only" fails if the specified heads and HEAD can not be reduced down to only one real parent. The only allowed outcome is a fast forward unless HEAD is up to date with the specified heads. FF strategy "common" does a fast-forward to the common ancestor of the specified heads. The merge will fail unless HEAD is the common ancestor or HEAD can be fast-forwarded to the common ancestor. FF strategy "fork" does a fast-forward to the common ancestor of the real heads. The merge will fail unless HEAD is the common ancestor of these heads or HEAD can be fast-forwarded to the common ancestor of the real heads. FF strategy "path" does a fast-forward to the first possible branch that no other branches are ahead of. HEAD will be fast-forwarded to such a branch if it exist. If no such branch exist, HEAD is considered to be up to date. This patch also uses the real heads found instead of those specified for real merges. This means that merge startegies that only take two heads can now accept more than two heads if they can be reduced down to only two real heads. However, fast-forward of head in combination with a real merge is handled as before. Signed-off-by: Sverre Hvammen Johansen <hvammen@xxxxxxxxx> --- Documentation/fast-forward-strategies.txt | 36 ++ Documentation/git-merge.txt | 5 +- Documentation/git-pull.txt | 2 + Documentation/merge-options.txt | 11 +- git-merge.sh | 326 ++++++++---- git-pull.sh | 4 +- t/t7601-merge-ff-strategies.sh | 870 +++++++++++++++++++++++++++++ 7 files changed, 1151 insertions(+), 103 deletions(-) create mode 100644 Documentation/fast-forward-strategies.txt create mode 100755 t/t7601-merge-ff-strategies.sh diff --git a/Documentation/fast-forward-strategies.txt b/Documentation/fast-forward-strategies.txt new file mode 100644 index 0000000..e0da05c --- /dev/null +++ b/Documentation/fast-forward-strategies.txt @@ -0,0 +1,36 @@ +FAST FORWARD STRATEGIES +----------------------- + +allow:: + Do not generate a merge commit if the merge resolved + as a fast-forward, only update the branch pointer. + This is the default behavior of git-merge. + +never:: + Generate a merge commit even if the merge resolved + as a fast-forward. + +only:: + Only allow a fast-forward. The merge will fail + unless HEAD is up to date or the merge resolved as + a fast-forward. + +common:: + Fast-forward to the common ancestor of the specified + branches if possible. The merge will fail unless + HEAD is the common ancestor or HEAD can be fast- + forwarded to the common ancestor. + +fork:: + Fast-forward to the earliest fork if possible. + The earliest fork is defined as the common ancestor + of the real branches. The merge will fail unless + HEAD is the earliest fork or HEAD can be fast- + forwarded to the earliest fork. + +path:: + Fast-forward to the first possible branch that + no other branches are ahead of. HEAD will be + fast-forwarded to such a branch if it exist. + If no such branch exist, HEAD is considered to be + up to date. diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index 0c9ad7f..47fcd9e 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -10,7 +10,7 @@ SYNOPSIS -------- [verse] 'git-merge' [-n] [--summary] [--no-commit] [--squash] [-s <strategy>]... - [-m <msg>] <remote> <remote>... + [--ff[=<fast forward strategy>]][-m <msg>] <remote> <remote>... 'git-merge' <msg> HEAD <remote>... DESCRIPTION @@ -37,6 +37,9 @@ include::merge-options.txt[] least one <remote>. Specifying more than one <remote> obviously means you are trying an Octopus. + +include::fast-forward-strategies.txt[] + include::merge-strategies.txt[] diff --git a/Documentation/git-pull.txt b/Documentation/git-pull.txt index 4cc633a..4388150 100644 --- a/Documentation/git-pull.txt +++ b/Documentation/git-pull.txt @@ -32,6 +32,8 @@ include::pull-fetch-param.txt[] include::urls-remotes.txt[] +include::fast-forward-strategies.txt[] + include::merge-strategies.txt[] \--rebase:: diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt index 9f1fc82..848b786 100644 --- a/Documentation/merge-options.txt +++ b/Documentation/merge-options.txt @@ -29,12 +29,13 @@ --no-ff:: Generate a merge commit even if the merge resolved as a - fast-forward. + fast-forward. --on-ff is an alias for --ff=never. ---ff:: - Do not generate a merge commit if the merge resolved as - a fast-forward, only update the branch pointer. This is - the default behavior of git-merge. +--ff[=<fast forward strategy>]:: + Select fast forward strategy. --ff without any argument + is an alias for --ff=allow which is the default behavior + of git-merge. It will not generate a merge commit if the + merge resolved as a fast-forward, -s <strategy>, \--strategy=<strategy>:: Use the given merge strategy; can be supplied more than diff --git a/git-merge.sh b/git-merge.sh index 1c123a3..078f522 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -12,7 +12,7 @@ summary show a diffstat at the end of the merge n,no-summary don't show a diffstat at the end of the merge squash create a single commit instead of doing a merge commit perform a commit if the merge sucesses (default) -ff allow fast forward (default) +ff? allow fast forward (default) s,strategy= merge strategy to use m,message= message to be used for the merge commit (if any) " @@ -35,7 +35,7 @@ no_fast_forward_strategies='subtree ours' no_trivial_strategies='recursive recur subtree ours' use_strategies= -allow_fast_forward=t +fast_forward=allow allow_trivial_merge=t dropsave() { @@ -152,17 +152,34 @@ parse_config () { --summary) show_diffstat=t ;; --squash) - allow_fast_forward=t squash=t no_commit=t ;; + fast_forward=allow squash=t no_commit=t ;; --no-squash) - allow_fast_forward=t squash= no_commit= ;; + fast_forward=allow squash= no_commit= ;; --commit) - allow_fast_forward=t squash= no_commit= ;; + fast_forward=allow squash= no_commit= ;; --no-commit) - allow_fast_forward=t squash= no_commit=t ;; + fast_forward=allow squash= no_commit=t ;; --ff) - allow_fast_forward=t squash= no_commit= ;; + case "$2" in + allow|never|only|common|fork|path) + fast_forward=$2; shift ;; + -*) + fast_forward=allow ;; + *) + die "available fast-forward strategies are: allow, newer, only, common, path" ;; + esac + ;; + --ff=*) + fast_forward=$(echo $1 |cut -d = -f 2) + case $fast_forward in + allow|never|only|common|fork|path) + ;; + *) + die "available fast-forward strategies are: allow, newer, only, common, path" ;; + esac + ;; --no-ff) - allow_fast_forward=false squash= no_commit= ;; + fast_forward=never squash= no_commit= ;; -s|--strategy) shift case " $all_strategies " in @@ -274,24 +291,156 @@ do done set x $remoteheads ; shift +echo "$head" >"$GIT_DIR/ORIG_HEAD" + +find_one_real_parent () { + # The real parent candidate + real_parent=$1 + shift + + # Other parents that are indepent of the real parent candidate + other_parents= + + # Parents that need further processing to determine whether + # they are independent parents of the parent candidate or not + parents_x= + + while test $# -gt 0 + do + if test $real_parent = $1 + then + # Found a parent that is equal to the real + # parent candidate + echo "Duplicate $(git rev-parse --short $1)" + echo "Ignoring $1" + else + common_b=$(git merge-base --all $real_parent $1) + + if test "$common_b" = $1 + then + # Found a parent that is not + # independent of the real parent + # candidate + echo "Possible ff $(git rev-parse --short $1)..$(git rev-parse --short $real_parent)." + echo "Ignoring $1" + elif test "$common_b" = $real_parent + then + # Found a better real parent candidate + echo "Possible ff $(git rev-parse --short $real_parent)..$(git rev-parse --short $1)." + echo "Ignoring $real_parent" + real_parent=$1 + parents_x="$other_parents" + other_parents= + else + # Found a parent that is independent + # of the real parent candidate + other_parents="$other_parents $1" + fi + fi + shift + done + + # We have a real parent, some parents we know is independt of + # this real parent, and some parents that need further + # processing. + + for b in $parents_x + do + common_b=$(git merge-base --all $real_parent $b) + if test "$common_b" != $b + then + other_parents="$other_parents $b" + fi + done + + # We have a real parent and other parents we know is independent + # of this real parent +} + +find_real_parents () { + find_one_real_parent $head "$@" + ff_head=$real_parent + real_parents= + + while test -n "$other_parents" + do + find_one_real_parent $other_parents + real_parents="$real_parents $real_parent" + done +} + +if test $fast_forward = never -o $fast_forward = common +then + real_parents="$@" + ff_head=$head +else + find_real_parents "$@" +fi + +if test -n "$real_parents" +then + case $fast_forward in + only) + die "Fast forward strategy only can only handle one real parent" ;; + path) + echo "Ignoring branches $real_parents" + real_parents= + ;; + common|fork) + if test $fast_forward = fork + then + common=$(git show-branch --merge-base $ff_head $real_parents) + else + common=$(git show-branch --merge-base $real_parents) + fi + if test -n "$common" + then + common_h=$(git show-branch --merge-base $common $head) + if test "$common_h" = $head + then + if test $common = $head + then + echo "Ignoring all branches" + else + echo "Ignoring all branches except for $common" + fi + ff_head=$common + else + die "HEAD is ahead of the common anchestor" + fi + else + die "The specified branches does not have any common anchestor" + fi + real_parents= + ;; + never|allow) + if test $head != $ff_head + then + real_parents="$ff_head $real_parents" + ff_head=$head + fi + ;; + esac +fi + case "$use_strategies" in '') - case "$#" in - 1) - var="`git config --get pull.twohead`" + case "$real_parents" in + ?*" "?*) + var="`git config --get pull.octopus`" if test -n "$var" then use_strategies="$var" else - use_strategies="$default_twohead_strategies" + use_strategies="$default_octopus_strategies" fi ;; *) - var="`git config --get pull.octopus`" + var="`git config --get pull.twohead`" if test -n "$var" then use_strategies="$var" else - use_strategies="$default_octopus_strategies" + use_strategies="$default_twohead_strategies" fi ;; esac ;; @@ -303,7 +452,7 @@ do do case " $s " in *" $ss "*) - allow_fast_forward=f + fast_forward=never break ;; esac @@ -318,88 +467,73 @@ do esac done done - -case "$#" in -1) - common=$(git merge-base --all $head "$@") - ;; -*) - common=$(git show-branch --merge-base $head "$@") - ;; -esac -echo "$head" >"$GIT_DIR/ORIG_HEAD" - -case "$allow_fast_forward,$#,$common,$no_commit" in -?,*,'',*) - # No common ancestors found. We need a real merge. - ;; -?,1,"$1",*) - # If head can reach all the merge then we are up to date. - # but first the most common case of merging one remote. - finish_up_to_date "Already up-to-date." - exit 0 - ;; -t,1,"$head",*) - # Again the most common case of merging one remote. - echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $1)" - git update-index --refresh 2>/dev/null - msg="Fast forward" - if test -n "$have_message" +if test -z "$real_parents" +then + if test $head = $ff_head then - msg="$msg (no commit created; -m option ignored)" - fi - new_head=$(git rev-parse --verify "$1^0") && - git read-tree -v -m -u --exclude-per-directory=.gitignore $head "$new_head" && - finish "$new_head" "$msg" || exit - dropsave - exit 0 - ;; -?,1,?*"$LF"?*,*) - # We are not doing octopus and not fast forward. Need a - # real merge. - ;; -?,1,*,) - # We are not doing octopus, not fast forward, and have only - # one common. - git update-index --refresh 2>/dev/null - case "$allow_trivial_merge" in - t) - # See if it is really trivial. - git var GIT_COMMITTER_IDENT >/dev/null || exit - echo "Trying really trivial in-index merge..." - if git read-tree --trivial -m -u -v $common $head "$1" && - result_tree=$(git write-tree) - then - echo "Wonderful." - result_commit=$( - printf '%s\n' "$merge_msg" | - git commit-tree $result_tree -p HEAD -p "$1" - ) || exit - finish "$result_commit" "In-index merge" - dropsave - exit 0 - fi - echo "Nope." - esac - ;; -*) - # An octopus. If we can reach all the remote we are up to date. - up_to_date=t - for remote - do - common_one=$(git merge-base --all $head $remote) - if test "$common_one" != "$remote" + finish_up_to_date "Already up-to-date." + exit 0 + elif test $fast_forward = never + then + real_parents="$ff_head" + ff_head=$head + else + echo "Updating $(git rev-parse --short $head)..$(git rev-parse --short $ff_head)" + git update-index --refresh 2>/dev/null + msg="Fast forward" + if test -n "$have_message" then - up_to_date=f - break + msg="$msg (no commit created; -m option ignored)" fi - done - if test "$up_to_date" = t - then - finish_up_to_date "Already up-to-date. Yeeah!" + new_head=$(git rev-parse --verify "$ff_head^0") && + git read-tree -v -m -u --exclude-per-directory=.gitignore $head "$new_head" && + finish "$new_head" "$msg" || exit + dropsave exit 0 fi +else + if test $head != $ff_head -a $fast_forward = never + then + real_parents="$ff_head $real_parents" + ff_head=$head + fi +fi + +case "$real_parents" in +?*" "?*) + # We have more than one parent + common=$(git show-branch --merge-base $head $real_parents) ;; +*) + # We have exactly one parent + common=$(git merge-base --all $ff_head $real_parents) + case "$common" in + ?*"$LF"?*) + # We are not doing octopus and not fast forward. Need a + # real merge. + ;; + *) + git update-index --refresh 2>/dev/null + if test "$allow_trivial_merge" = t + then + # See if it is really trivial. + git var GIT_COMMITTER_IDENT >/dev/null || exit + echo "Trying really trivial in-index merge..." + if git read-tree --trivial -m -u -v $common $head $real_parents && + result_tree=$(git write-tree) + then + echo "Wonderful." + result_commit=$( + printf '%s\n' "$merge_msg" | + git commit-tree $result_tree -p HEAD -p $real_parents + ) || exit + finish "$result_commit" "In-index merge" + dropsave + exit 0 + fi + echo "Nope." + fi ;; + esac ;; esac # We are going to make a new commit. @@ -440,7 +574,7 @@ do # Remember which strategy left the state in the working tree wt_strategy=$strategy - git-merge-$strategy $common -- "$head_arg" "$@" + git-merge-$strategy $common -- "$head_arg" $real_parents exit=$? if test "$no_commit" = t && test "$exit" = 0 then @@ -476,11 +610,11 @@ done # auto resolved the merge cleanly. if test '' != "$result_tree" then - if test "$allow_fast_forward" = "t" + if test $fast_forward != never then - parents=$(git show-branch --independent "$head" "$@") + parents=$(git show-branch --independent "$head" $real_parents) else - parents=$(git rev-parse "$head" "$@") + parents=$(git rev-parse "$head" $real_parents) fi parents=$(echo "$parents" | sed -e 's/^/-p /') result_commit=$(printf '%s\n' "$merge_msg" | git commit-tree $result_tree $parents) || exit @@ -510,7 +644,7 @@ case "$best_strategy" in echo "Rewinding the tree to pristine..." restorestate echo "Using the $best_strategy to prepare resolving by hand." - git-merge-$best_strategy $common -- "$head_arg" "$@" + git-merge-$best_strategy $common -- "$head_arg" $real_parents ;; esac diff --git a/git-pull.sh b/git-pull.sh index 46da0f4..6cbb0fe 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -4,7 +4,7 @@ # # Fetch one or more remote refs and merge it/them into the current HEAD. -USAGE='[-n | --no-summary] [--[no-]commit] [--[no-]squash] [--[no-]ff] [-s strategy]... [<fetch-options>] <repo> <head>...' +USAGE='[-n | --no-summary] [--[no-]commit] [--[no-]squash] [--ff=<ff-strategy>] [-s strategy]... [<fetch-options>] <repo> <head>...' LONG_USAGE='Fetch one or more remote refs and merge it/them into the current HEAD.' SUBDIRECTORY_OK=Yes OPTIONS_SPEC= @@ -41,6 +41,8 @@ do no_ff=--ff ;; --no-ff) no_ff=--no-ff ;; + --ff=*) + no_ff=$1 ;; -s=*|--s=*|--st=*|--str=*|--stra=*|--strat=*|--strate=*|\ --strateg=*|--strategy=*|\ -s|--s|--st|--str|--stra|--strat|--strate|--strateg|--strategy) diff --git a/t/t7601-merge-ff-strategies.sh b/t/t7601-merge-ff-strategies.sh new file mode 100755 index 0000000..28fca88 --- /dev/null +++ b/t/t7601-merge-ff-strategies.sh @@ -0,0 +1,870 @@ +#!/bin/sh +# +# Copyright (c) 2007 Lars Hjemli +# + +test_description='git-merge + +Testing basic merge operations/option parsing.' + +. ./test-lib.sh + +cat >file <<EOF +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +EOF + +cat >file.1 <<EOF +1 X +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +EOF + +cat >file.5 <<EOF +1 +2 +3 +4 +5 X +6 +7 +8 +9 +10 +11 +12 +EOF + +cat >file.9 <<EOF +1 +2 +3 +4 +5 +6 +7 +8 +9 X +10 +11 +12 +EOF + +cat >result.0 <<EOF +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +EOF + +cat >result.1 <<EOF +1 X +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +EOF + +cat >result.1-5 <<EOF +1 X +2 +3 +4 +5 X +6 +7 +8 +9 +10 +11 +12 +EOF + +cat >result.1-5-9 <<EOF +1 X +2 +3 +4 +5 X +6 +7 +8 +9 X +10 +11 +12 +EOF + +cat >result.1-5-9-13 <<EOF +1 X +2 +3 +4 +5 X +6 +7 +8 +9 X +10 +11 +12 +13 x +EOF + +cat >result.1-5-13 <<EOF +1 X +2 +3 +4 +5 X +6 +7 +8 +9 +10 +11 +12 +13 x +EOF + +cat >result.5-13 <<EOF +1 +2 +3 +4 +5 X +6 +7 +8 +9 +10 +11 +12 +13 x +EOF + +cat >result.1-13 <<EOF +1 X +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 x +EOF + +cat >extend <<EOF +13 x +EOF + + +create_merge_msgs() { + echo "Merge commit 'c2'" >msg.1-5 && + echo "Merge commit 'c2'; commit 'c3'" >msg.1-5-9 && + echo "Squashed commit of the following:" >squash.1 && + echo >>squash.1 && + git log --no-merges ^HEAD c1 >>squash.1 && + echo "Squashed commit of the following:" >squash.1-5 && + echo >>squash.1-5 && + git log --no-merges ^HEAD c2 >>squash.1-5 && + echo "Squashed commit of the following:" >squash.1-5-9 && + echo >>squash.1-5-9 && + git log --no-merges ^HEAD c2 c3 >>squash.1-5-9 +} + +verify_diff() { + if ! diff -u "$1" "$2" + then + echo "$3" + false + fi +} + +verify_merge() { + verify_diff "$2" "$1" "[OOPS] bad merge result" && + if test $(git ls-files -u | wc -l) -gt 0 + then + echo "[OOPS] unmerged files" + false + fi && + if ! git diff --exit-code + then + echo "[OOPS] working tree != index" + false + fi && + if test -n "$3" + then + git show -s --pretty=format:%s HEAD >msg.act && + verify_diff "$3" msg.act "[OOPS] bad merge message" + fi +} + +verify_head() { + if test "$1" != "$(git rev-parse HEAD)" + then + echo "[OOPS] HEAD != $1" + false + fi +} + +verify_parents() { + i=1 + while test $# -gt 0 + do + if test "$1" != "$(git rev-parse HEAD^$i)" + then + echo "[OOPS] HEAD^$i != $1" + return 1 + fi + i=$(expr $i + 1) + shift + done +} + +verify_mergeheads() { + i=1 + if ! test -f .git/MERGE_HEAD + then + echo "[OOPS] MERGE_HEAD is missing" + false + fi && + while test $# -gt 0 + do + head=$(head -n $i .git/MERGE_HEAD | tail -n 1) + if test "$1" != "$head" + then + echo "[OOPS] MERGE_HEAD $i != $1" + return 1 + fi + i=$(expr $i + 1) + shift + done +} + +verify_no_mergehead() { + if test -f .git/MERGE_HEAD + then + echo "[OOPS] MERGE_HEAD exists" + false + fi +} + + +test_expect_success 'setup' ' + git add file && + test_tick && + git commit -m "commit 0" && + git tag c0 && + c0=$(git rev-parse HEAD) && + + cp file.1 file && + git add file && + test_tick && + git commit -m "commit 1" && + git tag c1 && + c1=$(git rev-parse HEAD) && + test_tick && + + git reset --hard "$c0" && + cp file.5 file && + git add file && + git commit -m "commit 2" && + test_tick && + git tag c2 && + c2=$(git rev-parse HEAD) && + + git reset --hard "$c0" && + cp file.9 file && + git add file && + test_tick && + git commit -m "commit 3" && + git tag c3 && + c3=$(git rev-parse HEAD) && + test_tick && + + git reset --hard "$c1" && + cat extend >>file && + git add file && + git commit -m "commit 4" && + git tag x1 && + x1=$(git rev-parse HEAD) && + test_tick && + + git reset --hard "$c1" && + git merge "$c2" && + git tag x0 && + x0=$(git rev-parse HEAD) && + test_tick && + + git reset --hard "$c2" && + cat extend >>file && + git add file && + git commit -m "commit 5" && + git tag x2 && + x2=$(git rev-parse HEAD) && + test_tick && + + git reset --hard "$x1" && + git merge "$x0" && + git tag y1 && + y1=$(git rev-parse HEAD) && + test_tick && + + git reset --hard "$x0" && + git merge "$x2" && + git tag y2 && + y2=$(git rev-parse HEAD) && + test_tick && + + git reset --hard "$y1" && + git merge "$y2" && + git tag y3 && + y3=$(git rev-parse HEAD) && + test_tick && + git reset --hard "$c0" && + create_merge_msgs && + + git reset --hard x1 && + git clone .git clone && + git config remote.clone.url clone && + git config remote.clone.fetch "+refs/heads/*:refs/remotes/clone/*" && + + (mkdir new && cd new && git init && cp ../file.9 file2 && git add file2 && test_tick && git commit -m "commit new") && + git config remote.new.url new && + git config remote.new.fetch "+refs/heads/*:refs/remotes/new/*" +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with c1 (ff-only overrides no-ff)' ' + git reset --hard c0 && + git config branch.master.mergeoptions "--no-ff" && + git merge --ff=only c1 && + verify_merge file result.1 && + verify_head $c1 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with c1 (--ff=only in config)' ' + git reset --hard c0 && + git config branch.master.mergeoptions "--ff=only" && + git merge c1 && + test_tick && + verify_merge file result.1 && + verify_head $c1 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with c0 (--ff=only in config)' ' + git reset --hard c1 && + git config branch.master.mergeoptions "--ff=only" && + git merge c0 && + verify_merge file result.1 && + verify_head $c1 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with c2 (--ff=only in config)' ' + git reset --hard c1 && + test_tick && + git config branch.master.mergeoptions "--ff=only" && + if git merge c2 + then + false + else + verify_merge file result.1 && + verify_head $c1 + fi +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with c1 (--ff=only)' ' + git reset --hard c0 && + test_tick && + git merge --ff=only c1 && + verify_merge file result.1 && + verify_head $c1 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with c0 (--ff=only)' ' + git reset --hard c1 && + test_tick && + git merge --ff=only c0 && + verify_merge file result.1 && + verify_head $c1 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with c1 and c2 (--ff=only)' ' + git reset --hard c0 && + if git --ff=only merge c1 c2 + then + false + else + verify_merge file result.0 && + verify_head $c0 + fi +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with c0 (--ff=only)' ' + git reset --hard c1 && + test_tick && + git merge --ff=only c0 && + verify_merge file result.1 && + verify_head $c1 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with c2 (--ff=only overrides no-ff)' ' + git reset --hard c1 && + git config branch.master.mergeoptions "--no-ff" && + test_tick && + if git merge c2 --ff=only + then + false + else + verify_merge file result.1 && + verify_head $c1 + fi +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with c1 (no-ff overrides --ff=only)' ' + git reset --hard c0 && + git config branch.master.mergeoptions "--ff=only" && + test_tick && + git merge --no-ff c1 && + verify_merge file result.1 && + verify_parents $c0 $c1 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with c2 (ff owerrides --ff=only)' ' + git reset --hard c1 && + git config branch.master.mergeoptions "--ff=only" && + test_tick && + git merge --ff c2 && + verify_merge file result.1-5 && + verify_parents $c1 $c2 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with c1 and c2' ' + git reset --hard c0 && + git config branch.master.mergeoptions "" && + test_tick && + git merge c1 c2 && + verify_merge file result.1-5 && + verify_parents $c1 $c2 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with c0, c2, c0, and c1' ' + git reset --hard c1 && + git config branch.master.mergeoptions "" && + test_tick && + git merge c0 c2 c0 c1 && + verify_merge file result.1-5 && + verify_parents $c1 $c2 +' + +test_debug 'gitk --all' + +test_expect_success 'merge y2 with x0, c3, and c0' ' + git reset --hard y2 && + git config branch.master.mergeoptions "" && + test_tick && + git merge x0 c3 c0 && + verify_merge file result.1-5-9-13 && + verify_parents $y2 $c3 +' + +test_debug 'gitk --all' + +test_expect_success 'merge x0 with y2, c3, and c0' ' + git reset --hard x0 && + git config branch.master.mergeoptions "" && + test_tick && + git merge y2 c3 c0 && + verify_merge file result.1-5-9-13 && + verify_parents $y2 $c3 +' + +test_debug 'gitk --all' + +test_expect_success 'merge y2 with x0, c3, and c0 (--ff=path)' ' + git reset --hard y2 && + git config branch.master.mergeoptions "" && + test_tick && + git merge --ff=path x0 c3 c0 && + verify_merge file result.1-5-13 && + verify_head $y2 +' + +test_debug 'gitk --all' + +test_expect_success 'merge x0 with y2, c3, and c0 (--ff=path)' ' + git reset --hard x0 && + git config branch.master.mergeoptions "" && + test_tick && + git merge --ff=path y2 c3 c0 && + verify_merge file result.1-5-13 && + verify_head $y2 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with x0, c3, and c0 (--ff=path)' ' + git reset --hard c0 && + git config branch.master.mergeoptions "" && + test_tick && + git merge --ff=path x0 c3 c0 && + verify_merge file result.1-5 && + verify_head $x0 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with y2, c3, and c0 (--ff=path)' ' + git reset --hard c0 && + git config branch.master.mergeoptions "" && + test_tick && + git merge --ff=path y2 c3 c0 && + verify_merge file result.1-5-13 && + verify_head $y2 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with x0, y3, and c0 (--ff=path)' ' + git reset --hard c0 && + git config branch.master.mergeoptions "" && + test_tick && + git merge --ff=path x0 y3 c0 && + verify_merge file result.1-5-13 && + verify_head $y3 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with x0, c3, and y3 (--ff=path)' ' + git reset --hard c0 && + git config branch.master.mergeoptions "" && + test_tick && + git merge --ff=path x0 c3 y3 && + verify_merge file result.1-5-13 && + verify_head $y3 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with y1, y2, and y3 (--ff=path)' ' + git reset --hard c0 && + git config branch.master.mergeoptions "" && + test_tick && + git merge --ff=path y1 y2 y3 && + verify_merge file result.1-5-13 && + verify_head $y3 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with y1 (--ff=common)' ' + git reset --hard c0 && + git config branch.master.mergeoptions "" && + test_tick && + git merge --ff=common y1 && + verify_merge file result.1-5-13 && + verify_head $y1 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with y1, y2, and y3 (--ff=common)' ' + git reset --hard c0 && + git config branch.master.mergeoptions "" && + test_tick && + git merge --ff=common y1 y2 y3 && + verify_merge file result.1-5 && + verify_head $x0 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with c1 and c2 (--ff=common)' ' + git reset --hard c0 && + git config branch.master.mergeoptions "" && + test_tick && + git merge --ff=common c1 c2 && + verify_merge file result.0 && + verify_head $c0; +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with y1 and y2 (--ff=common)' ' + git reset --hard c0 && + git config branch.master.mergeoptions "" && + test_tick && + git merge --ff=common y1 y2 && + verify_merge file result.1-5 && + verify_head $x0; +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with y1 and y2 (--ff=common)' ' + git reset --hard c1 && + git config branch.master.mergeoptions "" && + test_tick && + git merge --ff=common y1 y2 && + verify_merge file result.1-5 && + verify_head $x0; +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with y1 and y2 c0 (--ff=common)' ' + git reset --hard c1 && + git config branch.master.mergeoptions "" && + test_tick && + if git merge --ff=common y1 y2 c0 + then + false + else + verify_merge file result.1 && + verify_head $c1; + fi +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with x1 and c2 (--ff=common)' ' + git reset --hard c1 && + git config branch.master.mergeoptions "" && + test_tick && + if git merge --ff=common x1 c2 + then + false + else + verify_merge file result.1 && + verify_head $c1 + fi +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with x0 (--ff=fork)' ' + git reset --hard c0 && + git config branch.master.mergeoptions "" && + test_tick && + git merge --ff=fork x0 && + verify_merge file result.1-5 && + verify_head $x0 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with y1, y2, and y3 (--ff=fork)' ' + git reset --hard c0 && + git config branch.master.mergeoptions "" && + test_tick && + git merge --ff=fork y1 y2 y3 && + verify_merge file result.1-5-13 && + verify_head $y3 +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with c1 and c2 (--ff=fork)' ' + git reset --hard c0 && + git config branch.master.mergeoptions "" && + test_tick && + git merge --ff=fork c1 c2 && + verify_merge file result.0 && + verify_head $c0; +' + +test_debug 'gitk --all' + +test_expect_success 'merge c0 with y1 and y2 (--ff=fork)' ' + git reset --hard c0 && + git config branch.master.mergeoptions "" && + test_tick && + git merge --ff=fork y1 y2 && + verify_merge file result.1-5 && + verify_head $x0; +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with y1 and y2 (--ff=fork)' ' + git reset --hard c1 && + git config branch.master.mergeoptions "" && + test_tick && + git merge --ff=fork y1 y2 && + verify_merge file result.1-5 && + verify_head $x0; +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with y1 and y2 c0 (--ff=fork)' ' + git reset --hard c1 && + git config branch.master.mergeoptions "" && + test_tick && + git merge --ff=fork y1 y2 c0 && + verify_merge file result.1-5 && + verify_head $x0; +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with x1 and c2 (--ff=fork)' ' + git reset --hard c1 && + git config branch.master.mergeoptions "" && + test_tick && + if git merge --ff=fork x1 c2 + then + false + else + verify_merge file result.1 && + verify_head $c1 + fi +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with x1 (pull --ff=only)' ' + git reset --hard c1 && + test_tick && + git pull --ff=only clone refs/heads/master && + verify_merge file result.1-13 && + verify_head $x1 +' + +test_debug 'gitk --all' + +test_expect_success 'merge x2 with x1 (pull --ff=only)' ' + git reset --hard x2 && + test_tick && + if git pull --ff=only clone refs/heads/master + then + false + else + verify_merge file result.5-13 && + verify_head $x2 + fi +' + +test_debug 'gitk --all' + + + +test_expect_success 'merge c1 with new repository (pull --ff=only)' ' + git reset --hard c1 && + test_tick && + if git pull --ff=only new refs/heads/master + then + false + else + verify_merge file result.1 && + verify_head $c1 + fi +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with new repository (pull --ff=common)' ' + git reset --hard c1 && + test_tick && + if git pull --ff=common new refs/heads/master + then + false + else + verify_merge file result.1 && + verify_head $c1 + fi +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with new repository (pull --ff=fork)' ' + git reset --hard c1 && + test_tick && + if git pull --ff=fork new refs/heads/master + then + false + else + verify_merge file result.1 && + verify_head $c1 + fi +' + +test_debug 'gitk --all' + +test_expect_success 'merge c1 with new repository (pull --ff=path)' ' + git reset --hard c1 && + test_tick && + git pull --ff=path new refs/heads/master && + verify_merge file result.1 && + verify_head $c1 +' + +test_debug 'gitk --all' + +test_done -- 1.5.3.3