--
Sverre Hvammen Johansen
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.3threads / rfc / 11862
RFC patchFast forward strategies only, common, fork and path
Subject: [RFC/PATCH] Fast forward strategies only, common, fork and path
Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
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
Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
On Feb 3, 2008 4:54 PM, Sverre Hvammen Johansen <hvammen@gmail.com> wrote:
Show 6 quoted lines
> 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
Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
"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.
Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
On Feb 3, 2008 11:24 PM, Junio C Hamano <gitster@pobox.com> wrote:
Show 5 quoted lines
> 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.
Show 10 quoted lines
> 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
Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
"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.
Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
On Feb 4, 2008 12:22 AM, Junio C Hamano <gitster@pobox.com> wrote:
Show 5 quoted lines
> > 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
Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
"Sverre Hvammen Johansen" <hvammen@gmail.com> writes:
Show 28 quoted lines
> 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 <-- sideafter merge
a---b---c---d---E---F---G <-- main
^
\----- side2. Forced merge commits ("never", 2 heads)before merge
a---b---c---d <-- main
\
\-E---F---G <-- sideafter merge
a---b---c---d-------------* <-- main
\ /
\-E---F---G <-- side-- Jakub Narebski Poland ShadeHawk on #git
Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
"Sverre Hvammen Johansen" <hvammen@gmail.com> writes:
Show 11 quoted lines
> 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?
Show 6 quoted lines
> 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?
Show 14 quoted lines
> 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?
Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
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.
Show 8 quoted lines
> 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.
Show 9 quoted lines
> > 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.
Show 10 quoted lines
> > 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.
Show 17 quoted lines
> > 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
Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
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
Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
"Sverre Hvammen Johansen" <hvammen@gmail.com> writes:
Show 6 quoted lines
> 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.
Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
> 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
Re: [RFC/PATCH] Fast forward strategies only, common, fork and path
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