git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
* [PATCH] merge: allow using --no-ff and --ff-only at the same time
@ 2013-07-01  7:01 Miklos Vajna
  2013-07-01 14:52 ` Michael Haggerty
  0 siblings, 1 reply; 12+ messages in thread
From: Miklos Vajna @ 2013-07-01  7:01 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

1347483 (Teach 'git merge' and 'git pull' the option --ff-only,
2009-10-29) says this is not allowed, as they contradict each other.

However, --ff-only is about asserting the input of the merge, and
--no-ff is about instructing merge to always create a merge commit, i.e.
it makes sense to use these options together in some workflow, e.g. when
branches are integrated by rebasing then merging, and the maintainer
wants to be sure the branch is rebased.

Signed-off-by: Miklos Vajna <vmiklos@suse.cz>
---
 builtin/merge.c  | 12 ++++++++----
 t/t7600-merge.sh | 11 ++++++++---
 2 files changed, 16 insertions(+), 7 deletions(-)

diff --git a/builtin/merge.c b/builtin/merge.c
index 2ebe732..7026ce0 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -1162,9 +1162,6 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 		option_commit = 0;
 	}
 
-	if (!allow_fast_forward && fast_forward_only)
-		die(_("You cannot combine --no-ff with --ff-only."));
-
 	if (!abort_current_merge) {
 		if (!argc) {
 			if (default_to_upstream)
@@ -1433,7 +1430,14 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 		}
 	}
 
-	if (fast_forward_only)
+	/*
+	 * If --ff-only was used without --no-ff, or: --ff-only and --no-ff was
+	 * used at the same time, and this is not a fast-forward.
+	 */
+	if (fast_forward_only && (allow_fast_forward || remoteheads->next ||
+				common->next ||
+				hashcmp(common->item->object.sha1,
+					head_commit->object.sha1)))
 		die(_("Not possible to fast-forward, aborting."));
 
 	/* We are going to make a new commit. */
diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
index 460d8eb..bf3d9b2 100755
--- a/t/t7600-merge.sh
+++ b/t/t7600-merge.sh
@@ -497,9 +497,14 @@ test_expect_success 'combining --squash and --no-ff is refused' '
 	test_must_fail git merge --no-ff --squash c1
 '
 
-test_expect_success 'combining --ff-only and --no-ff is refused' '
-	test_must_fail git merge --ff-only --no-ff c1 &&
-	test_must_fail git merge --no-ff --ff-only c1
+test_expect_success 'combining --ff-only and --no-ff (ff is possible)' '
+	git reset --hard c0 &&
+	git merge --ff-only --no-ff c1
+'
+
+test_expect_success 'combining --ff-only and --no-ff (ff is not possible)' '
+	git reset --hard c1 &&
+	test_must_fail git merge --ff-only --no-ff c2
 '
 
 test_expect_success 'merge c0 with c1 (ff overrides no-ff)' '
-- 
1.8.1.4

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

* Re: [PATCH] merge: allow using --no-ff and --ff-only at the same time
  2013-07-01  7:01 [PATCH] merge: allow using --no-ff and --ff-only at the same time Miklos Vajna
@ 2013-07-01 14:52 ` Michael Haggerty
  2013-07-01 15:27   ` Miklos Vajna
                     ` (2 more replies)
  0 siblings, 3 replies; 12+ messages in thread
From: Michael Haggerty @ 2013-07-01 14:52 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: Junio C Hamano, git

On 07/01/2013 09:01 AM, Miklos Vajna wrote:
> 1347483 (Teach 'git merge' and 'git pull' the option --ff-only,
> 2009-10-29) says this is not allowed, as they contradict each other.
> 
> However, --ff-only is about asserting the input of the merge, and
> --no-ff is about instructing merge to always create a merge commit, i.e.
> it makes sense to use these options together in some workflow, e.g. when
> branches are integrated by rebasing then merging, and the maintainer
> wants to be sure the branch is rebased.

That is one interpretation of what these options should mean, and I
agree that it is one way of reading the manpage (which says

--ff-only::
	Refuse to merge and exit with a non-zero status unless the
	current `HEAD` is already up-to-date or the merge can be
	resolved as a fast-forward.

).  However, I don't think that the manpage unambiguously demands this
interpretation, and that (more importantly) most users would be very
surprised if --ff-only and --no-ff were not opposites.

How does it hurt?  If I have configuration value merge.ff set to "only"
and run "git merge --no-ff" and then I merge a branch that *cannot* be
fast forwarded, the logic of your patch would require the merge to be
rejected, no?  But I think it is more plausible to expect that the
command line option takes precedence.

Hmmph.  I just tested and found out that (before your patch) a "--no-ff"
command-line option does *not* override a "git config merge.ff only" but
rather that the combination provokes the error that you are trying to
remove,

    fatal: You cannot combine --no-ff with --ff-only.

I find *that* surprising; usually command-line options override
configuration file settings.  OK, it's time for some more exhaustive
testing:

   situation      merge.ff  option      result
   -------------------------------------------------------------------
1  ff possible    false     --ff        works (ff)
2  ff impossible  false     --ff        works (non-ff)
3  ff possible    false     --ff-only   error "cannot combine options"
4  ff impossible  false     --ff-only   error "cannot combine options"
5  ff possible    false     --no-ff     works (non-ff)
6  ff impossible  false     --no-ff     works (non-ff)
7  ff possible    only      --ff        works (ff)
8  ff impossible  only      --ff        error "not possible to ff"
9  ff possible    only      --ff-only   works (ff)
10 ff impossible  only      --ff-only   error "not possible to ff"
11 ff possible    only      --no-ff     error "cannot combine options"
12 ff impossible  only      --no-ff     error "cannot combine options"

>From line 1 we see that "--ff" overrides "merge.ff=false".

>From lines 3 and 4 we see that "--ff-only" cannot be combined with
"merge.ff=false".

>From line 8 we see that "merge.ff=only" has its effect despite "--ff",
which would normally allow a non-ff merge.

>From lines 11 and 12 we see that "--no-ff" cannot be combined with
"merge.ff=only".

I find this inconsistent.  I think it would be more consistent to have
exactly three states,

* merge.ff unset == --ff == "do ff if possible, otherwise non-ff"

* merge.ff=false == --no-ff == "always create merge commit"

* merge.ff=only == --ff-only == "do ff if possible, otherwise fail"

and for the command-line option to always take precedence over the
config file option.

Moreover, isn't it the usual practice for later command-line options to
take precedence over earlier ones?  It is the case for at least one command:

    $ git log --oneline --no-walk --no-decorate --decorate
    cf71d9b (HEAD, master) 2
    $ git log --oneline --no-walk --decorate --no-decorate
    cf71d9b 2

So I think that command invocations with more than one of {"--ff",
"--no-ff", "--ff-only"} should respect the last option listed rather
than complaining about "cannot combine options".

If I find the time (unlikely) I might submit a patch to implement these
expectations.


In my opinion, your use case shouldn't be supported by the command
because (1) it is confusing, (2) it is not very common, and (3) it is
easy to work around:

    if git merge-base --is-ancestor HEAD $branch
    then
        git merge --no-ff $branch
    else
        echo "fatal: Not possible to fast-forward, aborting."
        exit 1
    fi

Michael

-- 
Michael Haggerty
mhagger@alum.mit.edu
http://softwareswirl.blogspot.com/

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

* Re: [PATCH] merge: allow using --no-ff and --ff-only at the same time
  2013-07-01 14:52 ` Michael Haggerty
@ 2013-07-01 15:27   ` Miklos Vajna
  2013-07-01 15:38   ` Junio C Hamano
  2013-07-01 19:54   ` [PATCH] merge: handle --ff/--no-ff/--ff-only as a tri-state option Miklos Vajna
  2 siblings, 0 replies; 12+ messages in thread
From: Miklos Vajna @ 2013-07-01 15:27 UTC (permalink / raw)
  To: Michael Haggerty; +Cc: Junio C Hamano, git

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

Hi Michael,

On Mon, Jul 01, 2013 at 04:52:29PM +0200, Michael Haggerty <mhagger@alum.mit.edu> wrote:
> On 07/01/2013 09:01 AM, Miklos Vajna wrote:
> > 1347483 (Teach 'git merge' and 'git pull' the option --ff-only,
> > 2009-10-29) says this is not allowed, as they contradict each other.
> > 
> > However, --ff-only is about asserting the input of the merge, and
> > --no-ff is about instructing merge to always create a merge commit, i.e.
> > it makes sense to use these options together in some workflow, e.g. when
> > branches are integrated by rebasing then merging, and the maintainer
> > wants to be sure the branch is rebased.
> 
> That is one interpretation of what these options should mean, and I
> agree that it is one way of reading the manpage (which says
> 
> --ff-only::
> 	Refuse to merge and exit with a non-zero status unless the
> 	current `HEAD` is already up-to-date or the merge can be
> 	resolved as a fast-forward.
> 
> ).  However, I don't think that the manpage unambiguously demands this
> interpretation, and that (more importantly) most users would be very
> surprised if --ff-only and --no-ff were not opposites.

Yes, I agree that that's an unfortunate naming. --ff and --no-ff is the
opposite of each other, however --ff-only is independent, and I would
even rename it to something like --ff-input-only -- but I don't think
it's worth to do so, seeing the cost of it (probably these options are
used by scripts as well).

> How does it hurt?  If I have configuration value merge.ff set to "only"
> and run "git merge --no-ff" and then I merge a branch that *cannot* be
> fast forwarded, the logic of your patch would require the merge to be
> rejected, no?  But I think it is more plausible to expect that the
> command line option takes precedence.

Hmm, I did not remember that actually merge.ff even uses the same
configuration slot for these switches. :-( Yes, that would make sense to
fix, once the switches can be combined. Maybe merge.ff and
merge.ff-only?

> In my opinion, your use case shouldn't be supported by the command
> because (1) it is confusing,

I don't see why it would be confusing. I think using these two options
together is one way to try to get the benefits of both rebase (cleaner
history) and merge (keeping the history of which commits came from a
given merge).

> (2) it is not very common,

Hard to argue that argument. :-) No idea what counts as common, my
motivation is that some projects (e.g. syslog-ng) integrate *every*
feature branch this way, and doing this "manually" (as in indeed
manually or by using a helper script) seems suboptimal, when the support
for this is already mostly in merge.c, just disabled.

> easy to work around:
> 
>     if git merge-base --is-ancestor HEAD $branch
>     then
>         git merge --no-ff $branch
>     else
>         echo "fatal: Not possible to fast-forward, aborting."
>         exit 1
>     fi

Right, that's indeed a viable workaround for the problem.

Miklos

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

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

* Re: [PATCH] merge: allow using --no-ff and --ff-only at the same time
  2013-07-01 14:52 ` Michael Haggerty
  2013-07-01 15:27   ` Miklos Vajna
@ 2013-07-01 15:38   ` Junio C Hamano
  2013-07-01 16:10     ` Miklos Vajna
  2013-07-01 19:54   ` [PATCH] merge: handle --ff/--no-ff/--ff-only as a tri-state option Miklos Vajna
  2 siblings, 1 reply; 12+ messages in thread
From: Junio C Hamano @ 2013-07-01 15:38 UTC (permalink / raw)
  To: Michael Haggerty; +Cc: Miklos Vajna, git

Michael Haggerty <mhagger@alum.mit.edu> writes:

> So I think that command invocations with more than one of {"--ff",
> "--no-ff", "--ff-only"} should respect the last option listed rather
> than complaining about "cannot combine options".
>
> If I find the time (unlikely) I might submit a patch to implement these
> expectations.

And I wouldn't reject it on the basis of the design --- I agree
fully with your analysis above.  Thanks for digging and spelling out
how they should be fixed.

As to "--no-ff" vs "--ff-only", "--ff-only" has always meant "only
fast-forward updates are allowed.  We do not want to create a merge
commit with this operation."  I do agree with you that the proposed
patch changes the established semantis and may be too disruptive a
thing to do at this point.

> In my opinion, your use case shouldn't be supported by the command
> because (1) it is confusing, (2) it is not very common, and (3) it is
> easy to work around:
> ...

If one were designing Git merge from scratch today, however, I could
see one may have designed these as two orthogonal switches.

 - Precondition on the shape of histories being merged ("fail unless
   fast forward" does not have to be the only criteria);

 - How the update is done ("fast forward to the other head", "always
   create a merge", "fast forward if possible, otherwise merge" do
   not have to be the only three choices).

I do not fundamentally oppose to such a new feature, but they have
to interact sanely with the current "--ff={only,only,never}".

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

* Re: [PATCH] merge: allow using --no-ff and --ff-only at the same time
  2013-07-01 15:38   ` Junio C Hamano
@ 2013-07-01 16:10     ` Miklos Vajna
  2013-07-01 16:43       ` Junio C Hamano
  0 siblings, 1 reply; 12+ messages in thread
From: Miklos Vajna @ 2013-07-01 16:10 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Michael Haggerty, git

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

On Mon, Jul 01, 2013 at 08:38:21AM -0700, Junio C Hamano <gitster@pobox.com> wrote:
> As to "--no-ff" vs "--ff-only", "--ff-only" has always meant "only
> fast-forward updates are allowed.  We do not want to create a merge
> commit with this operation."  I do agree with you that the proposed
> patch changes the established semantis and may be too disruptive a
> thing to do at this point.

Hmm, one way around this may be to add a new option that is basically
the same as --no-ff --ff-only (with the patch), except it has a
different name, so it's not confusing. 'git merge --rebase' could be
used for this, but such a name is misleading as well. Anyone has a
better naming idea? :-)

> If one were designing Git merge from scratch today, however, I could
> see one may have designed these as two orthogonal switches.
> 
>  - Precondition on the shape of histories being merged ("fail unless
>    fast forward" does not have to be the only criteria);
> 
>  - How the update is done ("fast forward to the other head", "always
>    create a merge", "fast forward if possible, otherwise merge" do
>    not have to be the only three choices).
> 
> I do not fundamentally oppose to such a new feature, but they have
> to interact sanely with the current "--ff={only,only,never}".

OK, so if I get it right, the problem is that users got used to that
the --ff-only not only means a precondition for the merge, but also
means "either don't create a merge commit or fail", while my patch would
change this second behaviour.

I could imagine then new switches, like 'git merge --pre=ff
--update=no-ff" could provide these, though I'm not sure if it makes
sense to add such generic switches till the only user is "ff".

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

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

* Re: [PATCH] merge: allow using --no-ff and --ff-only at the same time
  2013-07-01 16:10     ` Miklos Vajna
@ 2013-07-01 16:43       ` Junio C Hamano
  0 siblings, 0 replies; 12+ messages in thread
From: Junio C Hamano @ 2013-07-01 16:43 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: Michael Haggerty, git

Miklos Vajna <vmiklos@suse.cz> writes:

> OK, so if I get it right, the problem is that users got used to
> that the --ff-only not only means a precondition for the merge,
> but also means "either don't create a merge commit or fail", while
> my patch would change this second behaviour.

It is not just "users got used to".  "We do not want to create a
merge commit with this operation." is what "--ff-only" means from
the day one [*1*].

For a merge not to create an extra merge commit, the other history
has to be a proper descendant, but that "precondition" is a mere
logical consequence of the ultimate goal of the mode.

> I could imagine then new switches, like 'git merge --pre=ff
> --update=no-ff" could provide these, though I'm not sure if it makes
> sense to add such generic switches till the only user is "ff".

Yes, that is why I said "if one were designing it from scratch, I
could see..." in a very weak form.


[Footnote]

*1* 13474835 (Teach 'git merge' and 'git pull' the option --ff-only,
2009-10-29) and also $gmane/107768 whose documentation part says:

  "Refuse to merge unless the merge is resolved as a fast-forward."

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

* [PATCH] merge: handle --ff/--no-ff/--ff-only as a tri-state option
  2013-07-01 14:52 ` Michael Haggerty
  2013-07-01 15:27   ` Miklos Vajna
  2013-07-01 15:38   ` Junio C Hamano
@ 2013-07-01 19:54   ` Miklos Vajna
  2013-07-01 20:27     ` Junio C Hamano
  2013-07-02  8:42     ` Michael Haggerty
  2 siblings, 2 replies; 12+ messages in thread
From: Miklos Vajna @ 2013-07-01 19:54 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Michael Haggerty, git

This has multiple benefits: with more than one of {"--ff", "--no-ff",
"--ff-only"} respects the last option; also the command-line option to
always take precedence over the config file option.

Signed-off-by: Miklos Vajna <vmiklos@suse.cz>
---

On Mon, Jul 01, 2013 at 04:52:29PM +0200, Michael Haggerty <mhagger@alum.mit.edu> wrote:
> If I find the time (unlikely) I might submit a patch to implement these
> expectations.

Seeing that the --no-ff / --ff-only combo wasn't denied just sort of 
accidently, I agree that it makes more sense to merge allow_fast_forward
and fast_forward_only to a single enum, that automatically gives you 
both benefits.

 builtin/merge.c  | 65 +++++++++++++++++++++++++++++++++++++-------------------
 t/t7600-merge.sh | 12 ++++++++---
 2 files changed, 52 insertions(+), 25 deletions(-)

diff --git a/builtin/merge.c b/builtin/merge.c
index 2ebe732..561edf4 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -47,8 +47,8 @@ static const char * const builtin_merge_usage[] = {
 };
 
 static int show_diffstat = 1, shortlog_len = -1, squash;
-static int option_commit = 1, allow_fast_forward = 1;
-static int fast_forward_only, option_edit = -1;
+static int option_commit = 1;
+static int option_edit = -1;
 static int allow_trivial = 1, have_message, verify_signatures;
 static int overwrite_ignore = 1;
 static struct strbuf merge_msg = STRBUF_INIT;
@@ -76,6 +76,14 @@ static struct strategy all_strategy[] = {
 
 static const char *pull_twohead, *pull_octopus;
 
+enum ff_type {
+	FF_ALLOW,
+	FF_NO,
+	FF_ONLY
+};
+
+static enum ff_type fast_forward = FF_ALLOW;
+
 static int option_parse_message(const struct option *opt,
 				const char *arg, int unset)
 {
@@ -178,6 +186,21 @@ static int option_parse_n(const struct option *opt,
 	return 0;
 }
 
+static int option_parse_ff(const struct option *opt,
+			  const char *arg, int unset)
+{
+	fast_forward = unset ? FF_NO : FF_ALLOW;
+	return 0;
+}
+
+static int option_parse_ff_only(const struct option *opt,
+			  const char *arg, int unset)
+{
+	if (!unset)
+		fast_forward = FF_ONLY;
+	return 0;
+}
+
 static struct option builtin_merge_options[] = {
 	{ OPTION_CALLBACK, 'n', NULL, NULL, NULL,
 		N_("do not show a diffstat at the end of the merge"),
@@ -194,10 +217,12 @@ static struct option builtin_merge_options[] = {
 		N_("perform a commit if the merge succeeds (default)")),
 	OPT_BOOL('e', "edit", &option_edit,
 		N_("edit message before committing")),
-	OPT_BOOLEAN(0, "ff", &allow_fast_forward,
-		N_("allow fast-forward (default)")),
-	OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
-		N_("abort if fast-forward is not possible")),
+	{ OPTION_CALLBACK, 0, "ff", NULL, NULL,
+		N_("allow fast-forward (default)"),
+		PARSE_OPT_NOARG, option_parse_ff },
+	{ OPTION_CALLBACK, 0, "ff-only", NULL, NULL,
+		N_("abort if fast-forward is not possible"),
+		PARSE_OPT_NOARG, option_parse_ff_only },
 	OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
 	OPT_BOOL(0, "verify-signatures", &verify_signatures,
 		N_("Verify that the named commit has a valid GPG signature")),
@@ -581,10 +606,9 @@ static int git_merge_config(const char *k, const char *v, void *cb)
 	else if (!strcmp(k, "merge.ff")) {
 		int boolval = git_config_maybe_bool(k, v);
 		if (0 <= boolval) {
-			allow_fast_forward = boolval;
+			fast_forward = boolval ? FF_ALLOW : FF_NO;
 		} else if (v && !strcmp(v, "only")) {
-			allow_fast_forward = 1;
-			fast_forward_only = 1;
+			fast_forward = FF_ONLY;
 		} /* do not barf on values from future versions of git */
 		return 0;
 	} else if (!strcmp(k, "merge.defaulttoupstream")) {
@@ -863,7 +887,7 @@ static int finish_automerge(struct commit *head,
 
 	free_commit_list(common);
 	parents = remoteheads;
-	if (!head_subsumed || !allow_fast_forward)
+	if (!head_subsumed || fast_forward == FF_NO)
 		commit_list_insert(head, &parents);
 	strbuf_addch(&merge_msg, '\n');
 	prepare_to_commit(remoteheads);
@@ -1008,7 +1032,7 @@ static void write_merge_state(struct commit_list *remoteheads)
 	if (fd < 0)
 		die_errno(_("Could not open '%s' for writing"), filename);
 	strbuf_reset(&buf);
-	if (!allow_fast_forward)
+	if (fast_forward == FF_NO)
 		strbuf_addf(&buf, "no-ff");
 	if (write_in_full(fd, buf.buf, buf.len) != buf.len)
 		die_errno(_("Could not write to '%s'"), filename);
@@ -1157,14 +1181,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 		show_diffstat = 0;
 
 	if (squash) {
-		if (!allow_fast_forward)
+		if (fast_forward == FF_NO)
 			die(_("You cannot combine --squash with --no-ff."));
 		option_commit = 0;
 	}
 
-	if (!allow_fast_forward && fast_forward_only)
-		die(_("You cannot combine --no-ff with --ff-only."));
-
 	if (!abort_current_merge) {
 		if (!argc) {
 			if (default_to_upstream)
@@ -1206,7 +1227,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 				"empty head"));
 		if (squash)
 			die(_("Squash commit into empty head not supported yet"));
-		if (!allow_fast_forward)
+		if (fast_forward == FF_NO)
 			die(_("Non-fast-forward commit does not make sense into "
 			    "an empty head"));
 		remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
@@ -1294,11 +1315,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 			    sha1_to_hex(commit->object.sha1));
 		setenv(buf.buf, merge_remote_util(commit)->name, 1);
 		strbuf_reset(&buf);
-		if (!fast_forward_only &&
+		if (fast_forward != FF_ONLY &&
 		    merge_remote_util(commit) &&
 		    merge_remote_util(commit)->obj &&
 		    merge_remote_util(commit)->obj->type == OBJ_TAG)
-			allow_fast_forward = 0;
+			fast_forward = FF_NO;
 	}
 
 	if (option_edit < 0)
@@ -1315,7 +1336,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 
 	for (i = 0; i < use_strategies_nr; i++) {
 		if (use_strategies[i]->attr & NO_FAST_FORWARD)
-			allow_fast_forward = 0;
+			fast_forward = FF_NO;
 		if (use_strategies[i]->attr & NO_TRIVIAL)
 			allow_trivial = 0;
 	}
@@ -1345,7 +1366,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 		 */
 		finish_up_to_date("Already up-to-date.");
 		goto done;
-	} else if (allow_fast_forward && !remoteheads->next &&
+	} else if (fast_forward != FF_NO && !remoteheads->next &&
 			!common->next &&
 			!hashcmp(common->item->object.sha1, head_commit->object.sha1)) {
 		/* Again the most common case of merging one remote. */
@@ -1392,7 +1413,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 		 * only one common.
 		 */
 		refresh_cache(REFRESH_QUIET);
-		if (allow_trivial && !fast_forward_only) {
+		if (allow_trivial && fast_forward != FF_ONLY) {
 			/* See if it is really trivial. */
 			git_committer_info(IDENT_STRICT);
 			printf(_("Trying really trivial in-index merge...\n"));
@@ -1433,7 +1454,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 		}
 	}
 
-	if (fast_forward_only)
+	if (fast_forward == FF_ONLY)
 		die(_("Not possible to fast-forward, aborting."));
 
 	/* We are going to make a new commit. */
diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
index 460d8eb..3ff5fb8 100755
--- a/t/t7600-merge.sh
+++ b/t/t7600-merge.sh
@@ -497,9 +497,15 @@ test_expect_success 'combining --squash and --no-ff is refused' '
 	test_must_fail git merge --no-ff --squash c1
 '
 
-test_expect_success 'combining --ff-only and --no-ff is refused' '
-	test_must_fail git merge --ff-only --no-ff c1 &&
-	test_must_fail git merge --no-ff --ff-only c1
+test_expect_success 'option --ff-only overwrites --no-ff' '
+	git merge --no-ff --ff-only c1 &&
+	test_must_fail git merge --no-ff --ff-only c2
+'
+
+test_expect_success 'option --ff-only overwrites merge.ff=only config' '
+	git reset --hard c0 &&
+	test_config merge.ff only &&
+	git merge --no-ff c1
 '
 
 test_expect_success 'merge c0 with c1 (ff overrides no-ff)' '
-- 
1.8.1.4

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

* Re: [PATCH] merge: handle --ff/--no-ff/--ff-only as a tri-state option
  2013-07-01 19:54   ` [PATCH] merge: handle --ff/--no-ff/--ff-only as a tri-state option Miklos Vajna
@ 2013-07-01 20:27     ` Junio C Hamano
  2013-07-02  8:42     ` Michael Haggerty
  1 sibling, 0 replies; 12+ messages in thread
From: Junio C Hamano @ 2013-07-01 20:27 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: Michael Haggerty, git

Miklos Vajna <vmiklos@suse.cz> writes:

> On Mon, Jul 01, 2013 at 04:52:29PM +0200, Michael Haggerty <mhagger@alum.mit.edu> wrote:
>> If I find the time (unlikely) I might submit a patch to implement these
>> expectations.
>
> Seeing that the --no-ff / --ff-only combo wasn't denied just sort of 
> accidently, I agree that it makes more sense to merge allow_fast_forward
> and fast_forward_only to a single enum, that automatically gives you 
> both benefits.

Yes, this goes in the right direction.  "Pick one out of these three
possibilities" is how the configuration is done, and the command
line option parsing should follow suit by consolidating these two
variables into one.

Thanks, will queue.

I didn't read the patch carefully, though, so review comments are
very much appreciated.

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

* Re: [PATCH] merge: handle --ff/--no-ff/--ff-only as a tri-state option
  2013-07-01 19:54   ` [PATCH] merge: handle --ff/--no-ff/--ff-only as a tri-state option Miklos Vajna
  2013-07-01 20:27     ` Junio C Hamano
@ 2013-07-02  8:42     ` Michael Haggerty
  2013-07-02 14:47       ` [PATCH v2] " Miklos Vajna
  2013-07-02 18:46       ` [PATCH] " Junio C Hamano
  1 sibling, 2 replies; 12+ messages in thread
From: Michael Haggerty @ 2013-07-02  8:42 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: Junio C Hamano, git

On 07/01/2013 09:54 PM, Miklos Vajna wrote:
> This has multiple benefits: with more than one of {"--ff", "--no-ff",
> "--ff-only"} respects the last option; also the command-line option to
> always take precedence over the config file option.
> 
> Signed-off-by: Miklos Vajna <vmiklos@suse.cz>
> ---
> 
> On Mon, Jul 01, 2013 at 04:52:29PM +0200, Michael Haggerty <mhagger@alum.mit.edu> wrote:
>> If I find the time (unlikely) I might submit a patch to implement these
>> expectations.
> 
> Seeing that the --no-ff / --ff-only combo wasn't denied just sort of 
> accidently, I agree that it makes more sense to merge allow_fast_forward
> and fast_forward_only to a single enum, that automatically gives you 
> both benefits.

Thanks a lot for taking this on!  I would definitely be happy to be able
to set merge.ff=false without preventing the use of explicit "--ff-only"
from the command line.

See comments below...

>  builtin/merge.c  | 65 +++++++++++++++++++++++++++++++++++++-------------------
>  t/t7600-merge.sh | 12 ++++++++---
>  2 files changed, 52 insertions(+), 25 deletions(-)
> 
> diff --git a/builtin/merge.c b/builtin/merge.c
> index 2ebe732..561edf4 100644
> --- a/builtin/merge.c
> +++ b/builtin/merge.c
> @@ -47,8 +47,8 @@ static const char * const builtin_merge_usage[] = {
>  };
>  
>  static int show_diffstat = 1, shortlog_len = -1, squash;
> -static int option_commit = 1, allow_fast_forward = 1;
> -static int fast_forward_only, option_edit = -1;
> +static int option_commit = 1;
> +static int option_edit = -1;
>  static int allow_trivial = 1, have_message, verify_signatures;
>  static int overwrite_ignore = 1;
>  static struct strbuf merge_msg = STRBUF_INIT;
> @@ -76,6 +76,14 @@ static struct strategy all_strategy[] = {
>  
>  static const char *pull_twohead, *pull_octopus;
>  
> +enum ff_type {
> +	FF_ALLOW,
> +	FF_NO,
> +	FF_ONLY
> +};
> +
> +static enum ff_type fast_forward = FF_ALLOW;
> +
>  static int option_parse_message(const struct option *opt,
>  				const char *arg, int unset)
>  {
> @@ -178,6 +186,21 @@ static int option_parse_n(const struct option *opt,
>  	return 0;
>  }
>  
> +static int option_parse_ff(const struct option *opt,
> +			  const char *arg, int unset)
> +{
> +	fast_forward = unset ? FF_NO : FF_ALLOW;
> +	return 0;
> +}
> +
> +static int option_parse_ff_only(const struct option *opt,
> +			  const char *arg, int unset)
> +{
> +	if (!unset)
> +		fast_forward = FF_ONLY;
> +	return 0;
> +}
> +

You allow --no-ff-only but ignore it, which I think is incorrect.  In

    git merge --ff-only --no-ff-only [...]

, the --no-ff-only should presumably cancel the effect of the previous
--ff-only (i.e., be equivalent to "--ff").  But it is a little bit
subtle because

    git merge --no-ff --no-ff-only

should presumably be equivalent to --no-ff.  So I think that
"--no-ff-only" should do something like

    if (fast_forward == FF_ONLY)
        fast_forward = FF_ALLOW;

(Note that there is an asymmetry here, because "--no-ff-only"
*shouldn't* cancel the effect of "--no-ff", whereas "--ff" *should*
cancel the effect of "--ff-only".  This is because --ff-only restricts
what the user wants to allow whereas --ff removes a restriction.  So I
think it is OK.)

>  static struct option builtin_merge_options[] = {
>  	{ OPTION_CALLBACK, 'n', NULL, NULL, NULL,
>  		N_("do not show a diffstat at the end of the merge"),
> @@ -194,10 +217,12 @@ static struct option builtin_merge_options[] = {
>  		N_("perform a commit if the merge succeeds (default)")),
>  	OPT_BOOL('e', "edit", &option_edit,
>  		N_("edit message before committing")),
> -	OPT_BOOLEAN(0, "ff", &allow_fast_forward,
> -		N_("allow fast-forward (default)")),
> -	OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
> -		N_("abort if fast-forward is not possible")),
> +	{ OPTION_CALLBACK, 0, "ff", NULL, NULL,
> +		N_("allow fast-forward (default)"),
> +		PARSE_OPT_NOARG, option_parse_ff },
> +	{ OPTION_CALLBACK, 0, "ff-only", NULL, NULL,
> +		N_("abort if fast-forward is not possible"),
> +		PARSE_OPT_NOARG, option_parse_ff_only },
>  	OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
>  	OPT_BOOL(0, "verify-signatures", &verify_signatures,
>  		N_("Verify that the named commit has a valid GPG signature")),

I'm no options guru, but I think it would be possible to implement --ff
and --no-ff without callbacks if you choose constants such that
FF_NO==0, something like:

    enum ff_type {
    	FF_NO = 0, /* It is important that this value be zero! */
    	FF_ALLOW,
    	FF_ONLY
    };

    static int fast_forward = FF_ALLOW;

    static struct option builtin_merge_options[] = {
        [...]
        { OPTION_SET_INT, 0, "ff", &fast_forward, NULL,
        	N_("allow fast-forward (default)"),
        	PARSE_OPT_NOARG, NULL, FF_ALLOW },
        { OPTION_CALLBACK, 0, "ff-only", [...]

because OPTION_SET_INT resets the value to zero if "--no-ff" is
specified, which is just what we need.

> @@ -581,10 +606,9 @@ static int git_merge_config(const char *k, const char *v, void *cb)
>  	else if (!strcmp(k, "merge.ff")) {
>  		int boolval = git_config_maybe_bool(k, v);
>  		if (0 <= boolval) {
> -			allow_fast_forward = boolval;
> +			fast_forward = boolval ? FF_ALLOW : FF_NO;
>  		} else if (v && !strcmp(v, "only")) {
> -			allow_fast_forward = 1;
> -			fast_forward_only = 1;
> +			fast_forward = FF_ONLY;
>  		} /* do not barf on values from future versions of git */
>  		return 0;
>  	} else if (!strcmp(k, "merge.defaulttoupstream")) {
> @@ -863,7 +887,7 @@ static int finish_automerge(struct commit *head,
>  
>  	free_commit_list(common);
>  	parents = remoteheads;
> -	if (!head_subsumed || !allow_fast_forward)
> +	if (!head_subsumed || fast_forward == FF_NO)
>  		commit_list_insert(head, &parents);
>  	strbuf_addch(&merge_msg, '\n');
>  	prepare_to_commit(remoteheads);
> @@ -1008,7 +1032,7 @@ static void write_merge_state(struct commit_list *remoteheads)
>  	if (fd < 0)
>  		die_errno(_("Could not open '%s' for writing"), filename);
>  	strbuf_reset(&buf);
> -	if (!allow_fast_forward)
> +	if (fast_forward == FF_NO)
>  		strbuf_addf(&buf, "no-ff");
>  	if (write_in_full(fd, buf.buf, buf.len) != buf.len)
>  		die_errno(_("Could not write to '%s'"), filename);
> @@ -1157,14 +1181,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
>  		show_diffstat = 0;
>  
>  	if (squash) {
> -		if (!allow_fast_forward)
> +		if (fast_forward == FF_NO)
>  			die(_("You cannot combine --squash with --no-ff."));
>  		option_commit = 0;
>  	}
>  

So there is still a problem with setting merge.ff=false, namely that it
prevents the use of --squash.  That's not good.  (I realize that you are
not to blame for this pre-existing behavior.)

How should --squash and the ff-related options interact?

    git merge --ff --squash
    git merge --no-ff --squash

I think these should just squash.

    git merge --ff-only --squash

I think this should definitely squash.  But perhaps it should require
that HEAD be an ancestor of the branch to be merged?

    git merge --squash --ff
    git merge --squash --no-ff
    git merge --squash --ff-only

Should these do the same as the versions with the option order reversed?
 Or should the command line option that appears later take precedence?
The latter implies that {--ff, --no-ff, --ff-only, --squash} actually
constitute a single *quad-state* option, representing "how the results
of the merge should be handled", and, for example,

    git merge --squash --ff-only

ignores the --squash option, and

    git merge --ff-only --squash

ignores the --ff-only option.

I'm not sure.

> -	if (!allow_fast_forward && fast_forward_only)
> -		die(_("You cannot combine --no-ff with --ff-only."));
> -
>  	if (!abort_current_merge) {
>  		if (!argc) {
>  			if (default_to_upstream)
> @@ -1206,7 +1227,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
>  				"empty head"));
>  		if (squash)
>  			die(_("Squash commit into empty head not supported yet"));
> -		if (!allow_fast_forward)
> +		if (fast_forward == FF_NO)
>  			die(_("Non-fast-forward commit does not make sense into "
>  			    "an empty head"));
>  		remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
> @@ -1294,11 +1315,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
>  			    sha1_to_hex(commit->object.sha1));
>  		setenv(buf.buf, merge_remote_util(commit)->name, 1);
>  		strbuf_reset(&buf);
> -		if (!fast_forward_only &&
> +		if (fast_forward != FF_ONLY &&
>  		    merge_remote_util(commit) &&
>  		    merge_remote_util(commit)->obj &&
>  		    merge_remote_util(commit)->obj->type == OBJ_TAG)
> -			allow_fast_forward = 0;
> +			fast_forward = FF_NO;
>  	}
>  
>  	if (option_edit < 0)
> @@ -1315,7 +1336,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
>  
>  	for (i = 0; i < use_strategies_nr; i++) {
>  		if (use_strategies[i]->attr & NO_FAST_FORWARD)
> -			allow_fast_forward = 0;
> +			fast_forward = FF_NO;
>  		if (use_strategies[i]->attr & NO_TRIVIAL)
>  			allow_trivial = 0;
>  	}
> @@ -1345,7 +1366,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
>  		 */
>  		finish_up_to_date("Already up-to-date.");
>  		goto done;
> -	} else if (allow_fast_forward && !remoteheads->next &&
> +	} else if (fast_forward != FF_NO && !remoteheads->next &&
>  			!common->next &&
>  			!hashcmp(common->item->object.sha1, head_commit->object.sha1)) {
>  		/* Again the most common case of merging one remote. */
> @@ -1392,7 +1413,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
>  		 * only one common.
>  		 */
>  		refresh_cache(REFRESH_QUIET);
> -		if (allow_trivial && !fast_forward_only) {
> +		if (allow_trivial && fast_forward != FF_ONLY) {
>  			/* See if it is really trivial. */
>  			git_committer_info(IDENT_STRICT);
>  			printf(_("Trying really trivial in-index merge...\n"));
> @@ -1433,7 +1454,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
>  		}
>  	}
>  
> -	if (fast_forward_only)
> +	if (fast_forward == FF_ONLY)
>  		die(_("Not possible to fast-forward, aborting."));
>  
>  	/* We are going to make a new commit. */
> diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
> index 460d8eb..3ff5fb8 100755
> --- a/t/t7600-merge.sh
> +++ b/t/t7600-merge.sh
> @@ -497,9 +497,15 @@ test_expect_success 'combining --squash and --no-ff is refused' '
>  	test_must_fail git merge --no-ff --squash c1
>  '
>  
> -test_expect_success 'combining --ff-only and --no-ff is refused' '
> -	test_must_fail git merge --ff-only --no-ff c1 &&
> -	test_must_fail git merge --no-ff --ff-only c1
> +test_expect_success 'option --ff-only overwrites --no-ff' '
> +	git merge --no-ff --ff-only c1 &&
> +	test_must_fail git merge --no-ff --ff-only c2
> +'
> +
> +test_expect_success 'option --ff-only overwrites merge.ff=only config' '
> +	git reset --hard c0 &&
> +	test_config merge.ff only &&
> +	git merge --no-ff c1
>  '
>  
>  test_expect_success 'merge c0 with c1 (ff overrides no-ff)' '
> 


-- 
Michael Haggerty
mhagger@alum.mit.edu
http://softwareswirl.blogspot.com/

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

* [PATCH v2] merge: handle --ff/--no-ff/--ff-only as a tri-state option
  2013-07-02  8:42     ` Michael Haggerty
@ 2013-07-02 14:47       ` Miklos Vajna
  2013-07-02 20:12         ` Junio C Hamano
  2013-07-02 18:46       ` [PATCH] " Junio C Hamano
  1 sibling, 1 reply; 12+ messages in thread
From: Miklos Vajna @ 2013-07-02 14:47 UTC (permalink / raw)
  To: Michael Haggerty; +Cc: Junio C Hamano, git

This has multiple benefits: with more than one of {"--ff", "--no-ff",
"--ff-only"} respects the last option; also the command-line option to
always take precedence over the config file option.

Signed-off-by: Miklos Vajna <vmiklos@suse.cz>
---
 builtin/merge.c  | 55 +++++++++++++++++++++++++++++++++----------------------
 t/t7600-merge.sh | 12 +++++++++---
 2 files changed, 42 insertions(+), 25 deletions(-)

On Tue, Jul 02, 2013 at 10:42:39AM +0200, Michael Haggerty <mhagger@alum.mit.edu> wrote:
> You allow --no-ff-only but ignore it, which I think is incorrect.  In
> 
>     git merge --ff-only --no-ff-only [...]
> 
> , the --no-ff-only should presumably cancel the effect of the previous
> --ff-only (i.e., be equivalent to "--ff").  But it is a little bit
> subtle because
> 
>     git merge --no-ff --no-ff-only
> 
> should presumably be equivalent to --no-ff.  So I think that
> "--no-ff-only" should do something like
> 
>     if (fast_forward == FF_ONLY)
>         fast_forward = FF_ALLOW;

Do we really want --no-ff-only? I would rather just disable it, see the
updated patch.

> I'm no options guru, but I think it would be possible to implement --ff
> and --no-ff without callbacks if you choose constants such that
> FF_NO==0, something like:

Indeed, done.

> Should these do the same as the versions with the option order reversed?
>  Or should the command line option that appears later take precedence?
> The latter implies that {--ff, --no-ff, --ff-only, --squash} actually
> constitute a single *quad-state* option, representing "how the results
> of the merge should be handled", and, for example,
> 
>     git merge --squash --ff-only
> 
> ignores the --squash option, and
> 
>     git merge --ff-only --squash
> 
> ignores the --ff-only option.
> 
> I'm not sure.

Actually there is also --no-squash, used by e.g. git-pull internally.
You definitely don't want a five-state option. :-) So for now I would
rather let --squash/--no-squash alone.

diff --git a/builtin/merge.c b/builtin/merge.c
index 2ebe732..149f32a 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -47,8 +47,8 @@ static const char * const builtin_merge_usage[] = {
 };
 
 static int show_diffstat = 1, shortlog_len = -1, squash;
-static int option_commit = 1, allow_fast_forward = 1;
-static int fast_forward_only, option_edit = -1;
+static int option_commit = 1;
+static int option_edit = -1;
 static int allow_trivial = 1, have_message, verify_signatures;
 static int overwrite_ignore = 1;
 static struct strbuf merge_msg = STRBUF_INIT;
@@ -76,6 +76,14 @@ static struct strategy all_strategy[] = {
 
 static const char *pull_twohead, *pull_octopus;
 
+enum ff_type {
+	FF_NO,
+	FF_ALLOW,
+	FF_ONLY
+};
+
+static enum ff_type fast_forward = FF_ALLOW;
+
 static int option_parse_message(const struct option *opt,
 				const char *arg, int unset)
 {
@@ -178,6 +186,13 @@ static int option_parse_n(const struct option *opt,
 	return 0;
 }
 
+static int option_parse_ff_only(const struct option *opt,
+			  const char *arg, int unset)
+{
+	fast_forward = FF_ONLY;
+	return 0;
+}
+
 static struct option builtin_merge_options[] = {
 	{ OPTION_CALLBACK, 'n', NULL, NULL, NULL,
 		N_("do not show a diffstat at the end of the merge"),
@@ -194,10 +209,10 @@ static struct option builtin_merge_options[] = {
 		N_("perform a commit if the merge succeeds (default)")),
 	OPT_BOOL('e', "edit", &option_edit,
 		N_("edit message before committing")),
-	OPT_BOOLEAN(0, "ff", &allow_fast_forward,
-		N_("allow fast-forward (default)")),
-	OPT_BOOLEAN(0, "ff-only", &fast_forward_only,
-		N_("abort if fast-forward is not possible")),
+	OPT_SET_INT(0, "ff", &fast_forward, N_("allow fast-forward (default)"), FF_ALLOW),
+	{ OPTION_CALLBACK, 0, "ff-only", NULL, NULL,
+		N_("abort if fast-forward is not possible"),
+		PARSE_OPT_NOARG | PARSE_OPT_NONEG, option_parse_ff_only },
 	OPT_RERERE_AUTOUPDATE(&allow_rerere_auto),
 	OPT_BOOL(0, "verify-signatures", &verify_signatures,
 		N_("Verify that the named commit has a valid GPG signature")),
@@ -581,10 +596,9 @@ static int git_merge_config(const char *k, const char *v, void *cb)
 	else if (!strcmp(k, "merge.ff")) {
 		int boolval = git_config_maybe_bool(k, v);
 		if (0 <= boolval) {
-			allow_fast_forward = boolval;
+			fast_forward = boolval ? FF_ALLOW : FF_NO;
 		} else if (v && !strcmp(v, "only")) {
-			allow_fast_forward = 1;
-			fast_forward_only = 1;
+			fast_forward = FF_ONLY;
 		} /* do not barf on values from future versions of git */
 		return 0;
 	} else if (!strcmp(k, "merge.defaulttoupstream")) {
@@ -863,7 +877,7 @@ static int finish_automerge(struct commit *head,
 
 	free_commit_list(common);
 	parents = remoteheads;
-	if (!head_subsumed || !allow_fast_forward)
+	if (!head_subsumed || fast_forward == FF_NO)
 		commit_list_insert(head, &parents);
 	strbuf_addch(&merge_msg, '\n');
 	prepare_to_commit(remoteheads);
@@ -1008,7 +1022,7 @@ static void write_merge_state(struct commit_list *remoteheads)
 	if (fd < 0)
 		die_errno(_("Could not open '%s' for writing"), filename);
 	strbuf_reset(&buf);
-	if (!allow_fast_forward)
+	if (fast_forward == FF_NO)
 		strbuf_addf(&buf, "no-ff");
 	if (write_in_full(fd, buf.buf, buf.len) != buf.len)
 		die_errno(_("Could not write to '%s'"), filename);
@@ -1157,14 +1171,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 		show_diffstat = 0;
 
 	if (squash) {
-		if (!allow_fast_forward)
+		if (fast_forward == FF_NO)
 			die(_("You cannot combine --squash with --no-ff."));
 		option_commit = 0;
 	}
 
-	if (!allow_fast_forward && fast_forward_only)
-		die(_("You cannot combine --no-ff with --ff-only."));
-
 	if (!abort_current_merge) {
 		if (!argc) {
 			if (default_to_upstream)
@@ -1206,7 +1217,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 				"empty head"));
 		if (squash)
 			die(_("Squash commit into empty head not supported yet"));
-		if (!allow_fast_forward)
+		if (fast_forward == FF_NO)
 			die(_("Non-fast-forward commit does not make sense into "
 			    "an empty head"));
 		remoteheads = collect_parents(head_commit, &head_subsumed, argc, argv);
@@ -1294,11 +1305,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 			    sha1_to_hex(commit->object.sha1));
 		setenv(buf.buf, merge_remote_util(commit)->name, 1);
 		strbuf_reset(&buf);
-		if (!fast_forward_only &&
+		if (fast_forward != FF_ONLY &&
 		    merge_remote_util(commit) &&
 		    merge_remote_util(commit)->obj &&
 		    merge_remote_util(commit)->obj->type == OBJ_TAG)
-			allow_fast_forward = 0;
+			fast_forward = FF_NO;
 	}
 
 	if (option_edit < 0)
@@ -1315,7 +1326,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 
 	for (i = 0; i < use_strategies_nr; i++) {
 		if (use_strategies[i]->attr & NO_FAST_FORWARD)
-			allow_fast_forward = 0;
+			fast_forward = FF_NO;
 		if (use_strategies[i]->attr & NO_TRIVIAL)
 			allow_trivial = 0;
 	}
@@ -1345,7 +1356,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 		 */
 		finish_up_to_date("Already up-to-date.");
 		goto done;
-	} else if (allow_fast_forward && !remoteheads->next &&
+	} else if (fast_forward != FF_NO && !remoteheads->next &&
 			!common->next &&
 			!hashcmp(common->item->object.sha1, head_commit->object.sha1)) {
 		/* Again the most common case of merging one remote. */
@@ -1392,7 +1403,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 		 * only one common.
 		 */
 		refresh_cache(REFRESH_QUIET);
-		if (allow_trivial && !fast_forward_only) {
+		if (allow_trivial && fast_forward != FF_ONLY) {
 			/* See if it is really trivial. */
 			git_committer_info(IDENT_STRICT);
 			printf(_("Trying really trivial in-index merge...\n"));
@@ -1433,7 +1444,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 		}
 	}
 
-	if (fast_forward_only)
+	if (fast_forward == FF_ONLY)
 		die(_("Not possible to fast-forward, aborting."));
 
 	/* We are going to make a new commit. */
diff --git a/t/t7600-merge.sh b/t/t7600-merge.sh
index 460d8eb..3ff5fb8 100755
--- a/t/t7600-merge.sh
+++ b/t/t7600-merge.sh
@@ -497,9 +497,15 @@ test_expect_success 'combining --squash and --no-ff is refused' '
 	test_must_fail git merge --no-ff --squash c1
 '
 
-test_expect_success 'combining --ff-only and --no-ff is refused' '
-	test_must_fail git merge --ff-only --no-ff c1 &&
-	test_must_fail git merge --no-ff --ff-only c1
+test_expect_success 'option --ff-only overwrites --no-ff' '
+	git merge --no-ff --ff-only c1 &&
+	test_must_fail git merge --no-ff --ff-only c2
+'
+
+test_expect_success 'option --ff-only overwrites merge.ff=only config' '
+	git reset --hard c0 &&
+	test_config merge.ff only &&
+	git merge --no-ff c1
 '
 
 test_expect_success 'merge c0 with c1 (ff overrides no-ff)' '
-- 
1.8.1.4

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

* Re: [PATCH] merge: handle --ff/--no-ff/--ff-only as a tri-state option
  2013-07-02  8:42     ` Michael Haggerty
  2013-07-02 14:47       ` [PATCH v2] " Miklos Vajna
@ 2013-07-02 18:46       ` Junio C Hamano
  1 sibling, 0 replies; 12+ messages in thread
From: Junio C Hamano @ 2013-07-02 18:46 UTC (permalink / raw)
  To: Michael Haggerty; +Cc: Miklos Vajna, git

Michael Haggerty <mhagger@alum.mit.edu> writes:

> You allow --no-ff-only but ignore it, which I think is incorrect.  In
>
>     git merge --ff-only --no-ff-only [...]
>
> , the --no-ff-only should presumably cancel the effect of the previous
> --ff-only (i.e., be equivalent to "--ff").

Ideally, if we were starting from scratch and living in the "you
pick one out of three" world, we should forbid "--no-ff-only".  The
"--no-ff" option is spelled as if it is a negation of "--ff", and it
did start as such before "--ff-only" was introduced, but "--no-ff"
would have been better named "--always-create-merge", which is what
the option really means.

And in the tristate world, with mutually exclusive "--A", "--B", and
"--C" options, "--no-C" does not mean "I want to do A" at all.

If the existing code had allowed with "--no-ff-only" to defeat
configured merge.ff=only from the command line, then there may have
been users who are used to that behaviour, and we cannot break them,
but luckily or unluckily it does not work, so...

>> @@ -1157,14 +1181,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
>>  		show_diffstat = 0;
>>  
>>  	if (squash) {
>> -		if (!allow_fast_forward)
>> +		if (fast_forward == FF_NO)
>>  			die(_("You cannot combine --squash with --no-ff."));
>>  		option_commit = 0;
>>  	}
>>  
>
> So there is still a problem with setting merge.ff=false, namely that it
> prevents the use of --squash.  That's not good.  (I realize that you are
> not to blame for this pre-existing behavior.)
>
> How should --squash and the ff-related options interact?

Interesting point.

>     git merge --ff --squash
>     git merge --no-ff --squash
>
> I think these should just squash.
>
>     git merge --ff-only --squash
>
> I think this should definitely squash.  But perhaps it should require
> that HEAD be an ancestor of the branch to be merged?
>
>     git merge --squash --ff
>     git merge --squash --no-ff
>     git merge --squash --ff-only
>
> Should these do the same as the versions with the option order reversed?

As "--squash" is about _not_ moving the head but only updating the
working tree and the index, I personally think it should be treated
as an error if any of these "ff" options is explicitly given from
the command line.

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

* Re: [PATCH v2] merge: handle --ff/--no-ff/--ff-only as a tri-state option
  2013-07-02 14:47       ` [PATCH v2] " Miklos Vajna
@ 2013-07-02 20:12         ` Junio C Hamano
  0 siblings, 0 replies; 12+ messages in thread
From: Junio C Hamano @ 2013-07-02 20:12 UTC (permalink / raw)
  To: Miklos Vajna; +Cc: Michael Haggerty, git

Miklos Vajna <vmiklos@suse.cz> writes:

>>     if (fast_forward == FF_ONLY)
>>         fast_forward = FF_ALLOW;
>
> Do we really want --no-ff-only? I would rather just disable it, see the
> updated patch.

Sounds sane.

>> I'm no options guru, but I think it would be possible to implement --ff
>> and --no-ff without callbacks if you choose constants such that
>> FF_NO==0, something like:
>
> Indeed, done.

Yup, looks good.

> Actually there is also --no-squash, used by e.g. git-pull internally.
> You definitely don't want a five-state option. :-) So for now I would
> rather let --squash/--no-squash alone.

Sensible for this patch.

Will replace what was queued.  Thanks.

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

end of thread, other threads:[~2013-07-02 20:12 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2013-07-01  7:01 [PATCH] merge: allow using --no-ff and --ff-only at the same time Miklos Vajna
2013-07-01 14:52 ` Michael Haggerty
2013-07-01 15:27   ` Miklos Vajna
2013-07-01 15:38   ` Junio C Hamano
2013-07-01 16:10     ` Miklos Vajna
2013-07-01 16:43       ` Junio C Hamano
2013-07-01 19:54   ` [PATCH] merge: handle --ff/--no-ff/--ff-only as a tri-state option Miklos Vajna
2013-07-01 20:27     ` Junio C Hamano
2013-07-02  8:42     ` Michael Haggerty
2013-07-02 14:47       ` [PATCH v2] " Miklos Vajna
2013-07-02 20:12         ` Junio C Hamano
2013-07-02 18:46       ` [PATCH] " Junio C Hamano

Code repositories for project(s) associated with this public inbox

	https://80x24.org/mirrors/git.git

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 read-only IMAP folder(s) and NNTP newsgroup(s).