git.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [RFC/PATCH] Fast forward strategies only, common, fork and path
@ 2008-02-04  0:54 Sverre Hvammen Johansen
  2008-02-04  4:49 ` Stefan (metze) Metzmacher
                   ` (3 more replies)
  0 siblings, 4 replies; 14+ messages in thread
From: Sverre Hvammen Johansen @ 2008-02-04  0:54 UTC (permalink / raw)
  To: git

[-- Attachment #1: Type: text/plain, Size: 28 bytes --]

-- 
Sverre Hvammen Johansen

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Fast-forward-strategies-only-common-fork-and-path.patch --]
[-- Type: text/x-patch; name=0001-Fast-forward-strategies-only-common-fork-and-path.patch, Size: 33147 bytes --]

From 5a156f0719f03fbeb4fffb7a22847739dd47ce2a Mon Sep 17 00:00:00 2001
From: Sverre Hvammen Johansen <sj@black.local>
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@gmail.com>
---
 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


^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
  2008-02-04  0:54 [RFC/PATCH] Fast forward strategies only, common, fork and path Sverre Hvammen Johansen
@ 2008-02-04  4:49 ` Stefan (metze) Metzmacher
  2008-02-04  6:51 ` Sverre Hvammen Johansen
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 14+ messages in thread
From: Stefan (metze) Metzmacher @ 2008-02-04  4:49 UTC (permalink / raw)
  To: Sverre Hvammen Johansen; +Cc: git

[-- Attachment #1: Type: text/plain, Size: 256 bytes --]

Hi Sverre,

> New fast forward strategies, only, common, fork, and path is introduced.
> These new fast forward strategies allows additional work flows.

I really like that concept! I was always looking for a way to force
a fast-forward.

metze


[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 249 bytes --]

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
  2008-02-04  0:54 [RFC/PATCH] Fast forward strategies only, common, fork and path Sverre Hvammen Johansen
  2008-02-04  4:49 ` Stefan (metze) Metzmacher
@ 2008-02-04  6:51 ` Sverre Hvammen Johansen
  2008-02-04  7:24   ` Junio C Hamano
  2008-02-04  7:13 ` Sverre Hvammen Johansen
  2008-02-04  7:19 ` Sverre Hvammen Johansen
  3 siblings, 1 reply; 14+ messages in thread
From: Sverre Hvammen Johansen @ 2008-02-04  6:51 UTC (permalink / raw)
  To: git

On Feb 3, 2008 4:54 PM, Sverre Hvammen Johansen <hvammen@gmail.com> wrote:
> 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.

I intend to also submit a patch that does fast forward in combination
with a real merge.  This means that some case can be reduced down to
only two real parents and we can select a  twohead strategy instead of
an octopus strategy.  In cases where we have at least three real
parents there is no point in doing this.  We only need to specify the
fast forwarded head right after head to allow git-merge-octopus to do
its best.

I need some advise how to implement fast-forward in combination with a
real merge.  I know how to do this with an update of the directory
tree and the index with the fast forward before the real merge is
done.  But, how canweI do this without updating the directory tree
with the fast forward?  Or is it OK to always update the directory
with the intermediate state we get from the fast forward?

> --
> Sverre Hvammen Johansen
>



-- 
Sverre Hvammen Johansen

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
  2008-02-04  0:54 [RFC/PATCH] Fast forward strategies only, common, fork and path Sverre Hvammen Johansen
  2008-02-04  4:49 ` Stefan (metze) Metzmacher
  2008-02-04  6:51 ` Sverre Hvammen Johansen
@ 2008-02-04  7:13 ` Sverre Hvammen Johansen
  2008-02-04  7:31   ` Junio C Hamano
  2008-02-04  7:19 ` Sverre Hvammen Johansen
  3 siblings, 1 reply; 14+ messages in thread
From: Sverre Hvammen Johansen @ 2008-02-04  7:13 UTC (permalink / raw)
  To: git

git-pull only accepts one repository.  With this patch it makes sense
to accept more than one repository.  I would like to rewrite git-pull
to accept more than one repository.  This might break compatibility
with existing git-pull.  One solution could be to introduce a new
command that does the same as git-pull and more.  What about naming
such a command git-update and deprecate git-pull.

I believe such an command should not specify the repository and the
refspec but should instead specify the remote tracking branch or a
local branch if descired.  I believe this would be more natural to use
and just as easier to implement.

-- 
Sverre Hvammen Johansen

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
  2008-02-04  0:54 [RFC/PATCH] Fast forward strategies only, common, fork and path Sverre Hvammen Johansen
                   ` (2 preceding siblings ...)
  2008-02-04  7:13 ` Sverre Hvammen Johansen
@ 2008-02-04  7:19 ` Sverre Hvammen Johansen
  3 siblings, 0 replies; 14+ messages in thread
From: Sverre Hvammen Johansen @ 2008-02-04  7:19 UTC (permalink / raw)
  To: git

On Feb 3, 2008 4:54 PM, Sverre Hvammen Johansen <hvammen@gmail.com> wrote:
> find_real_parents ()

We should probably implement this using "git show-branch --independent
<heads>".  Will this command do the same job (except showing which
branch is ff_head)?

-- 
Sverre Hvammen Johansen

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
  2008-02-04  6:51 ` Sverre Hvammen Johansen
@ 2008-02-04  7:24   ` Junio C Hamano
  2008-02-04  8:06     ` Sverre Hvammen Johansen
  0 siblings, 1 reply; 14+ messages in thread
From: Junio C Hamano @ 2008-02-04  7:24 UTC (permalink / raw)
  To: Sverre Hvammen Johansen; +Cc: git

"Sverre Hvammen Johansen" <hvammen@gmail.com> writes:

> I intend to also submit a patch that does fast forward in combination
> with a real merge.

Please make the next round an in-line patch.  Attachments cannot
be commented on, and an RFC patch is all about getting comments,
not about being included.  Whitespace breakages do not matter as
much as the final submissions; readability and commentability
matters more.

Instead of adding many new sub-strategies at once, I think it
would make it easier to review to split the patch into (1) code
movement without adding any functionality changes to make your
further changes easier, if such a change is needed in your work
(I did not really look at the attachment carefully), (2) add
logic to find out the set of independent parents to remove
redundant parents (perhaps using show-branch --independent? I
dunno) and conditionally use it, (3) add infrastructure to allow
adding different --ff=<what-to-do>, and then finally (4) a
separate patch for each of <what-to-do>.

I suspect (2) is controversial if made unconditional.  Some
people do not even like the fast-forward "merges" we have
traditionally done.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
  2008-02-04  7:13 ` Sverre Hvammen Johansen
@ 2008-02-04  7:31   ` Junio C Hamano
  2008-02-04  7:43     ` Sverre Hvammen Johansen
  0 siblings, 1 reply; 14+ messages in thread
From: Junio C Hamano @ 2008-02-04  7:31 UTC (permalink / raw)
  To: Sverre Hvammen Johansen; +Cc: git

"Sverre Hvammen Johansen" <hvammen@gmail.com> writes:

> git-pull only accepts one repository.  With this patch it makes sense
> to accept more than one repository.  I would like to rewrite git-pull
> to accept more than one repository.  This might break compatibility
> with existing git-pull.  One solution could be to introduce a new
> command that does the same as git-pull and more.  What about naming
> such a command git-update and deprecate git-pull.

Just add contrib/multi-pull/ directory and put your shell script
there, something like:

	#!/bin/sh
	append=
	for repo
        do
        	git fetch $append $repo
                append=--append
	done
	merge_name=$(git fmt-merge-msg <"$GIT_DIR/FETCH_HEAD")
        git merge -m "$merge_name" $(sed -e '/	not-for-merge	/d'
        		-e 's/	.*//' "$GIT_DIR/FETCH_HEAD")

If it turns out to be useful for many people, it may become part
of the main Porcelain.  It's too early to talk about touching
git-pull at all.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
  2008-02-04  7:31   ` Junio C Hamano
@ 2008-02-04  7:43     ` Sverre Hvammen Johansen
  0 siblings, 0 replies; 14+ messages in thread
From: Sverre Hvammen Johansen @ 2008-02-04  7:43 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

> Just add contrib/multi-pull/ directory and put your shell script
> there, something like: ...

Thanks will do.

> If it turns out to be useful for many people, it may become part
> of the main Porcelain.  It's too early to talk about touching
> git-pull at all.

I agree.

-- 
Sverre Hvammen Johansen

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
  2008-02-04  7:24   ` Junio C Hamano
@ 2008-02-04  8:06     ` Sverre Hvammen Johansen
  2008-02-04  8:22       ` Junio C Hamano
  0 siblings, 1 reply; 14+ messages in thread
From: Sverre Hvammen Johansen @ 2008-02-04  8:06 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Feb 3, 2008 11:24 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Please make the next round an in-line patch.  Attachments cannot
> be commented on, and an RFC patch is all about getting comments,
> not about being included.  Whitespace breakages do not matter as
> much as the final submissions; readability and commentability
> matters more.

I will post an update in a few days, with a few bug-fixes.

> Instead of adding many new sub-strategies at once, I think it
> would make it easier to review to split the patch into (1) code
> movement without adding any functionality changes to make your
> further changes easier, if such a change is needed in your work
> (I did not really look at the attachment carefully), (2) add
> logic to find out the set of independent parents to remove
> redundant parents (perhaps using show-branch --independent? I
> dunno) and conditionally use it, (3) add infrastructure to allow
> adding different --ff=<what-to-do>, and then finally (4) a
> separate patch for each of <what-to-do>.

The patch is not easy to read for git-merge.sh.  You really need to
apply the patch and then review the code.  If I follow your suggestion
above it might be easier to read the patches.  I will do if tthere is
a demand for a split.  However, it might take some time.  What is the
time-frame for inclusion in 1.5.5?

> I suspect (2) is controversial if made unconditional.  Some
> people do not even like the fast-forward "merges" we have
> traditionally done.

--ff=never will turn this off together with fast forward.  Maybe we
should have --ff=traditional that is the old behavior.

-- 
Sverre Hvammen Johansen

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
  2008-02-04  8:06     ` Sverre Hvammen Johansen
@ 2008-02-04  8:22       ` Junio C Hamano
  2008-02-05  7:32         ` Sverre Hvammen Johansen
  0 siblings, 1 reply; 14+ messages in thread
From: Junio C Hamano @ 2008-02-04  8:22 UTC (permalink / raw)
  To: Sverre Hvammen Johansen; +Cc: git

"Sverre Hvammen Johansen" <hvammen@gmail.com> writes:

> ...  However, it might take some time.  What is the
> time-frame for inclusion in 1.5.5?

The 1.5.4 cycle was too long (5 months).  A regular interval
ought to be about 3 months but I'd really like to keep 1.5.5
focused on obvious and unanimously supported changes that do not
impact the existing semantics deeply, and keep it shorter than
that.

I think it is too early to tell if this topic falls into that
category.  The patch is only a few day old, and there has been
no real discussion nor comments on it yet.  I also felt that the
backstory was lacking and it would be hard to judge for people
other than yourself how useful the new ff substrategies are in
what context.

The documentation updates talked about what the options do, but
it was unclear why they could be useful in what situations and
workflows.  At least it was not apparent to me on my cursory
read.

> --ff=never will turn this off together with fast forward.  Maybe we
> should have --ff=traditional that is the old behavior.

Sure, and I mildly suspect that it should be the default.

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
  2008-02-04  8:22       ` Junio C Hamano
@ 2008-02-05  7:32         ` Sverre Hvammen Johansen
  2008-02-05  9:34           ` Jakub Narebski
  2008-02-05  9:40           ` Junio C Hamano
  0 siblings, 2 replies; 14+ messages in thread
From: Sverre Hvammen Johansen @ 2008-02-05  7:32 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Feb 4, 2008 12:22 AM, Junio C Hamano <gitster@pobox.com> wrote:
>
> The documentation updates talked about what the options do, but
> it was unclear why they could be useful in what situations and
> workflows.  At least it was not apparent to me on my cursory
> read.

Common, fork, and path only make sense where there are at least three
repositories or two plus an observer involved.

Lets explain the observer cases.

The observer is interested in changes that X, Y and Z agree upon.  He
can merge as follows:

  git merge --ff=common X Y Z

The observer is interested in changes up to the point where someone is
known to disagree.  He can merge as follows:

   git merge --ff=fork X Y Z

 The observer is interested in any give path up to one of the true
parents.  He can merge as follows:

  git merge --ff=path X Y Z

This will give priority to X then Y.

This + only are all the interesting cases for fast forward.  Some work
flows between more than two repositories in the general case would
require additional features for rebase:  Rebase on any patch, the fork
point, or common ancestor of the remote branches.  This is something I
would like to discuss at some later time.

> > --ff=never will turn this off together with fast forward.  Maybe we
> > should have --ff=traditional that is the old behavior.
>
> Sure, and I mildly suspect that it should be the default.

I would argue that it should not be the default, simply because we
already use the real parents when only two branches are involved, This
is most convenient for most users.   Exactly the same argument holds
when there are more than two branches involved.  The history may be
simplified in a similar way.   Most projects would however not care
simply because they never merge more than two branches.   It is quite
common to have one topic branch that is based on the tip of another
topic branch and if those are merged back to the main branch you would
like it to be reduced down to two real heads (or even a fast forward),
exactly as one branch would result in a fast forward if it happened to
be based on the tip of master.

-- 
Sverre Hvammen Johansen

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
  2008-02-05  7:32         ` Sverre Hvammen Johansen
@ 2008-02-05  9:34           ` Jakub Narebski
  2008-02-05  9:40           ` Junio C Hamano
  1 sibling, 0 replies; 14+ messages in thread
From: Jakub Narebski @ 2008-02-05  9:34 UTC (permalink / raw)
  To: Sverre Hvammen Johansen; +Cc: Junio C Hamano, git

"Sverre Hvammen Johansen" <hvammen@gmail.com> writes:

> On Feb 4, 2008 12:22 AM, Junio C Hamano <gitster@pobox.com> wrote:
> >
> > The documentation updates talked about what the options do, but
> > it was unclear why they could be useful in what situations and
> > workflows.  At least it was not apparent to me on my cursory
> > read.
> 
> Common, fork, and path only make sense where there are at least three
> repositories or two plus an observer involved.
> 
> Lets explain the observer cases.
> 
> The observer is interested in changes that X, Y and Z agree upon.  He
> can merge as follows:
> 
>   git merge --ff=common X Y Z
> 
> The observer is interested in changes up to the point where someone is
> known to disagree.  He can merge as follows:
> 
>    git merge --ff=fork X Y Z
> 
>  The observer is interested in any give path up to one of the true
> parents.  He can merge as follows:
> 
>   git merge --ff=path X Y Z
> 
> This will give priority to X then Y.

Could you please provide ascii-art diagrams for above explanations
(above cases), such as the following diagrams for fast-forward, and
for forced merge (no fast-forward)? This would make your explanations
much easier to follow, I think.


1. Fast-forward ("traditional", 2 heads)

   before merge

   a---b---c---d               <-- main
                \
                 \-E---F---G  <-- side


   after merge

   a---b---c---d---E---F---G  <-- main
                           ^
                            \----- side

2. Forced merge commits ("never", 2 heads)

   before merge

   a---b---c---d               <-- main
                \
                 \-E---F---G  <-- side


   after merge

   a---b---c---d-------------*  <-- main
                \           /
                 \-E---F---G    <-- side


-- 
Jakub Narebski
Poland
ShadeHawk on #git

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
  2008-02-05  7:32         ` Sverre Hvammen Johansen
  2008-02-05  9:34           ` Jakub Narebski
@ 2008-02-05  9:40           ` Junio C Hamano
  2008-02-06  3:46             ` Sverre Hvammen Johansen
  1 sibling, 1 reply; 14+ messages in thread
From: Junio C Hamano @ 2008-02-05  9:40 UTC (permalink / raw)
  To: Sverre Hvammen Johansen; +Cc: git

"Sverre Hvammen Johansen" <hvammen@gmail.com> writes:

> On Feb 4, 2008 12:22 AM, Junio C Hamano <gitster@pobox.com> wrote:
>>
>> The documentation updates talked about what the options do, but
>> it was unclear why they could be useful in what situations and
>> workflows.  At least it was not apparent to me on my cursory
>> read.
>
> Common, fork, and path only make sense where there are at least three
> repositories or two plus an observer involved.
>
> Lets explain the observer cases.

This is a good material.  The information here should reach the
end users who will be exposed to these new features in the
documentation patch before the final submission.

> The observer is interested in changes that X, Y and Z agree upon.  He
> can merge as follows:
>
>   git merge --ff=common X Y Z

Just to avoid confusion by making sure I understand.  Earlier
you said two plus an observer above but the example is about one
observer and three other repositories, and the "merge" operation
happens inside the observer's one.

This reminds me of an ancient message I sent to darcs list,
before or soon after I took git.git over, with a topic (iirc)
"mutually supervised developer workflow".  Each developer may
make excellent and crap changes, but they communicate with each
other.  The "consensus" can be made by picking the changes that
appear in all repositories (or majority of repositories).

That is unfortunately hard to arrange in git workflow if you do
not use topic branches and/or if you rebase often (but would be
more natural match to darcs's world view).  Even if you have a
set of changes in the same spirit (i.e. "the same patch text"),
the committer and the author information would make the actual
commit objects different, so you would need to identify
different commits that introduce the same change for this to be
really useful.

And it may not work well in practice, even if we somehow solved
that "changes in the same spirit are often made into different
commits" issue.  If the place they agree last is a dud, and some
have fix-ups that others lack, the observer will end up getting
that common dud commit.

> The observer is interested in changes up to the point where someone is
> known to disagree.  He can merge as follows:
>
>   git merge --ff=fork X Y Z

If --ff=common fast-forwards to the commit all of X, Y, and Z
have in common, that means commits on X, Y or Z that are beyond
that point are the ones these three do not agree with.  How's
that different from --ff=fork?

>  The observer is interested in any give path up to one of the true
> parents.  He can merge as follows:
>
>   git merge --ff=path X Y Z
>
> This will give priority to X then Y.

"Any given path up to one of the true parents?"  Path from
where?  How is "true parent" (as opposed to untrue ones?)
defined?

> This + only are all the interesting cases for fast forward.  Some work
> flows between more than two repositories in the general case would
> require additional features for rebase:  Rebase on any patch, the fork
> point, or common ancestor of the remote branches.  This is something I
> would like to discuss at some later time.
>
>> > --ff=never will turn this off together with fast forward.  Maybe we
>> > should have --ff=traditional that is the old behavior.
>>
>> Sure, and I mildly suspect that it should be the default.
>
> I would argue that it should not be the default, simply because we
> already use the real parents when only two branches are involved, This
> is most convenient for most users.

Sorry, I referred --ff=traditional by "it".  Are we talking
about the same thing?

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
  2008-02-05  9:40           ` Junio C Hamano
@ 2008-02-06  3:46             ` Sverre Hvammen Johansen
  0 siblings, 0 replies; 14+ messages in thread
From: Sverre Hvammen Johansen @ 2008-02-06  3:46 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

On Feb 5, 2008 1:40 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Just to avoid confusion by making sure I understand.  Earlier
> you said two plus an observer above but the example is about one
> observer and three other repositories, and the "merge" operation
> happens inside the observer's one.

There need to be at least 2 + 1.  There is no difference between
--ff=common and --ff=path when there are 2+1.  I used 3+1 in the
examples.

> That is unfortunately hard to arrange in git workflow if you do
> not use topic branches and/or if you rebase often (but would be
> more natural match to darcs's world view).  Even if you have a
> set of changes in the same spirit (i.e. "the same patch text"),
> the committer and the author information would make the actual
> commit objects different, so you would need to identify
> different commits that introduce the same change for this to be
> really useful.

And I  have no intention of doing that.  This patch will keep it
simple.  I imagine that these features will not be used by most users
and we should probably not document them in the tutorials.

> > The observer is interested in changes up to the point where someone is
> > known to disagree.  He can merge as follows:
> >
> >   git merge --ff=fork X Y Z
>
> If --ff=common fast-forwards to the commit all of X, Y, and Z
> have in common, that means commits on X, Y or Z that are beyond
> that point are the ones these three do not agree with.  How's
> that different from --ff=fork?

Lets say X is on the path before the fork point before Y and Z.  X is
then the common ancestor, otherwise there will not be any difference.

> >  The observer is interested in any give path up to one of the true
> > parents.  He can merge as follows:
> >
> >   git merge --ff=path X Y Z
> >
> > This will give priority to X then Y.
>
> "Any given path up to one of the true parents?"  Path from
> where?  How is "true parent" (as opposed to untrue ones?)
> defined?

The path from HEAD.

Consider a number of distinct parents, then we can define it as
follows.  An untrue parent is a parent that can be fast forwarded to
at least one other parent.  A true (or real) parent is a parent that
can not be fast forwarded to any other parent.

> > This + only are all the interesting cases for fast forward.  Some work
> > flows between more than two repositories in the general case would
> > require additional features for rebase:  Rebase on any patch, the fork
> > point, or common ancestor of the remote branches.  This is something I
> > would like to discuss at some later time.
> >
> >> > --ff=never will turn this off together with fast forward.  Maybe we
> >> > should have --ff=traditional that is the old behavior.
> >>
> >> Sure, and I mildly suspect that it should be the default.
> >
> > I would argue that it should not be the default, simply because we
> > already use the real parents when only two branches are involved, This
> > is most convenient for most users.
>
> Sorry, I referred --ff=traditional by "it".  Are we talking
> about the same thing?

What I mean is that the commit should only record the true parents.
That may already be the case by use of git show-branch --independent.
I am not sure what that option does.  However, the merge strategy
itself is picked based on the parents specified.  That part need to
change.  It could be that the patch could use the above option to find
the true parents.  Someone need to explain to me exactly what that
option does.

I am not exactly sure how a work flow between commiters would be.
With one coordinator it would be as follows.  The coordinator would
merge using any of the --ff options.  He would use --ff=same,
--ff=only, or --ff=common if everyone needs to agree.  He would use
--ff=fork when less care needs to be taken and --ff=path when he
essentially don't  care.  Everyone else needs to rebase on his work or
something else based on his work.  --ff=same is not included in the
patch.  It requires everyone except for HEAD to point to the same
commit.

I  am really busy for about three weeks and will not be able to do
much before beginning of March.

-- 
Sverre Hvammen Johansen

^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2008-02-06  3:47 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-02-04  0:54 [RFC/PATCH] Fast forward strategies only, common, fork and path Sverre Hvammen Johansen
2008-02-04  4:49 ` Stefan (metze) Metzmacher
2008-02-04  6:51 ` Sverre Hvammen Johansen
2008-02-04  7:24   ` Junio C Hamano
2008-02-04  8:06     ` Sverre Hvammen Johansen
2008-02-04  8:22       ` Junio C Hamano
2008-02-05  7:32         ` Sverre Hvammen Johansen
2008-02-05  9:34           ` Jakub Narebski
2008-02-05  9:40           ` Junio C Hamano
2008-02-06  3:46             ` Sverre Hvammen Johansen
2008-02-04  7:13 ` Sverre Hvammen Johansen
2008-02-04  7:31   ` Junio C Hamano
2008-02-04  7:43     ` Sverre Hvammen Johansen
2008-02-04  7:19 ` Sverre Hvammen Johansen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).