[RFC/PATCH] Merge strategy single

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



A patch for this feature is attached.  I intend to write more tests
for this patch next weekend.  Any comments are welcome.

-- 
Sverre Hvammen Johansen
From 1283f5ea17b493a9224fa7a65bb233962bc9ea41 Mon Sep 17 00:00:00 2001
From: Sverre Hvammen Johansen <sj@xxxxxxxxxxx>
Date: Tue, 22 Jan 2008 00:29:37 -0800
Subject: [PATCH] Merge strategy single

A new merge strategy, single is introduces.  This merge
strategy 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.

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.

Known issues:  More tests are needed.  Add documentation.
Better handling of fast forward of HEAD when doing real merge.

Signed-off-by: Sverre Hvammen Johansen <hvammen@xxxxxxxxx>
---
 git-merge.sh     |  238 ++++++++++++++++++++++++++++++++++++------------------
 t/t7600-merge.sh |  172 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 332 insertions(+), 78 deletions(-)

diff --git a/git-merge.sh b/git-merge.sh
index 1c123a3..57f2f00 100755
--- a/git-merge.sh
+++ b/git-merge.sh
@@ -28,7 +28,7 @@ test -z "$(git ls-files -u)" ||
 LF='
 '
 
-all_strategies='recur recursive octopus resolve stupid ours subtree'
+all_strategies='single recur recursive octopus resolve stupid ours subtree'
 default_twohead_strategies='recursive'
 default_octopus_strategies='octopus'
 no_fast_forward_strategies='subtree ours'
@@ -167,7 +167,12 @@ parse_config () {
 			shift
 			case " $all_strategies " in
 			*" $1 "*)
-				use_strategies="$use_strategies$1 " ;;
+				if test "$use_strategies" = single -o "$1" = single -o -z "$use_strategies"
+				then
+					use_strategies="$1"
+				else
+					use_strategies="$use_strategies $1"
+				fi ;;
 			*)
 				die "available strategies are: $all_strategies" ;;
 			esac
@@ -274,24 +279,113 @@ 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)"
+		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)."
+			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)."
+				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 $first_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 independt
+	# 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
+}
+
+find_real_parents "$@"
+
+if test -n "$real_parents" -a "$use_strategies" != single -a $head != $ff_head
+then
+	# We currently don't handle ff_head
+	if test -n "$real_parents"
+	then
+		real_parents="$ff_head $real_parents"
+	else
+		real_parents=$ff_head
+	fi	
+	ff_head=$head
+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
 	;;
@@ -319,87 +413,75 @@ do
 	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.
+if test -n "$real_parents"
+then
+	if test "$use_strategies" = single
+	then
+		die "Merge strategy single can not handle more than one real parent"
+	fi
+elif test $head = $ff_head
+then
 	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)"
+elif test $allow_fast_forward = t
+then
+	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
 		msg="$msg (no commit created; -m option ignored)"
 	fi
-	new_head=$(git rev-parse --verify "$1^0") &&
+	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
-	;;
-?,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"
-		then
-			up_to_date=f
-			break
-		fi
-	done
-	if test "$up_to_date" = t
+else
+	if test "$use_strategies" = "single"
 	then
-		finish_up_to_date "Already up-to-date. Yeeah!"
-		exit 0
+		die "Merge strategy single can not fast forward when --no-ff is specified"
+	else
+		real_parents=$ff_head
+		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 +522,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
@@ -478,9 +560,9 @@ if test '' != "$result_tree"
 then
     if test "$allow_fast_forward" = "t"
     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 +592,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/t/t7600-merge.sh b/t/t7600-merge.sh
index 50c51c8..fe01941 100755
--- a/t/t7600-merge.sh
+++ b/t/t7600-merge.sh
@@ -57,6 +57,18 @@ cat >file.9 <<EOF
 9 X
 EOF
 
+cat  >result.0 <<EOF
+1
+2
+3
+4
+5
+6
+7
+8
+9
+EOF
+
 cat  >result.1 <<EOF
 1 X
 2
@@ -437,4 +449,164 @@ test_expect_success 'merge c0 with c1 (ff overrides no-ff)' '
 
 test_debug 'gitk --all'
 
+test_expect_success 'merge c0 with c1 (-s single in config)' '
+	git reset --hard c0 &&
+	git config branch.master.mergeoptions "-s single" &&
+	git merge c1 &&
+	test_tick &&
+	verify_merge file result.1 &&
+	verify_head $c1
+'
+
+test_debug 'gitk --all'
+
+test_expect_success 'merge c1 with c0 (--strategy=single in config)' '
+	git reset --hard c1 &&
+	git config branch.master.mergeoptions "--strategy single" &&
+	git merge c0 &&
+	verify_merge file result.1 &&
+	verify_head $c1
+'
+
+test_debug 'gitk --all'
+
+test_expect_success 'merge c1 with c2 (--strategy=single in config)' '
+	git reset --hard c1 &&
+	test_tick &&
+	git config branch.master.mergeoptions "--strategy single" &&
+	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 (strategy=single)' '
+	git reset --hard c0 &&
+	test_tick &&
+	git merge c1 --strategy=single &&
+	verify_merge file result.1 &&
+	verify_head $c1
+'
+
+test_debug 'gitk --all'
+
+test_expect_success 'merge c1 with c0 (strategy single)' '
+	git reset --hard c1 &&
+	test_tick &&
+	git merge c0 --strategy single &&
+	verify_merge file result.1 &&
+	verify_head $c1
+'
+
+test_debug 'gitk --all'
+
+test_expect_success 'merge c1 with c2 (-s single)' '
+	git reset --hard c1 &&
+	test_tick &&
+	if git merge c2 -s single
+	then
+		false
+	else
+		verify_merge file result.1 &&
+		verify_head $c1
+	fi
+'
+
+test_debug 'gitk --all'
+
+test_expect_success 'merge c0 with c1 and c2 (-s single)' '
+	git reset --hard c0 &&
+	if git merge c1 c2 -s single
+	then
+		false
+	else
+		verify_merge file result.0 &&
+		verify_head $c0
+	fi
+'
+
+test_debug 'gitk --all'
+
+test_expect_success 'merge c1 with c0 (-s single and no-ff)' '
+	git reset --hard c1 &&
+	test_tick &&
+	git merge -s single --no-ff c0 &&
+	verify_merge file result.1 &&
+	verify_head $c1
+'
+
+test_debug 'gitk --all'
+
+test_expect_success 'merge c1 with c2 (--strategy=single and no-ff)' '
+	git reset --hard c1 &&
+	git config branch.master.mergeoptions "--no-ff" &&
+	test_tick &&
+	if git merge c2 --strategy=single
+	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 and -s single)' '
+	git reset --hard c0 &&
+	git config branch.master.mergeoptions "-s single" &&
+	test_tick &&
+	if git merge --no-ff c1
+	then
+		false
+	else
+		verify_merge file result.0 &&
+		verify_head $c0
+	fi
+'
+
+test_debug 'gitk --all'
+
+test_expect_success 'merge c1 with c2 (ff and -s single)' '
+	git reset --hard c1 &&
+	git config branch.master.mergeoptions "-s single" &&
+	test_tick &&
+	if git merge --ff c2
+	then
+		false
+	else
+		verify_merge file result.1 &&
+		verify_head $c1
+	fi
+'
+
+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_done
-- 
1.5.3.3


[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux