threads / patch / 9949

patch, 2 partsgit-merge: add option --no-ff

Subject: [PATCH 1/2] git-merge: add option --no-ff

## tl;dr

8 messages between Sep 20, 2007 and Sep 20, 2007. Diffs are folded; open one to read it.

replies: 7people: 3as markdown or json

Lars Hjemli· Sep 20, 2007, 12:53 UTC · lore

[PATCH 0/2] add support for per branch merge policy

These two patches extends git-merge with the options --ff and --no-ff, and uses the configuration setting branch.<name>.integrationonly to select the default policy per branch (it obviously defaults to --ff), as mentioned in http://article.gmane.org/gmane.comp.version-control.git/41980

This can also be used as a clean solution to the problem related to fast-forward merges between git-svn tracking branches, especially if git-svn could be taught to set integrationonly=true on all relevant branches whenever this setting is undefined.

Diffstat:
 Documentation/config.txt        |    7 +++
 Documentation/git-svn.txt       |   17 ++++++
 Documentation/merge-options.txt |   13 +++++
 git-merge.sh                    |   30 ++++++++++-
 t/t6029-merge-integration.sh    |  108 +++++++++++++++++++++++++++++++++++++++
 5 files changed, 172 insertions(+), 3 deletions(-)
Lars Hjemli· Sep 20, 2007, 12:53 UTC · re: Lars Hjemli · lore

This option forces all merges to create a "true" merge commit, i.e. a commit with multiple parents.

A fast-forward merge will often be considered The Right Thing when working in a distributed environment, but it doesn't work well when the branches to be merged originated in subversion and the merge commit will be pushed back with 'git svn dcommit'. In this case, a fast-forward merge will make git-svn mistakenly think that the current branch is tracking the merged upstream branch. But if the merge is created with --no-ff, 'git svn dcommit' will work as expected (and the history in subversion and git will be equal).

There are also a use-case for --no-ff in 'native git' when integrating topic branches: if the merge of a topic-branch results in a fast-forward operation, there will be no way to detect that the topic-branch ever existed. This might be good or bad dependent on project policy, but both options (ff/no-ff) are technically sane.

Signed-off-by: Lars Hjemli <hjemli@gmail.com>
---
 Documentation/git-svn.txt       |   13 +++++++++++
 Documentation/merge-options.txt |    5 ++++
 git-merge.sh                    |   13 +++++++++-
 t/t6029-merge-integration.sh    |   46 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 75 insertions(+), 2 deletions(-)
 create mode 100755 t/t6029-merge-integration.sh
Show changes to 4 files +75 −2

Documentation/git-svn.txt, Documentation/merge-options.txt, git-merge.sh, t/t6029-merge-integration.sh

diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt
index e157c6a..619023d 100644
--- a/Documentation/git-svn.txt
+++ b/Documentation/git-svn.txt
@@ -475,6 +475,19 @@ use 'git-svn rebase' to update your work branch instead of 'git pull' or
 when committing into SVN, which can lead to merge commits reversing
 previous commits in SVN.
 
+If you use 'git-svn dcommit' to commit your local work to the upstream
+subversion branch, merge commits are usually handled correctly, i.e.
+git-svn will only follow the first parent of each merge commit and create
+a single subversion revision for each of them. An exception is when two
+subversion branches has been merged locally and the merge ended up as a
+fast-forward operation. This will make git-svn believe that there are no
+local changes to dcommit. To work around this issue, one can redo the
+merge using the --no-ff option:
+
+       $ git reset --hard HEAD@{1}   ## undo the fast-forward merge
+       $ git merge --no-ff <branch>
+
+
 DESIGN PHILOSOPHY
 -----------------
 Merge tracking in Subversion is lacking and doing branched development
diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt
index d64c259..b34b888 100644
--- a/Documentation/merge-options.txt
+++ b/Documentation/merge-options.txt
@@ -25,3 +25,8 @@
 	If there is no `-s` option, a built-in list of strategies
 	is used instead (`git-merge-recursive` when merging a single
 	head, `git-merge-octopus` otherwise).
+
+--no-ff::
+	Force the creation of a merge commit even when the merge would
+	have resolved as a fast-forward operation. See gitlink:git-svn[1]
+	for a use-case for this option.
diff --git a/git-merge.sh b/git-merge.sh
index 3a01db0..70ca5ff 100755
--- a/git-merge.sh
+++ b/git-merge.sh
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-USAGE='[-n] [--summary] [--no-commit] [--squash] [-s <strategy>] [-m=<merge-message>] <commit>+'
+USAGE='[-n] [--summary] [--no-commit] [--no-ff] [--squash] [-s <strategy>] [-m=<merge-message>] <commit>+'
 
 SUBDIRECTORY_OK=Yes
 . git-sh-setup
@@ -165,6 +165,9 @@ do
 		merge_msg="$1"
 		have_message=t
 		;;
+	--no-ff)
+		allow_fast_forward=f
+		;;
 	-*)	usage ;;
 	*)	break ;;
 	esac
@@ -444,7 +447,13 @@ done
 # auto resolved the merge cleanly.
 if test '' != "$result_tree"
 then
-    parents=$(git show-branch --independent "$head" "$@" | sed -e 's/^/-p /')
+    if test "$allow_fast_forward" = "f"
+    then
+        parents=$(git rev-parse "$head" "$@")
+    else
+        parents=$(git show-branch --independent "$head" "$@")
+    fi
+    parents=$(echo "$parents" | sed -e 's/^/-p /')
     result_commit=$(printf '%s\n' "$merge_msg" | git commit-tree $result_tree $parents) || exit
     finish "$result_commit" "Merge made by $wt_strategy."
     dropsave
diff --git a/t/t6029-merge-integration.sh b/t/t6029-merge-integration.sh
new file mode 100755
index 0000000..6ba7dd9
--- /dev/null
+++ b/t/t6029-merge-integration.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+test_description='merge --no-ff'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+	>file &&
+	git add file &&
+	test_tick &&
+	git commit -m initial &&
+	git tag c0 &&
+	c0=$(git rev-parse c0)
+
+	echo second >file &&
+	git add file &&
+	test_tick &&
+	git commit -m second &&
+	git tag c1 &&
+	c1=$(git rev-parse c1)
+	git branch test
+'
+
+test_expect_success 'merge c1' '
+
+	git reset --hard c0 &&
+	test_tick &&
+	git merge c1 &&
+	test $c0 = $(git rev-parse HEAD^) &&
+	test $c1 = $(git rev-parse HEAD)
+
+'
+
+test_expect_success 'merge --no-ff c1' '
+
+	git reset --hard c0 &&
+	test_tick &&
+	git merge --no-ff c1 &&
+	test $c0 = $(git rev-parse HEAD^1) &&
+	test $c1 = $(git rev-parse HEAD^2)
+
+'
+
+test_debug 'gitk &'
+
+test_done
-- 
1.5.3.2.82.g75c8d
Lars Hjemli· Sep 20, 2007, 12:53 UTC · re: Lars Hjemli · lore

[PATCH 2/2] add support for branch.<name>.integrationonly

This setting can be used to set --no-ff as default merge policy for selected branches. There is also a new new merge-option, --ff, which can be used to override the default.

Signed-off-by: Lars Hjemli <hjemli@gmail.com>
---
 Documentation/config.txt        |    7 ++++
 Documentation/git-svn.txt       |    4 ++
 Documentation/merge-options.txt |   14 +++++++--
 git-merge.sh                    |   19 ++++++++++-
 t/t6029-merge-integration.sh    |   64 ++++++++++++++++++++++++++++++++++++++-
 5 files changed, 102 insertions(+), 6 deletions(-)
Show changes to 5 files +102 −6

Documentation/config.txt, Documentation/git-svn.txt, Documentation/merge-options.txt, git-merge.sh, t/t6029-merge-integration.sh

diff --git a/Documentation/config.txt b/Documentation/config.txt
index 015910f..fa7c3c1 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -337,6 +337,13 @@ branch.<name>.merge::
 	branch.<name>.merge to the desired branch, and use the special setting
 	`.` (a period) for branch.<name>.remote.
 
+branch.<name>.integrationonly::
+	When merging into branch <name>, this setting specifies whether
+	fast-forward merges are disallowed. The setting can be overridden
+	by the --ff and --no-ff options of gitlink:git-merge[1]. See
+	gitlink:git-svn[1] for information about this setting when
+	working with subversion branches. Defaults to false.
+
 clean.requireForce::
 	A boolean to make git-clean do nothing unless given -f or -n.  Defaults
 	to false.
diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt
index 619023d..55828a3 100644
--- a/Documentation/git-svn.txt
+++ b/Documentation/git-svn.txt
@@ -487,6 +487,10 @@ merge using the --no-ff option:
        $ git reset --hard HEAD@{1}   ## undo the fast-forward merge
        $ git merge --no-ff <branch>
 
+Alternatively, one can configure the appropriate local branches to use
+--no-ff as default by setting branch.<name>.integrationonly=true in
+$GIT_DIR/config.
+
 
 DESIGN PHILOSOPHY
 -----------------
diff --git a/Documentation/merge-options.txt b/Documentation/merge-options.txt
index b34b888..f83bba9 100644
--- a/Documentation/merge-options.txt
+++ b/Documentation/merge-options.txt
@@ -26,7 +26,15 @@
 	is used instead (`git-merge-recursive` when merging a single
 	head, `git-merge-octopus` otherwise).
 
+--ff::
+	Explicitly allow fast-forward merges. This can be used to
+	override the merge policy of the current branch, as specified
+	by the branch.<branch>.integrationonly configuration setting (see 
+	gitlink:git-config[1] for further details).
+
 --no-ff::
-	Force the creation of a merge commit even when the merge would
-	have resolved as a fast-forward operation. See gitlink:git-svn[1]
-	for a use-case for this option.
+	Explicitly disallow fast-forward merges. This can be used to
+	override the merge policy of the current branch, as specified
+	by the branch.<branch>.integrationonly configuration setting (see 
+	gitlink:git-config[1] for further details, and gitlink:git-svn[1]
+	for a use-case for this option).
diff --git a/git-merge.sh b/git-merge.sh
index 70ca5ff..4775767 100755
--- a/git-merge.sh
+++ b/git-merge.sh
@@ -3,7 +3,7 @@
 # Copyright (c) 2005 Junio C Hamano
 #
 
-USAGE='[-n] [--summary] [--no-commit] [--no-ff] [--squash] [-s <strategy>] [-m=<merge-message>] <commit>+'
+USAGE='[-n] [--summary] [--no-commit] [--ff] [--no-ff] [--squash] [-s <strategy>] [-m=<merge-message>] <commit>+'
 
 SUBDIRECTORY_OK=Yes
 . git-sh-setup
@@ -23,7 +23,7 @@ no_fast_forward_strategies='subtree ours'
 no_trivial_strategies='recursive recur subtree ours'
 use_strategies=
 
-allow_fast_forward=t
+allow_fast_forward=
 allow_trivial_merge=t
 
 dropsave() {
@@ -165,6 +165,9 @@ do
 		merge_msg="$1"
 		have_message=t
 		;;
+	--ff)
+		allow_fast_forward=t
+		;;
 	--no-ff)
 		allow_fast_forward=f
 		;;
@@ -245,6 +248,18 @@ do
 done
 set x $remoteheads ; shift
 
+if test -z "$allow_fast_forward"
+then
+	branch=$(git-symbolic-ref HEAD | sed -e 's|^refs/heads/||')
+	integrate=$(git config --bool "branch.$branch.integrationonly")
+	if test -n "$branch" -a "$integrate" = "true"
+	then
+		allow_fast_forward=f
+	else
+		allow_fast_forward=t
+	fi
+fi
+
 case "$use_strategies" in
 '')
 	case "$#" in
diff --git a/t/t6029-merge-integration.sh b/t/t6029-merge-integration.sh
index 6ba7dd9..c5ffa34 100755
--- a/t/t6029-merge-integration.sh
+++ b/t/t6029-merge-integration.sh
@@ -31,6 +31,8 @@ test_expect_success 'merge c1' '
 
 '
 
+test_debug 'gitk'
+
 test_expect_success 'merge --no-ff c1' '
 
 	git reset --hard c0 &&
@@ -41,6 +43,66 @@ test_expect_success 'merge --no-ff c1' '
 
 '
 
-test_debug 'gitk &'
+test_debug 'gitk'
+
+test_expect_success 'setup integrationonly' '
+
+	git config branch.master.integrationonly true
+
+'	
+
+test_expect_success 'merge c1' '
+
+	git reset --hard c0 &&
+	test_tick &&
+	git merge c1 &&
+	test $c0 = $(git rev-parse HEAD^1) &&
+	test $c1 = $(git rev-parse HEAD^2)
+
+'
+
+test_debug 'gitk'
+
+test_expect_success 'merge c1 --ff' '
+
+	git reset --hard c0 &&
+	test_tick &&
+	git merge --ff c1 &&
+	test $c0 = $(git rev-parse HEAD^) &&
+	test $c1 = $(git rev-parse HEAD)
+
+'
+
+test_debug 'gitk'
+
+test_expect_success 'revert integrationonly' '
+
+	git config branch.master.integrationonly false
+
+'	
+
+test_expect_success 'merge c1' '
+
+	git reset --hard c0 &&
+	test_tick &&
+	git merge c1 &&
+	test $c0 = $(git rev-parse HEAD^) &&
+	test $c1 = $(git rev-parse HEAD)
+
+'
+
+test_debug 'gitk'
+
+test_expect_success 'merge c1 --no-ff' '
+
+	git reset --hard c0 &&
+	test_tick &&
+	git merge --no-ff c1 &&
+	test $c0 = $(git rev-parse HEAD^1) &&
+	test $c1 = $(git rev-parse HEAD^2)
+
+'
+
+test_debug 'gitk'
 
 test_done
-- 
1.5.3.2.82.g75c8d
Matthieu Moy· Sep 20, 2007, 13:36 UTC · re: Johannes Schindelin · lore

Re: [PATCH 2/2] add support for branch.<name>.integrationonly

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
Show 5 quoted lines
> On Thu, 20 Sep 2007, Lars Hjemli wrote:
>
>> +branch.<name>.integrationonly::
>
> Why not have something like branch.<name>.mergeOptions instead?

Why make it plural? I agree that it's better to have a multiple values option than having just "true/false", which makes it more extensible, but I don't understand the kind of things you could put here that wouldn't be better located in a separate configuration option.

-- 
Matthieu
Lars Hjemli· Sep 20, 2007, 13:07 UTC · re: Lars Hjemli · lore

Re: [PATCH 0/2] add support for per branch merge policy

On 9/20/07, Lars Hjemli <hjemli@gmail.com> wrote:
> branch.<name>.integrationonly
Btw: we might want to call this 'branch.<name>.mergepolicy' with the
possible values 'synchronize' and 'integrate'.

-- larsh

Johannes Schindelin· Sep 20, 2007, 13:24 UTC · re: Lars Hjemli · lore

Re: [PATCH 0/2] add support for per branch merge policy

Hi,
On Thu, 20 Sep 2007, Lars Hjemli wrote:
Show 5 quoted lines
> On 9/20/07, Lars Hjemli <hjemli@gmail.com> wrote:
> > branch.<name>.integrationonly
> 
> Btw: we might want to call this 'branch.<name>.mergepolicy' with the
> possible values 'synchronize' and 'integrate'.
IMHO this is way too limiting.

Ciao, Dscho

Lars Hjemli· Sep 20, 2007, 13:38 UTC · re: Johannes Schindelin · lore

Re: [PATCH 0/2] add support for per branch merge policy

[...joining two posts in one...]
On 9/20/07, Johannes Schindelin <Johannes.Schindelin@gmx.de> wrote:
Show 8 quoted lines
> > On 9/20/07, Lars Hjemli <hjemli@gmail.com> wrote:
> > > branch.<name>.integrationonly
> >
> > Btw: we might want to call this 'branch.<name>.mergepolicy' with the
> > possible values 'synchronize' and 'integrate'.
>
> IMHO this is way too limiting.
> Why not have something like branch.<name>.mergeOptions instead?
Yeah, that would be more powerful, I'll give it a try.

-- larsh

← back to recent threads