Skip to contentSkip to conversation

Add default merge options for all branches

PATCH In review Michael Grubb 8 participants 46 messages Jun 15, 2016 Documentation

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 Add default merge options for all branches

Add support for branch.*.mergeoptions for setting default options for all branches. This new value shares semantics with the existing branch.<name>.mergeoptions variable. If a branch specific value is found, that value will be used.

The need for this arises from the fact that there is currently not an easy way to set merge options for all branches. Instead of having to specify merge options for each individual branch there should be a way to set defaults for all branches and then override a specific branch's options.

The approach taken is to make note of whether a branch specific mergeoptions key has been seen and only apply the global value if it hasn't.

---
 Documentation/git-merge.txt |    3 +++
 builtin/merge.c             |   27 ++++++++++++++++++++++-----
 t/t7600-merge.sh            |   27 +++++++++++++++++++++++++++
 3 files changed, 52 insertions(+), 5 deletions(-)
Documentation/git-merge.txt +3 -0
index e2e6aba..eaab3e4 100644
@@ -307,6 +307,9 @@ branch.<name>.mergeoptions::
 	Sets default options for merging into branch <name>. The syntax and
 	supported options are the same as those of 'git merge', but option
 	values containing whitespace characters are currently not supported.
+	The special value '*' for <name> may be used to configure default
+	options for all branches.  Values for specific branch names will
+	override the this default.
 
 SEE ALSO
 --------
builtin/merge.c +22 -5
index 0bdd19a..9e5b6bd 100644
@@ -505,9 +505,18 @@ cleanup:
 
 static int git_merge_config(const char *k, const char *v, void *cb)
 {
-	if (branch && !prefixcmp(k, "branch.") &&
-		!prefixcmp(k + 7, branch) &&
-		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+	static int branch_merge_options_set = 0;
+	int merge_option_mode = 0;
+
+	if (!strcmp(k, "branch.*.mergeoptions"))
+		merge_option_mode = 1;
+	else if (branch && !prefixcmp(k, "branch.") &&
+			 !prefixcmp(k + 7, branch) &&
+			 !strcmp(k + 7 + strlen(branch), ".mergeoptions"))
+		merge_option_mode = 2;
+
+	if ((merge_option_mode == 1 && !branch_merge_options_set) ||
+		  merge_option_mode == 2) {
 		const char **argv;
 		int argc;
 		char *buf;
@@ -515,14 +524,22 @@ static int git_merge_config(const char *k, const char *v, void *cb)
 		buf = xstrdup(v);
 		argc = split_cmdline(buf, &argv);
 		if (argc < 0)
-			die(_("Bad branch.%s.mergeoptions string: %s"), branch,
-			    split_cmdline_strerror(argc));
+		{
+			if (merge_option_mode == 1)
+				die(_("Bad merge.mergeoptions string: %s"), 
+					split_cmdline_strerror(argc));
+			else
+				die(_("Bad branch.%s.mergeoptions string: %s"), branch,
+					split_cmdline_strerror(argc));
+		}
 		argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
 		memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
 		argc++;
 		parse_options(argc, argv, NULL, builtin_merge_options,
 			      builtin_merge_usage, 0);
 		free(buf);
+		if (merge_option_mode == 2)
+			branch_merge_options_set = 1;
 	}
 
 	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
t/t7600-merge.sh +27 -0
index 87d5d78..bfb7348 100755
@@ -415,6 +415,33 @@ test_expect_success 'merge c0 with c1 (no-ff)' '
 
 test_debug 'git log --graph --decorate --oneline --all'
 
+test_expect_success 'merge c0 with c1 (global no-ff)' '
+	git reset --hard c0 &&
+	git config --unset branch.master.mergeoptions &&
+	git config "branch.*.mergeoptions" "--no-ff" &&
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section "branch.*" &&
+	verify_merge file result.1 &&
+	verify_parents $c0 $c1
+'
+
+test_debug 'git log --graph --decorate --oneline --all'
+
+test_expect_success 'combine merge.mergeoptions with branch.x.mergeoptions' '
+	git reset --hard c0 &&
+	git config --remove-section branch.master &&
+	git config "branch.*.mergeoptions" "--no-ff" &&
+	git config branch.master.mergeoptions "--ff" &&
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section "branch.*" &&
+	verify_merge file result.1 &&
+	verify_parents "$c0"
+'
+
+test_debug 'git log --graph --decorate --oneline --all'
+
 test_expect_success 'combining --squash and --no-ff is refused' '
 	test_must_fail git merge --squash --no-ff c1 &&
 	test_must_fail git merge --no-ff --squash c1
Signature · 1 line
1.7.5
#1 lore raw

builtin/merge.c static int git_merge_config(const char *k, const char *v, void *cb) · line 528

Quoted 5 lines · Michael Grubb
builtin/merge.c +2 -2
 		if (argc < 0)
-			die(_("Bad branch.%s.mergeoptions string: %s"), branch,
-			    split_cmdline_strerror(argc));
+		{
+			if (merge_option_mode == 1)

checkpatch.pl from the kernel tree may help you - { belongs to the end of the previous line.

Thanks.

#2 lore raw

PATCH v3 1/1 Add default merge options for all branches

In reply to Michael Grubb

Add support for branch.*.mergeoptions for setting default options for all branches. This new value shares semantics with the existing branch.<name>.mergeoptions variable. If a branch specific value is found, that value will be used.

The need for this arises from the fact that there is currently not an easy way to set merge options for all branches. Instead of having to specify merge options for each individual branch there should be a way to set defaults for all branches and then override a specific branch's options.

The approach taken is to make note of whether a branch specific mergeoptions key has been seen and only apply the global value if it hasn't.

---
 Documentation/git-merge.txt |    3 +++
 builtin/merge.c             |   40 +++++++++++++++++++++++++++++++++-------
 t/t7600-merge.sh            |   27 +++++++++++++++++++++++++++
 3 files changed, 63 insertions(+), 7 deletions(-)
Documentation/git-merge.txt +3 -0
index e2e6aba..eaab3e4 100644
@@ -307,6 +307,9 @@ branch.<name>.mergeoptions::
 	Sets default options for merging into branch <name>. The syntax and
 	supported options are the same as those of 'git merge', but option
 	values containing whitespace characters are currently not supported.
+	The special value '*' for <name> may be used to configure default
+	options for all branches.  Values for specific branch names will
+	override the this default.
 
 SEE ALSO
 --------
builtin/merge.c +33 -7
index d171c63..9fe129f 100644
@@ -32,6 +32,13 @@
 #define NO_FAST_FORWARD (1<<2)
 #define NO_TRIVIAL      (1<<3)
 
+#define MERGEOPTIONS_DEFAULT (1<<0)
+#define MERGEOPTIONS_BRANCH (1<<1)
+
+struct merge_options_cb {
+	int override_default;
+};
+
 struct strategy {
 	const char *name;
 	unsigned attr;
@@ -505,24 +512,42 @@ cleanup:
 
 static int git_merge_config(const char *k, const char *v, void *cb)
 {
-	if (branch && !prefixcmp(k, "branch.") &&
-		!prefixcmp(k + 7, branch) &&
-		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+	int merge_option_mode = 0;
+	struct merge_options_cb *merge_options =
+		(struct merge_options_cb *)cb;
+
+	if (!strcmp(k, "branch.*.mergeoptions"))
+		merge_option_mode = MERGEOPTIONS_DEFAULT;
+	else if (branch && !prefixcmp(k, "branch.") &&
+			 !prefixcmp(k + 7, branch) &&
+			 !strcmp(k + 7 + strlen(branch), ".mergeoptions"))
+		merge_option_mode = MERGEOPTIONS_BRANCH;
+
+	if ((merge_option_mode == MERGEOPTIONS_DEFAULT &&
+		!merge_options->override_default) ||
+		merge_option_mode == MERGEOPTIONS_BRANCH) {
 		const char **argv;
 		int argc;
 		char *buf;
 
 		buf = xstrdup(v);
 		argc = split_cmdline(buf, &argv);
-		if (argc < 0)
-			die(_("Bad branch.%s.mergeoptions string: %s"), branch,
-			    split_cmdline_strerror(argc));
+		if (argc < 0) {
+			if (merge_option_mode == 1)
+				die(_("Bad merge.mergeoptions string: %s"), 
+					split_cmdline_strerror(argc));
+			else
+				die(_("Bad branch.%s.mergeoptions string: %s"), branch,
+					split_cmdline_strerror(argc));
+		}
 		argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
 		memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
 		argc++;
 		parse_options(argc, argv, NULL, builtin_merge_options,
 			      builtin_merge_usage, 0);
 		free(buf);
+		if (merge_option_mode == MERGEOPTIONS_BRANCH)
+			merge_options->override_default = 1;
 	}
 
 	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
@@ -987,6 +1012,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 	const char *head_arg;
 	int flag, head_invalid = 0, i;
 	int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
+	struct merge_options_cb merge_options = {0};
 	struct commit_list *common = NULL;
 	const char *best_strategy = NULL, *wt_strategy = NULL;
 	struct commit_list **remotes = &remoteheads;
@@ -1004,7 +1030,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 	if (is_null_sha1(head))
 		head_invalid = 1;
 
-	git_config(git_merge_config, NULL);
+	git_config(git_merge_config, &merge_options);
 
 	/* for color.ui */
 	if (diff_use_color_default == -1)
t/t7600-merge.sh +27 -0
index e84e822..cea2b31 100755
@@ -415,6 +415,33 @@ test_expect_success 'merge c0 with c1 (no-ff)' '
 
 test_debug 'git log --graph --decorate --oneline --all'
 
+test_expect_success 'merge c0 with c1 (global no-ff)' '
+	git reset --hard c0 &&
+	git config --unset branch.master.mergeoptions &&
+	git config "branch.*.mergeoptions" "--no-ff" &&
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section "branch.*" &&
+	verify_merge file result.1 &&
+	verify_parents $c0 $c1
+'
+
+test_debug 'git log --graph --decorate --oneline --all'
+
+test_expect_success 'combine merge.mergeoptions with branch.x.mergeoptions' '
+	git reset --hard c0 &&
+	git config --remove-section branch.master &&
+	git config "branch.*.mergeoptions" "--no-ff" &&
+	git config branch.master.mergeoptions "--ff" &&
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section "branch.*" &&
+	verify_merge file result.1 &&
+	verify_parents "$c0"
+'
+
+test_debug 'git log --graph --decorate --oneline --all'
+
 test_expect_success 'combining --squash and --no-ff is refused' '
 	test_must_fail git merge --squash --no-ff c1 &&
 	test_must_fail git merge --no-ff --squash c1
Signature · 1 line
1.7.5
Reviewed-by on v3 1/1
#3 lore raw

Hi,

Quoted 4 lines · Michael Grubb

Add support for branch.*.mergeoptions for setting default options for all branches. This new value shares semantics with the existing branch.<name>.mergeoptions variable. If a branch specific value is found, that value will be used.

So in the future one might be able to do things like

	[branch "git-gui/*"]
		mergeoptions = -s subtree

Interesting.

Quoted 2 lines

The need for this arises from the fact that there is currently not an easy way to set merge options for all branches.

I'm curious: what merge options/workflows does this tend to be useful for? The above explanation seems a bit abstract (though already convincing).

Quoted 3 lines

The approach taken is to make note of whether a branch specific mergeoptions key has been seen and only apply the global value if it hasn't.

What happens if the global value is seen first?

On to the code. Warning: nitpicks ahead.

[...]

Review line 36

Quoted 7 lines

+++ b/builtin/merge.c

(quoted diff) +2 -0
@@ -32,6 +32,13 @@
 #define NO_FAST_FORWARD (1<<2)
 #define NO_TRIVIAL      (1<<3)
 
+#define MERGEOPTIONS_DEFAULT (1<<0)
+#define MERGEOPTIONS_BRANCH (1<<1)

Are these bitflags?

Review cleanup: · line 517

Quoted 7 lines
(quoted diff) +3 -0
@@ -505,24 +512,42 @@ cleanup:
 
 static int git_merge_config(const char *k, const char *v, void *cb)
 {
+	int merge_option_mode = 0;
+	struct merge_options_cb *merge_options =
+		(struct merge_options_cb *)cb;

This cast should not needed, I'd think.

[...]

Quoted 4 lines
(quoted diff) +1 -3
-	if (branch && !prefixcmp(k, "branch.") &&
-		!prefixcmp(k + 7, branch) &&
-		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+	if (!strcmp(k, "branch.*.mergeoptions"))

Review cleanup: · line 528

Quoted 10 lines
(quoted diff) +9 -0
+		merge_option_mode = MERGEOPTIONS_DEFAULT;
+	else if (branch && !prefixcmp(k, "branch.") &&
+			 !prefixcmp(k + 7, branch) &&
+			 !strcmp(k + 7 + strlen(branch), ".mergeoptions"))
+		merge_option_mode = MERGEOPTIONS_BRANCH;
+
+	if ((merge_option_mode == MERGEOPTIONS_DEFAULT &&
+		!merge_options->override_default) ||
+		merge_option_mode == MERGEOPTIONS_BRANCH) {
 		const char **argv;

It is hard to see at a glance where the "if" condition ends and the body begins. Why not

	if ((merge_option_mode == MERGEOPTIONS_DEFAULT &&
	     !merge_options->override_default) ||
	    merge_option_mode == MERGEOPTIONS_BRANCH) {
		const char **argv;
		...

or

	if (merge_option_mode == MERGEOPTIONS_BRANCH ? 1 :
	    merge_option_mode == MERGEOPTIONS_DEFAULT ?
			!merge_options->override_default : 0) {
		const char **argv;
		...

or even

	if (merge_option_mode == MERGEOPTIONS_DEFAULT &&
	    merge_options->override_default)
		merge_option_mode = 0;

	if (merge_option_mode) {
		const char **argv;
		...

?

Review cleanup: · line 537

Quoted 12 lines
(quoted diff) +4 -3
 		int argc;
 		char *buf;
 
 		buf = xstrdup(v);
 		argc = split_cmdline(buf, &argv);
-		if (argc < 0)
-			die(_("Bad branch.%s.mergeoptions string: %s"), branch,
-			    split_cmdline_strerror(argc));
+		if (argc < 0) {
+			if (merge_option_mode == 1)
+				die(_("Bad merge.mergeoptions string: %s"), 
+					split_cmdline_strerror(argc));

merge.*.mergeoptions, no?

Review cleanup: · line 549

Quoted 12 lines
(quoted diff) +6 -0
+			else
+				die(_("Bad branch.%s.mergeoptions string: %s"), branch,
+					split_cmdline_strerror(argc));
+		}
 		argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
 		memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
 		argc++;
 		parse_options(argc, argv, NULL, builtin_merge_options,
 			      builtin_merge_usage, 0);
 		free(buf);
+		if (merge_option_mode == MERGEOPTIONS_BRANCH)
+			merge_options->override_default = 1;

Could be clearer to put this next to the code that checks override_default.

[...]

t/t7600-merge.sh test_expect_success 'merge c0 with c1 (no-ff)' ' · line 420

Quoted 9 lines
t/t7600-merge.sh +3 -0
@@ -415,6 +415,33 @@ test_expect_success 'merge c0 with c1 (no-ff)' '
 
 test_debug 'git log --graph --decorate --oneline --all'
 
+test_expect_success 'merge c0 with c1 (global no-ff)' '
+	git reset --hard c0 &&
+	git config --unset branch.master.mergeoptions &&

Better to make that

test_might_fail git config --unset ...

so it will still work if earlier tests stop setting that variable.

t/t7600-merge.sh test_expect_success 'merge c0 with c1 (no-ff)' ' · line 429

Quoted 9 lines
t/t7600-merge.sh +9 -0
+	git config "branch.*.mergeoptions" "--no-ff" &&
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section "branch.*" &&
+	verify_merge file result.1 &&
+	verify_parents $c0 $c1
+'
+
+test_debug 'git log --graph --decorate --oneline --all'

Yuck. Did anything come of the idea of a --between-tests option to use an arbitrary command here automatically? (Not your fault.)

t/t7600-merge.sh test_expect_success 'merge c0 with c1 (no-ff)' ' · line 433

Quoted 4 lines
t/t7600-merge.sh +4 -0
+
+test_expect_success 'combine merge.mergeoptions with branch.x.mergeoptions' '
+	git reset --hard c0 &&
+	git config --remove-section branch.master &&

Could make sense to use test_might_fail for this one, too.

t/t7600-merge.sh test_expect_success 'merge c0 with c1 (no-ff)' ' · line 443

Quoted 10 lines
t/t7600-merge.sh +10 -0
+	git config "branch.*.mergeoptions" "--no-ff" &&
+	git config branch.master.mergeoptions "--ff" &&
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section "branch.*" &&
+	verify_merge file result.1 &&
+	verify_parents "$c0"
+'
+
+test_debug 'git log --graph --decorate --oneline --all'

Nice, a clean patch with a few reasonable tests.

With whichever of the changes below make sense,

Thanks.

---
 builtin/merge.c  |   37 ++++++++++++++++++++-----------------
 t/t7600-merge.sh |    4 ++--
 2 files changed, 22 insertions(+), 19 deletions(-)
builtin/merge.c +20 -17
index 9fe129f..7156e92 100644
@@ -32,8 +32,8 @@
 #define NO_FAST_FORWARD (1<<2)
 #define NO_TRIVIAL      (1<<3)
 
-#define MERGEOPTIONS_DEFAULT (1<<0)
-#define MERGEOPTIONS_BRANCH (1<<1)
+#define MERGEOPTIONS_DEFAULT 1
+#define MERGEOPTIONS_BRANCH 2
 
 struct merge_options_cb {
 	int override_default;
@@ -513,8 +513,7 @@ cleanup:
 static int git_merge_config(const char *k, const char *v, void *cb)
 {
 	int merge_option_mode = 0;
-	struct merge_options_cb *merge_options =
-		(struct merge_options_cb *)cb;
+	struct merge_options_cb *merge_options = cb;
 
 	if (!strcmp(k, "branch.*.mergeoptions"))
 		merge_option_mode = MERGEOPTIONS_DEFAULT;
@@ -523,31 +522,35 @@ static int git_merge_config(const char *k, const char *v, void *cb)
 			 !strcmp(k + 7 + strlen(branch), ".mergeoptions"))
 		merge_option_mode = MERGEOPTIONS_BRANCH;
 
-	if ((merge_option_mode == MERGEOPTIONS_DEFAULT &&
-		!merge_options->override_default) ||
-		merge_option_mode == MERGEOPTIONS_BRANCH) {
+	/*
+	 * If an applicable [branch "foo"] mergeoptions setting was
+	 * seen already, let it mask the [branch "*"] defaults.
+	 */
+	if (merge_options->override_default &&
+	    merge_option_mode == MERGEOPTIONS_DEFAULT)
+		merge_option_mode = 0;
+
+	if (merge_option_mode == MERGEOPTIONS_BRANCH)
+		merge_options->override_default = 1;
+
+	if (merge_option_mode) {
 		const char **argv;
 		int argc;
 		char *buf;
 
 		buf = xstrdup(v);
 		argc = split_cmdline(buf, &argv);
-		if (argc < 0) {
-			if (merge_option_mode == 1)
-				die(_("Bad merge.mergeoptions string: %s"), 
-					split_cmdline_strerror(argc));
-			else
-				die(_("Bad branch.%s.mergeoptions string: %s"), branch,
-					split_cmdline_strerror(argc));
-		}
+		if (argc < 0)
+			die(_("Bad merge.%s.mergeoptions string: %s"), 
+			    merge_option_mode == MERGEOPTIONS_DEFAULT ?
+							"*" : branch,
+			    split_cmdline_strerror(argc));
 		argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
 		memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
 		argc++;
 		parse_options(argc, argv, NULL, builtin_merge_options,
 			      builtin_merge_usage, 0);
 		free(buf);
-		if (merge_option_mode == MERGEOPTIONS_BRANCH)
-			merge_options->override_default = 1;
 	}
 
 	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
t/t7600-merge.sh +2 -2
index cea2b31..ff807f4 100755
@@ -417,7 +417,7 @@ test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c0 with c1 (global no-ff)' '
 	git reset --hard c0 &&
-	git config --unset branch.master.mergeoptions &&
+	test_might_fail git config --unset branch.master.mergeoptions &&
 	git config "branch.*.mergeoptions" "--no-ff" &&
 	test_tick &&
 	git merge c1 &&
@@ -430,7 +430,7 @@ test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'combine merge.mergeoptions with branch.x.mergeoptions' '
 	git reset --hard c0 &&
-	git config --remove-section branch.master &&
+	test_might_fail git config --remove-section branch.master &&
 	git config "branch.*.mergeoptions" "--no-ff" &&
 	git config branch.master.mergeoptions "--ff" &&
 	test_tick &&
Signature · 1 line
1.7.5
#4 lore raw
Quoted 7 lines · Jonathan Nieder
Quoted 3 lines · Michael Grubb

The approach taken is to make note of whether a branch specific mergeoptions key has been seen and only apply the global value if it hasn't.

What happens if the global value is seen first?

[...]

Quoted 2 lines

With whichever of the changes below make sense,

Agh, what I meant is "except as noted above". I am still worried about (and have not carefully reviewed) the following case:

	[branch "*"]
		mergeoptions = --foo

	[branch "topic"]
		mergeoptions = --bar

Does it produce different behavior from

	[branch "topic"]
		mergeoptions = --bar

	[branch "*"]
		mergeoptions = --foo

? If so, is that a good thing, and is it documented?

Sorry for the lack of clarity.

#5 lore raw
In reply to Jonathan Nieder
Quoted 14 lines · Jonathan Nieder

Hi,

Quoted 4 lines · Michael Grubb

Add support for branch.*.mergeoptions for setting default options for all branches. This new value shares semantics with the existing branch.<name>.mergeoptions variable. If a branch specific value is found, that value will be used.

So in the future one might be able to do things like

	[branch "git-gui/*"]
		mergeoptions = -s subtree
Quoted 8 lines

Interesting.

Quoted 2 lines

The need for this arises from the fact that there is currently not an easy way to set merge options for all branches.

I'm curious: what merge options/workflows does this tend to be useful for? The above explanation seems a bit abstract (though already convincing).

For myself I've adopted Vincent Driessen's branching model
(http://nvie.com/posts/a-successful-git-branching-model/)
Which specifically recommends using --no-ff when merging to preserve the history of the existence of a topic branch once those branches are removed. But it would equally be beneficial for folks that want the --log, etc option turned on for every merge.

Quoted 6 lines
Quoted 4 lines

The approach taken is to make note of whether a branch specific mergeoptions key has been seen and only apply the global value if it hasn't.

What happens if the global value is seen first?

I will code a test for this, but if the global option is seen first then those options are used, unless/until a branch specific option is seen which will override the globals. If the branch specific option is seen first then the global options are ignored. (I did test this btw, but I didn't add it in the unit tests, bad me).

Quoted 9 lines

On to the code. Warning: nitpicks ahead.

[...]

Quoted 5 lines

+++ b/builtin/merge.c

builtin/merge.c +0 -0
@@ -32,6 +32,13 @@
 #define NO_FAST_FORWARD (1<<2)
 #define NO_TRIVIAL      (1<<3)
 
Quoted 4 lines

Review line 36

Quoted 2 lines
(quoted diff) +2 -0
+#define MERGEOPTIONS_DEFAULT (1<<0)
+#define MERGEOPTIONS_BRANCH (1<<1)

Are these bitflags?

I had been using literals in the git_merge_config function but thought better of it and added these defines. I'm not really using them as bitflags right now. Perhaps an enum, or just plain literals are better. I'm not clear what the best practice is in this case, so I'm certainly open to a better/more clear way of doing this.

Quoted 10 lines

Review cleanup: · line 517

Quoted 8 lines
(quoted diff) +3 -0
@@ -505,24 +512,42 @@ cleanup:
 
 static int git_merge_config(const char *k, const char *v, void *cb)
 {
+	int merge_option_mode = 0;
+	struct merge_options_cb *merge_options =
+		(struct merge_options_cb *)cb;

This cast should not needed, I'd think.

Indeed. It has been removed.

Quoted 11 lines

[...]

Quoted 9 lines
(quoted diff) +6 -3
-	if (branch && !prefixcmp(k, "branch.") &&
-		!prefixcmp(k + 7, branch) &&
-		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+	if (!strcmp(k, "branch.*.mergeoptions"))
+		merge_option_mode = MERGEOPTIONS_DEFAULT;
+	else if (branch && !prefixcmp(k, "branch.") &&
+			 !prefixcmp(k + 7, branch) &&
+			 !strcmp(k + 7 + strlen(branch), ".mergeoptions"))
+		merge_option_mode = MERGEOPTIONS_BRANCH;
Quoted 8 lines

Review

Quoted 5 lines
(quoted diff) +4 -0
+
+	if ((merge_option_mode == MERGEOPTIONS_DEFAULT &&
+		!merge_options->override_default) ||
+		merge_option_mode == MERGEOPTIONS_BRANCH) {
 		const char **argv;

It is hard to see at a glance where the "if" condition ends and the body begins. Why not

I completely agree, though believe it or not, this was what the original code looked like so I was following it's lead. Here is what the original looks like:

	if (branch && !prefixcmp(k, "branch.") &&
		!prefixcmp(k + 7, branch) &&
		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
		const char **argv;
		int argc;
		char *buf;

I would, of course, be happy to clear this up.

Quoted 42 lines
	if ((merge_option_mode == MERGEOPTIONS_DEFAULT &&
	     !merge_options->override_default) ||
	    merge_option_mode == MERGEOPTIONS_BRANCH) {
		const char **argv;
		...

or

	if (merge_option_mode == MERGEOPTIONS_BRANCH ? 1 :
	    merge_option_mode == MERGEOPTIONS_DEFAULT ?
			!merge_options->override_default : 0) {
		const char **argv;
		...

or even

	if (merge_option_mode == MERGEOPTIONS_DEFAULT &&
	    merge_options->override_default)
		merge_option_mode = 0;

	if (merge_option_mode) {
		const char **argv;
		...

?

builtin/merge.c static int git_merge_config(const char *k, const char *v, void *cb) · line 547

Quoted 12 lines
builtin/merge.c +4 -3
 		int argc;
 		char *buf;
 
 		buf = xstrdup(v);
 		argc = split_cmdline(buf, &argv);
-		if (argc < 0)
-			die(_("Bad branch.%s.mergeoptions string: %s"), branch,
-			    split_cmdline_strerror(argc));
+		if (argc < 0) {
+			if (merge_option_mode == 1)
+				die(_("Bad merge.mergeoptions string: %s"), 
+					split_cmdline_strerror(argc));

merge.*.mergeoptions, no?

Quoted 15 lines

builtin/merge.c static int git_merge_config(const char *k, const char *v, void *cb)

Quoted 12 lines
builtin/merge.c +6 -0
+			else
+				die(_("Bad branch.%s.mergeoptions string: %s"), branch,
+					split_cmdline_strerror(argc));
+		}
 		argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
 		memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
 		argc++;
 		parse_options(argc, argv, NULL, builtin_merge_options,
 			      builtin_merge_usage, 0);
 		free(buf);
+		if (merge_option_mode == MERGEOPTIONS_BRANCH)
+			merge_options->override_default = 1;

Could be clearer to put this next to the code that checks override_default.

Yes. I had put this there originally so that it would only be set if there were no errors in parsing the options but after chasing the rabbit down the whole, if there are errors parse_options won't return, so I'll move this up closer to the override_default check.

Quoted 39 lines

[...]

t/t7600-merge.sh test_expect_success 'merge c0 with c1 (no-ff)' ' · line 420

Quoted 9 lines
t/t7600-merge.sh +3 -0
@@ -415,6 +415,33 @@ test_expect_success 'merge c0 with c1 (no-ff)' '
 
 test_debug 'git log --graph --decorate --oneline --all'
 
+test_expect_success 'merge c0 with c1 (global no-ff)' '
+	git reset --hard c0 &&
+	git config --unset branch.master.mergeoptions &&

Better to make that

test_might_fail git config --unset ...

so it will still work if earlier tests stop setting that variable.

t/t7600-merge.sh test_expect_success 'merge c0 with c1 (no-ff)' ' · line 429

Quoted 9 lines
t/t7600-merge.sh +9 -0
+	git config "branch.*.mergeoptions" "--no-ff" &&
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section "branch.*" &&
+	verify_merge file result.1 &&
+	verify_parents $c0 $c1
+'
+
+test_debug 'git log --graph --decorate --oneline --all'

Yuck. Did anything come of the idea of a --between-tests option to use an arbitrary command here automatically? (Not your fault.)

t/t7600-merge.sh test_expect_success 'merge c0 with c1 (no-ff)' ' · line 433

Quoted 4 lines
t/t7600-merge.sh +4 -0
+
+test_expect_success 'combine merge.mergeoptions with branch.x.mergeoptions' '
+	git reset --hard c0 &&
+	git config --remove-section branch.master &&

Could make sense to use test_might_fail for this one, too.

Quoted 12 lines

t/t7600-merge.sh test_expect_success 'merge c0 with c1 (no-ff)' ' · line 443

Quoted 10 lines
t/t7600-merge.sh +10 -0
+	git config "branch.*.mergeoptions" "--no-ff" &&
+	git config branch.master.mergeoptions "--ff" &&
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section "branch.*" &&
+	verify_merge file result.1 &&
+	verify_parents "$c0"
+'
+
+test_debug 'git log --graph --decorate --oneline --all'

Nice, a clean patch with a few reasonable tests.

Thanks for all the constructive criticism, Jonathan. I will make the recommended changes and post a new patch.

Quoted 106 lines

With whichever of the changes below make sense,

Thanks.

---
 builtin/merge.c  |   37 ++++++++++++++++++++-----------------
 t/t7600-merge.sh |    4 ++--
 2 files changed, 22 insertions(+), 19 deletions(-)
builtin/merge.c +20 -17
index 9fe129f..7156e92 100644
@@ -32,8 +32,8 @@
 #define NO_FAST_FORWARD (1<<2)
 #define NO_TRIVIAL      (1<<3)
 
-#define MERGEOPTIONS_DEFAULT (1<<0)
-#define MERGEOPTIONS_BRANCH (1<<1)
+#define MERGEOPTIONS_DEFAULT 1
+#define MERGEOPTIONS_BRANCH 2
 
 struct merge_options_cb {
 	int override_default;
@@ -513,8 +513,7 @@ cleanup:
 static int git_merge_config(const char *k, const char *v, void *cb)
 {
 	int merge_option_mode = 0;
-	struct merge_options_cb *merge_options =
-		(struct merge_options_cb *)cb;
+	struct merge_options_cb *merge_options = cb;
 
 	if (!strcmp(k, "branch.*.mergeoptions"))
 		merge_option_mode = MERGEOPTIONS_DEFAULT;
@@ -523,31 +522,35 @@ static int git_merge_config(const char *k, const char *v, void *cb)
 			 !strcmp(k + 7 + strlen(branch), ".mergeoptions"))
 		merge_option_mode = MERGEOPTIONS_BRANCH;
 
-	if ((merge_option_mode == MERGEOPTIONS_DEFAULT &&
-		!merge_options->override_default) ||
-		merge_option_mode == MERGEOPTIONS_BRANCH) {
+	/*
+	 * If an applicable [branch "foo"] mergeoptions setting was
+	 * seen already, let it mask the [branch "*"] defaults.
+	 */
+	if (merge_options->override_default &&
+	    merge_option_mode == MERGEOPTIONS_DEFAULT)
+		merge_option_mode = 0;
+
+	if (merge_option_mode == MERGEOPTIONS_BRANCH)
+		merge_options->override_default = 1;
+
+	if (merge_option_mode) {
 		const char **argv;
 		int argc;
 		char *buf;
 
 		buf = xstrdup(v);
 		argc = split_cmdline(buf, &argv);
-		if (argc < 0) {
-			if (merge_option_mode == 1)
-				die(_("Bad merge.mergeoptions string: %s"), 
-					split_cmdline_strerror(argc));
-			else
-				die(_("Bad branch.%s.mergeoptions string: %s"), branch,
-					split_cmdline_strerror(argc));
-		}
+		if (argc < 0)
+			die(_("Bad merge.%s.mergeoptions string: %s"), 
+			    merge_option_mode == MERGEOPTIONS_DEFAULT ?
+							"*" : branch,
+			    split_cmdline_strerror(argc));
 		argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
 		memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
 		argc++;
 		parse_options(argc, argv, NULL, builtin_merge_options,
 			      builtin_merge_usage, 0);
 		free(buf);
-		if (merge_option_mode == MERGEOPTIONS_BRANCH)
-			merge_options->override_default = 1;
 	}
 
 	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
t/t7600-merge.sh +2 -2
index cea2b31..ff807f4 100755
@@ -417,7 +417,7 @@ test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c0 with c1 (global no-ff)' '
 	git reset --hard c0 &&
-	git config --unset branch.master.mergeoptions &&
+	test_might_fail git config --unset branch.master.mergeoptions &&
 	git config "branch.*.mergeoptions" "--no-ff" &&
 	test_tick &&
 	git merge c1 &&
@@ -430,7 +430,7 @@ test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'combine merge.mergeoptions with branch.x.mergeoptions' '
 	git reset --hard c0 &&
-	git config --remove-section branch.master &&
+	test_might_fail git config --remove-section branch.master &&
 	git config "branch.*.mergeoptions" "--no-ff" &&
 	git config branch.master.mergeoptions "--ff" &&
 	test_tick &&
#6 lore raw
In reply to Jonathan Nieder
Quoted 5 lines · Jonathan Nieder

So in the future one might be able to do things like

	[branch "git-gui/*"]
		mergeoptions = -s subtree
Quoted 7 lines

Interesting.

Quoted 2 lines

The need for this arises from the fact that there is currently not an easy way to set merge options for all branches.

I'm curious: what merge options/workflows does this tend to be useful for?

I actually am curious myself, too. I want to see a real-life example.

The "git-gui/*" example you gave is unfortunately not it. It specifies the branch at the wrong end. Whether I am merging into "master" or "next", I would want "-s subtree" when I am merging "git-gui/*" project, but all my merges to "master" or "next" do not necessarily want to use "-s subtree".

It might turn out to be that "branch.<name>.mergeoptions" is a ill-conceived idea to begin with.

Quoted 5 lines
Quoted 3 lines

The approach taken is to make note of whether a branch specific mergeoptions key has been seen and only apply the global value if it hasn't.

What happens if the global value is seen first?

If implemented correctly, it should use the specific one and fall back to the wildcard one. Another issue is if the values should be cumulative or overriding, but in the remainder I'd assume we want overriding.

Quoted 9 lines

Review cleanup: · line 517

Quoted 7 lines
(quoted diff) +3 -0
@@ -505,24 +512,42 @@ cleanup:
 
 static int git_merge_config(const char *k, const char *v, void *cb)
 {
+	int merge_option_mode = 0;
+	struct merge_options_cb *merge_options =
+		(struct merge_options_cb *)cb;

This cast should not needed, I'd think.

Correct. That is the whole point of using (void *) as a parameter, so that it can be assigned to the real expected type easily without cast.

Quoted 10 lines
Quoted 10 lines
(quoted diff) +10 -0
+	if (!strcmp(k, "branch.*.mergeoptions"))
+		merge_option_mode = MERGEOPTIONS_DEFAULT;
+	else if (branch && !prefixcmp(k, "branch.") &&
+			 !prefixcmp(k + 7, branch) &&
+			 !strcmp(k + 7 + strlen(branch), ".mergeoptions"))
+		merge_option_mode = MERGEOPTIONS_BRANCH;
+
+	if ((merge_option_mode == MERGEOPTIONS_DEFAULT &&
+		!merge_options->override_default) ||
+		merge_option_mode == MERGEOPTIONS_BRANCH) {
Quoted 8 lines

builtin/merge.c static int git_merge_config(const char *k, const char *v, void *cb) · line 537

Quoted 1 line
builtin/merge.c +0 -0
 		const char **argv;

It is hard to see at a glance where the "if" condition ends and the body begins. Why not
...
or even
...
?

Why not have two string pointers in merge_options_cb structure that are initialized to NULL and holds the matched config key and the value?

When we are looking at a (k, v) pair in this function, if there is no previous key in cb, we store (k, v) in cb and return. If there already is a previous key, we see if k is more specific than that key, and replace (k, v) in cb with what we currently have. Otherwise we do not do anything.

When git_config() returns, the caller will find the final value in cb.

Quoted 4 lines
Quoted 4 lines
(quoted diff) +4 -0
+	git config "branch.*.mergeoptions" "--no-ff" &&
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section "branch.*" &&
Quoted 8 lines

Review cleanup: · line 537

Quoted 5 lines
(quoted diff) +5 -0
+	verify_merge file result.1 &&
+	verify_parents $c0 $c1
+'
+
+test_debug 'git log --graph --decorate --oneline --all'

Yuck. Did anything come of the idea of a --between-tests option to use an arbitrary command here automatically? (Not your fault.)

I actually think test_debug should go inside the previous test. Why not have it immediately after "git merge c1" above?

#7 lore raw
Quoted 9 lines · Junio C Hamano
Quoted 7 lines · Jonathan Nieder

So in the future one might be able to do things like

	[branch "git-gui/*"]
		mergeoptions = -s subtree

Interesting.

Quoted 8 lines
Quoted 5 lines
Quoted 2 lines

The need for this arises from the fact that there is currently not an easy way to set merge options for all branches.

I'm curious: what merge options/workflows does this tend to be useful for?

I actually am curious myself, too. I want to see a real-life example.

In my reply to Jonathan I gave the example of turning off fast forward merges globally for all branches (and then perhaps turning them back on for specific branches). The same could be done with --log or any other option that might make since to turn on for all branches without having to specifically name each branch in the config file.

Also as previously cited in my earlier reply to Jonathan, a good real life example can be found in Vincent Driessen's branching model where he creates ephemeral feature/topic branches for development then deletes them when they get merged back into the mainline development tree. However, he wants to keep the history that there was a merge from a branch instead of it just looking like a straight line. The link to his work is here: http://nvie.com/posts/a-successful-git-branching-model/

Quoted 12 lines

The "git-gui/*" example you gave is unfortunately not it. It specifies the branch at the wrong end. Whether I am merging into "master" or "next", I would want "-s subtree" when I am merging "git-gui/*" project, but all my merges to "master" or "next" do not necessarily want to use "-s subtree".

It might turn out to be that "branch.<name>.mergeoptions" is a ill-conceived idea to begin with.

Quoted 4 lines
Quoted 3 lines

The approach taken is to make note of whether a branch specific mergeoptions key has been seen and only apply the global value if it hasn't.

Quoted 6 lines
Quoted 1 line

What happens if the global value is seen first?

If implemented correctly, it should use the specific one and fall back to the wildcard one. Another issue is if the values should be cumulative or overriding, but in the remainder I'd assume we want overriding.

There is now a unit test for this scenario.

Quoted 10 lines
Quoted 9 lines

Review cleanup: · line 517

Quoted 7 lines
(quoted diff) +3 -0
@@ -505,24 +512,42 @@ cleanup:
 
 static int git_merge_config(const char *k, const char *v, void *cb)
 {
+	int merge_option_mode = 0;
+	struct merge_options_cb *merge_options =
+		(struct merge_options_cb *)cb;

This cast should not needed, I'd think.

Quoted 3 lines

Correct. That is the whole point of using (void *) as a parameter, so that it can be assigned to the real expected type easily without cast.

This has been corrected in the latest version of the patch.

Quoted 19 lines
Quoted 10 lines
Quoted 10 lines
(quoted diff) +10 -0
+	if (!strcmp(k, "branch.*.mergeoptions"))
+		merge_option_mode = MERGEOPTIONS_DEFAULT;
+	else if (branch && !prefixcmp(k, "branch.") &&
+			 !prefixcmp(k + 7, branch) &&
+			 !strcmp(k + 7 + strlen(branch), ".mergeoptions"))
+		merge_option_mode = MERGEOPTIONS_BRANCH;
+
+	if ((merge_option_mode == MERGEOPTIONS_DEFAULT &&
+		!merge_options->override_default) ||
+		merge_option_mode == MERGEOPTIONS_BRANCH) {
Quoted 8 lines

builtin/merge.c static int git_merge_config(const char *k, const char *v, void *cb) · line 537

Quoted 1 line
builtin/merge.c +0 -0
 		const char **argv;

It is hard to see at a glance where the "if" condition ends and the body begins. Why not
...
or even
...
?

Quoted 3 lines

Why not have two string pointers in merge_options_cb structure that are initialized to NULL and holds the matched config key and the value?

That was a great idea. I've reworked things around this and also moved to a voting type method for determining priority of the keys. So no more defines needed. I think keeping all that state in the struct is much better and scales better as well.

Quoted 21 lines

When we are looking at a (k, v) pair in this function, if there is no previous key in cb, we store (k, v) in cb and return. If there already is a previous key, we see if k is more specific than that key, and replace (k, v) in cb with what we currently have. Otherwise we do not do anything.

When git_config() returns, the caller will find the final value in cb.

Quoted 4 lines
Quoted 4 lines
(quoted diff) +4 -0
+	git config "branch.*.mergeoptions" "--no-ff" &&
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section "branch.*" &&
Quoted 8 lines

Review cleanup: · line 537

Quoted 5 lines
(quoted diff) +5 -0
+	verify_merge file result.1 &&
+	verify_parents $c0 $c1
+'
+
+test_debug 'git log --graph --decorate --oneline --all'

Yuck. Did anything come of the idea of a --between-tests option to use an arbitrary command here automatically? (Not your fault.)

Quoted 2 lines

I actually think test_debug should go inside the previous test. Why not have it immediately after "git merge c1" above?

Again I followed the existing pattern here. I didn't want to be the odd man out. Do you want me to make that change?

Quoted 5 lines

--
To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#8 lore raw
Quoted 11 lines · Michael Grubb
Quoted 7 lines · Junio C Hamano
Quoted 2 lines · Jonathan Nieder

Yuck. Did anything come of the idea of a --between-tests option to use an arbitrary command here automatically? (Not your fault.)

I actually think test_debug should go inside the previous test. Why not have it immediately after "git merge c1" above?

Again I followed the existing pattern here. I didn't want to be the odd man out. Do you want me to make that change?

No, I was just reminding the list at large about how annoying it is.

You did right to mimic the style of surrounding tests. Thank you.

#9 lore raw
In reply to Junio C Hamano

Am 03.05.2011 20:16, schrieb Junio C Hamano:

Quoted 8 lines
Quoted 5 lines · Jonathan Nieder
Quoted 2 lines

The need for this arises from the fact that there is currently not an easy way to set merge options for all branches.

I'm curious: what merge options/workflows does this tend to be useful for?

I actually am curious myself, too. I want to see a real-life example.

At my dayjob the reviewer of a branch does the merge of a topic into master. And to always record who did the review even if the merge is a fast forward we use the "branch.master.mergeoptions=--no-ff" config in all our repos, set by our installer.

But that doesn't work out of the box for our release branches, we have to manually set stuff like "branch.v1.23-4.mergeoptions=--no-ff" for every release branch in every clone where merges are done. This is easy to forget and as we use git gui for our daily work, we can't simply use the command line option --no-ff.

So we would greatly benefit from this patch (and actually I looked into doing it myself some time ago but postponed it to do some submodule stuff first ;-)

#10 lore raw

PATCH v4 1/1 Add default merge options for all branches

In reply to Jonathan Nieder

Add support for branch.*.mergeoptions for setting default options for all branches. This new value shares semantics with the existing branch.<name>.mergeoptions variable. If a branch specific value is found, that value will be used.

The need for this arises from the fact that there is currently not an easy way to set merge options for all branches. Instead of having to specify merge options for each individual branch there should be a way to set defaults for all branches and then override a specific branch's options.

In order to facilitate future features centered around this new "globlike" syntax a new struct has been created to keep track of the branch.*.* options. Currently it only supports branch.*.mergeoptions, but can be easily modified in the future to support other branch specific options as well. The mechanism will "vote" on specific-"ness" of the configuration key and ultimately only use the most specific options. This is not cumulative but overriding.

---
 Documentation/git-merge.txt |    3 +
 builtin/merge.c             |   90 +++++++++++++++++++++++++++++++++---------
 t/t7600-merge.sh            |   39 ++++++++++++++++++
 3 files changed, 112 insertions(+), 20 deletions(-)
Documentation/git-merge.txt +3 -0
index e2e6aba..eaab3e4 100644
@@ -307,6 +307,9 @@ branch.<name>.mergeoptions::
 	Sets default options for merging into branch <name>. The syntax and
 	supported options are the same as those of 'git merge', but option
 	values containing whitespace characters are currently not supported.
+	The special value '*' for <name> may be used to configure default
+	options for all branches.  Values for specific branch names will
+	override the this default.
 
 SEE ALSO
 --------
builtin/merge.c +70 -20

117 lines not on this page · raw message

t/t7600-merge.sh +39 -0
index e84e822..5b1f8e1 100755
@@ -415,6 +415,45 @@ test_expect_success 'merge c0 with c1 (no-ff)' '
 
 test_debug 'git log --graph --decorate --oneline --all'
 
+test_expect_success 'merge c0 with c1 (default no-ff)' '
+	git reset --hard c0 &&
+	test_might_fail git config --unset branch.master.mergeoptions &&
+	git config "branch.*.mergeoptions" "--no-ff" &&
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section "branch.*" &&
+	verify_merge file result.1 &&
+	verify_parents $c0 $c1
+'
+
+test_debug 'git log --graph --decorate --oneline --all'
+
+test_expect_success 'combine branch.*.mergeoptions with branch.x.mergeoptions' '
+	git reset --hard c0 &&
+	test_might_fail git config --remove-section branch.master &&
+	git config "branch.*.mergeoptions" "--no-ff" &&
+	git config branch.master.mergeoptions "--ff" &&
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section "branch.*" &&
+	verify_merge file result.1 &&
+	verify_parents "$c0"
+'
+
+test_expect_success 'reverse branch.x.mergeoptions with branch.*.mergeoptions' '
+	git reset --hard c0 &&
+	test_might_fail git config --remove-section branch.master &&
+	git config branch.master.mergeoptions "--ff" &&
+	git config "branch.*.mergeoptions" "--no-ff" &&
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section "branch.*" &&
+	verify_merge file result.1 &&
+	verify_parents "$c0"
+'
+
+test_debug 'git log --graph --decorate --oneline --all'
+
 test_expect_success 'combining --squash and --no-ff is refused' '
 	test_must_fail git merge --squash --no-ff c1 &&
 	test_must_fail git merge --no-ff --squash c1
Signature · 1 line
1.7.5
#11 lore raw
Quoted 7 lines · Michael Grubb

In order to facilitate future features centered around this new "globlike" syntax a new struct has been created to keep track of the branch.*.* options. Currently it only supports branch.*.mergeoptions, but can be easily modified in the future to support other branch specific options as well. The mechanism will "vote" on specific-"ness" of the configuration key and ultimately only use the most specific options.

My immediate reactions:

- Is this really needed? If we need some priority field when other branch globs are being supported, can't we add it then?

- Floating point? *turns to flee*

Documentation/git-merge.txt branch.<name>.mergeoptions:: · line 312

Quoted 9 lines
Documentation/git-merge.txt +3 -0
@@ -307,6 +307,9 @@ branch.<name>.mergeoptions::
 	Sets default options for merging into branch <name>. The syntax and
 	supported options are the same as those of 'git merge', but option
 	values containing whitespace characters are currently not supported.
+	The special value '*' for <name> may be used to configure default
+	options for all branches.  Values for specific branch names will
+	override the this default.

In what sense are they overridden? For example, if I write

	[branch "*"]
		mergeoptions = --no-ff

	[branch "master"]
		mergeoptions = --log=5

and merge another branch into master, will the effect be as though I wrote --no-ff --log=5 or just --log=5?

I'm starting to suspect it might be simpler to add a new "[merge] no-ff" configuration item, like the existing "[merge] log".

Quoted 5 lines
builtin/merge.c +0 -0
@@ -32,6 +32,21 @@
 #define NO_FAST_FORWARD (1<<2)
 #define NO_TRIVIAL      (1<<3)

builtin/merge.c line 43

Quoted 10 lines
builtin/merge.c +9 -0
 
+/* This is for branch.<foo>. blocks
+ * the vote member holds a value between
+ * 0.0 and 1.0 which measures how closely
+ * a branch name matches the key member.
+ * where branch.*.mergeoptions would be 0.1 and
+ * branch.<name>.mergeoptions would be 1.0
+ * Also it is called vote because I couldn't come
+ * up with a better name.
+ */

Style: comments should look like this:

	/*
	 * Explanation comes here, with each line being
	 * approximately the same length as the next one.
	 */

"It is called vote because I couldn't come up with a better name" does not belong in a comment unless you are asking the reader to fix it. In that case, I would write something like

float vote; /* NEEDSWORK: give this a better name */

or

	float priority;

builtin/merge.c line 48

Quoted 5 lines
builtin/merge.c +5 -0
+struct merge_options_cb {
+	char *key;
+	char *value;
+	float vote;
+};

Since I didn't read the comment, I'm not sure what these represent. Who is responsible for freeing them? Is the key a glob?

Quoted 22 lines
builtin/merge.c +2 -15
@@ -503,28 +518,60 @@ cleanup:
 	strbuf_release(&bname);
 }
 
-static int git_merge_config(const char *k, const char *v, void *cb)
+static void parse_git_merge_options(const char *k, const char *v,
+			void *cb)
 {
-	if (branch && !prefixcmp(k, "branch.") &&
-		!prefixcmp(k + 7, branch) &&
-		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
-		const char **argv;
-		int argc;
-		char *buf;
-
-		buf = xstrdup(v);
-		argc = split_cmdline(buf, &argv);
-		if (argc < 0)
-			die(_("Bad branch.%s.mergeoptions string: %s"), branch,
-			    split_cmdline_strerror(argc));
-		argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
-		memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));

builtin/merge.c cleanup: · line 529

Quoted 10 lines
builtin/merge.c +6 -4
-		argc++;
-		parse_options(argc, argv, NULL, builtin_merge_options,
-			      builtin_merge_usage, 0);
-		free(buf);
+	struct merge_options_cb *merge_options = cb;
+	int changed = 0;
+
+	/* We only handle mergeoptions for now */
+	if (suffixcmp(k, ".mergeoptions"))
+		return;

The function is called parse_git_merge_options; I wonder if this "for now" is an old comment from when it had a different name.

builtin/merge.c cleanup: · line 533

Quoted 4 lines
builtin/merge.c +4 -0
+
+	if (!prefixcmp(k, "branch.*") && merge_options->vote <= 0.1 ) {
+		merge_options->vote = 0.1;
+		changed = 1;

Style: parens. What happens if I use

	[branch "*aster"]
		mergeoptions = ...

or

	[branch "*.c"]
		mergeoptions = ...

? Where does 0.1 come from?

builtin/merge.c cleanup: · line 538

Quoted 5 lines
builtin/merge.c +5 -0
+	} else if (branch && !prefixcmp(k, "branch.") &&
+				!prefixcmp(k + 7, branch) &&
+				merge_options->vote < 1.0) {
+		merge_options->vote = 1.0;
+		changed = 1;

What does changed mean?

builtin/merge.c cleanup: · line 543

Quoted 5 lines
builtin/merge.c +3 -0
 	}
 
+	if (changed) {
+		merge_options->key = (char *)k;
+		merge_options->value = (char *)v;

Why not make the struct fields const?

builtin/merge.c cleanup: · line 553

Quoted 10 lines
builtin/merge.c +10 -0
+	}
+}
+
+static void apply_merge_options(struct merge_options_cb *opts)
+{
+	const char **argv;
+	int argc;
+	char *buf;
+
+	if ( opts == NULL )

Style: parens. It would be more idiomatic to say

	if (!opts)
Quoted 5 lines
builtin/merge.c +5 -0
+		return;
+
+	buf = xstrdup(opts->value);
+	argc = split_cmdline(buf, &argv);
+	if (argc < 0)

builtin/merge.c cleanup: · line 568

Quoted 10 lines
builtin/merge.c +10 -0
+		die(_("Bad %s string: %s"), 
+			opts->key, split_cmdline_strerror(argc));
+
+	argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+	memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+	argc++;
+	parse_options(argc, argv, NULL, builtin_merge_options,
+			  builtin_merge_usage, 0);
+	free(buf);
+}

Idea: the series could be made easier to read if splitting this off into its own function were a separate patch. Anyway, splitting it out was a good idea; thanks for doing that.

It seems you changed the whitespace while unindenting? The whitespace on the continuation line for parse_options(...) arguments is especially confusing (I suspect you are using a tabwidth of 6, but that doesn't make sense, either, since opts->key is not an argument to _. The official rendering uses a tabwidth of 8).

builtin/merge.c cleanup: · line 573

Quoted 5 lines
builtin/merge.c +5 -0
+
+static int git_merge_config(const char *k, const char *v, void *cb)
+{
+	if (cb != NULL && branch && !prefixcmp(k, "branch."))
+		parse_git_merge_options(k, v, cb);

More idiomatic to say

	if (!cb && branch && !prefixcmp(k, "branch."))
		...

Changing behavior when cb is NULL seems odd to me. Would it ever actually be NULL?

builtin/merge.c int cmd_merge(int argc, const char **argv, const char *prefix) · line 1057

Quoted 8 lines
builtin/merge.c +3 -1
@@ -1004,7 +1052,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 	if (is_null_sha1(head))
 		head_invalid = 1;
 
-	git_config(git_merge_config, NULL);
+	git_config(git_merge_config, &merge_options);
+	if (merge_options.key != NULL && merge_options.value != NULL)
+		apply_merge_options(&merge_options);

More idiomatic to say

	if (merge_options.key && merge_options.value)

But this seems odd, too --- when would "key" be non-null without "value" being so?

Quoted 3 lines
t/t7600-merge.sh +0 -0
@@ -415,6 +415,45 @@ test_expect_success 'merge c0 with c1 (no-ff)' '

[...]

Quoted 9 lines
t/t7600-merge.sh +9 -0
+test_expect_success 'combine branch.*.mergeoptions with branch.x.mergeoptions' '
+	git reset --hard c0 &&
+	test_might_fail git config --remove-section branch.master &&
+	git config "branch.*.mergeoptions" "--no-ff" &&
+	git config branch.master.mergeoptions "--ff" &&
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section "branch.*" &&
+	verify_merge file result.1 &&

t/t7600-merge.sh test_expect_success 'merge c0 with c1 (no-ff)' ' · line 449

Quoted 10 lines
t/t7600-merge.sh +10 -0
+	verify_parents "$c0"
+'
+
+test_expect_success 'reverse branch.x.mergeoptions with branch.*.mergeoptions' '
+	git reset --hard c0 &&
+	test_might_fail git config --remove-section branch.master &&
+	git config branch.master.mergeoptions "--ff" &&
+	git config "branch.*.mergeoptions" "--no-ff" &&
+	test_tick &&
+	git merge c1 &&

Thanks. This might pass by mistake with a "git config" implementation that sorts its entries when writing them; since I think git is intended to allow users writing to the config file directly, too, maybe something along the lines of

	cat >>.git/config <<-\EOF &&
	[branch "master"]
		mergeoptions = --ff

	[branch "*"]
		mergeoptions = --no-ff
	EOF

could be interesting.

Also --ff and --no-ff do not have orthogonal effect. What happens when the merge options do (e.g., --ff and --log)?

t/t7600-merge.sh test_expect_success 'merge c0 with c1 (no-ff)' ' · line 441

Quoted 4 lines
t/t7600-merge.sh +4 -0
+	git config --remove-section "branch.*" &&
+	verify_merge file result.1 &&
+	verify_parents "$c0"
+'

Thanks again.

Hope that helps,
Jonathan

#12 lore raw
Quoted 10 lines · Jonathan Nieder

In what sense are they overridden? For example, if I write

	[branch "*"]
		mergeoptions = --no-ff

	[branch "master"]
		mergeoptions = --log=5

and merge another branch into master, will the effect be as though I wrote --no-ff --log=5 or just --log=5?

I think the latter is overriding, and the former is cumulative.

Quoted 2 lines

I'm starting to suspect it might be simpler to add a new "[merge] no-ff" configuration item, like the existing "[merge] log".

Surely

[merge]
log = false
ff = false

would be a lot simpler and probably far easier to explain.

Does

	[merge]
		log = false
	[branch "master"]
        	mergeoptions = --log=5

do the right thing with the current codebase?

#13 lore raw
Quoted 5 lines · Junio C Hamano
Quoted 2 lines · Jonathan Nieder

I'm starting to suspect it might be simpler to add a new "[merge] no-ff" configuration item, like the existing "[merge] log".

Quoted 7 lines

Surely

[merge]
log = false
ff = false

would be a lot simpler and probably far easier to explain.

Yes, it is far simpler and easier to explain. I'll leave the tests and the commit log message to people who are more interested in this topic than I am ;-)

 Documentation/merge-config.txt |    6 ++++++
 builtin/merge.c                |    3 +++
 2 files changed, 9 insertions(+), 0 deletions(-)
Documentation/merge-config.txt +6 -0
index 8920258..2aa4408 100644
@@ -16,6 +16,12 @@ merge.defaultToUpstream::
 	to their corresponding remote tracking branches, and the tips of
 	these tracking branches are merged.
 
+merge.ff::
+	Do not generate a merge commit if the merge resolved as a
+	fast-forward; only update the branch pointer instead.  Setting
+	this to `false` would be equivalent to giving `--no-ff` from
+	the command line.
+
 merge.log::
 	In addition to branch names, populate the log message with at
 	most the specified number of one-line descriptions from the
builtin/merge.c +3 -0
index d171c63..5194f04 100644
@@ -541,6 +541,9 @@ static int git_merge_config(const char *k, const char *v, void *cb)
 		if (is_bool && shortlog_len)
 			shortlog_len = DEFAULT_MERGE_LOG_LEN;
 		return 0;
+	} else if (!strcmp(k, "merge.ff")) {
+		allow_fast_forward = git_config_bool(k, v);
+		return 0;
 	} else if (!strcmp(k, "merge.defaulttoupstream")) {
 		default_to_upstream = git_config_bool(k, v);
 		return 0;
#14 lore raw

I take this to mean that my patch is no longer needed/wanted?

Quoted 59 lines · Junio C Hamano
Quoted 5 lines · Junio C Hamano
Quoted 2 lines · Jonathan Nieder

I'm starting to suspect it might be simpler to add a new "[merge] no-ff" configuration item, like the existing "[merge] log".

Quoted 7 lines

Surely

[merge]
log = false
ff = false

would be a lot simpler and probably far easier to explain.

Yes, it is far simpler and easier to explain. I'll leave the tests and the commit log message to people who are more interested in this topic than I am ;-)

 Documentation/merge-config.txt |    6 ++++++
 builtin/merge.c                |    3 +++
 2 files changed, 9 insertions(+), 0 deletions(-)
Documentation/merge-config.txt +6 -0
index 8920258..2aa4408 100644
@@ -16,6 +16,12 @@ merge.defaultToUpstream::
 	to their corresponding remote tracking branches, and the tips of
 	these tracking branches are merged.
 
+merge.ff::
+	Do not generate a merge commit if the merge resolved as a
+	fast-forward; only update the branch pointer instead.  Setting
+	this to `false` would be equivalent to giving `--no-ff` from
+	the command line.
+
 merge.log::
 	In addition to branch names, populate the log message with at
 	most the specified number of one-line descriptions from the
builtin/merge.c +3 -1
index d171c63..5194f04 100644
@@ -541,6 +541,9 @@ static int git_merge_config(const char *k, const char *v, void *cb)
 		if (is_bool && shortlog_len)
 			shortlog_len = DEFAULT_MERGE_LOG_LEN;
 		return 0;
+	} else if (!strcmp(k, "merge.ff")) {
+		allow_fast_forward = git_config_bool(k, v);
+		return 0;
 	} else if (!strcmp(k, "merge.defaulttoupstream")) {
 		default_to_upstream = git_config_bool(k, v);
 		return 0;
--

To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#15 lore raw
Quoted 1 line · Michael Grubb

I take this to mean that my patch is no longer needed/wanted?

No, as usual it means that Junio was thinking and sent us his thoughts.

Do you like it? If so, the way to move it forward would be to add tests and write a commit log message. If not, the way to move things forward would be to explain what use case it misses and work on a better patch that takes care of them.

Hope that helps,
Jonathan

#16 lore raw

Well, it certainly serves *my* immediate needs and addresses the specific use case that I was originally working on. Though I think that what we've come up with would benefit the codebase in general if for no other reason than it lays some ground work for future features and is a bit more generic. I also wouldn't mind going a step further and working on the globbing feature in a separate series.

Here are some corrections to the previous patch:

This patch makes the branch specific options more flexible by laying the ground work for supporting globs for the branch names. At this time only the value '*' is supported which will set default values for all branches. In the future this may be expanded to support true globbing.

---
 builtin/merge.c  |  120 ++++++++++++++++++++++++++++++++++-------------------
 t/t7600-merge.sh |   27 ++++++++----
 2 files changed, 95 insertions(+), 52 deletions(-)
builtin/merge.c +77 -43
index fa56205..9e6ec5f 100644
@@ -31,20 +31,12 @@
 #define DEFAULT_OCTOPUS (1<<1)
 #define NO_FAST_FORWARD (1<<2)
 #define NO_TRIVIAL      (1<<3)
+#define MERGE_OPTIONS_INIT {NULL, NULL, NULL}
 
-/* This is for branch.<foo>. blocks
- * the vote member holds a value between
- * 0.0 and 1.0 which measures how closely
- * a branch name matches the key member.
- * where branch.*.mergeoptions would be 0.1 and
- * branch.<name>.mergeoptions would be 1.0
- * Also it is called vote because I couldn't come
- * up with a better name.
- */
 struct merge_options_cb {
-	char *key;
-	char *value;
-	float vote;
+	char *option;
+	char *spec;
+	char *name;
 };
 
 struct strategy {
@@ -518,30 +510,11 @@ cleanup:
 	strbuf_release(&bname);
 }
 
-static void parse_git_merge_options(const char *k, const char *v,
-			void *cb)
+static void free_merge_options_cb(struct merge_options_cb *data)
 {
-	struct merge_options_cb *merge_options = cb;
-	int changed = 0;
-
-	/* We only handle mergeoptions for now */
-	if (suffixcmp(k, ".mergeoptions"))
-		return;
-
-	if (!prefixcmp(k, "branch.*") && merge_options->vote <= 0.1 ) {
-		merge_options->vote = 0.1;
-		changed = 1;
-	} else if (branch && !prefixcmp(k, "branch.") &&
-				!prefixcmp(k + 7, branch) &&
-				merge_options->vote < 1.0) {
-		merge_options->vote = 1.0;
-		changed = 1;
-	}
-
-	if (changed) {
-		merge_options->key = (char *)k;
-		merge_options->value = (char *)v;
-	}
+	free(data->option);
+	free(data->spec);
+	free(data->name);
 }
 
 static void apply_merge_options(struct merge_options_cb *opts)
@@ -550,26 +523,85 @@ static void apply_merge_options(struct merge_options_cb *opts)
 	int argc;
 	char *buf;
 
-	if ( opts == NULL )
+	if (!opts)
 		return;
 
-	buf = xstrdup(opts->value);
+	buf = xstrdup(opts->option);
 	argc = split_cmdline(buf, &argv);
 	if (argc < 0)
-		die(_("Bad %s string: %s"),
-			opts->key, split_cmdline_strerror(argc));
+		die(_("Bad branch.%s.%s string: %s"),
+			opts->spec, opts->name, split_cmdline_strerror(argc));
 
 	argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
 	memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
 	argc++;
 	parse_options(argc, argv, NULL, builtin_merge_options,
-			  builtin_merge_usage, 0);
+		builtin_merge_usage, 0);
 	free(buf);
 }
 
+/*
+ * This function returns -1 if the first argument is less specific
+ * than the second, and 1 vice versa. Returns -1 if both arguments
+ * are the same.
+ */
+static int cmp_specificity(const char *a, const char *b)
+{
+	switch((!strcmp(a, "*") ? 2 : 0) |
+		(!strcmp(b, "*") ? 1 : 0)) {
+	case 3:
+	case 0:
+		return -1; /* later one wins if they are the same */
+	case 1:
+		return 1;
+	case 2:
+		return -1;
+	}
+	return 0; /* should never happen */
+}
+
+static void parse_git_merge_options(const char *k, const char *v,
+			void *cb)
+{
+	struct merge_options_cb *merge_options = cb;
+	char *spec, *eon; /* end-of-name */
+
+	k += 7; /* not interested in the leading "branch." */
+	eon = strrchr(k, '.');
+	if (!eon || strcmp(eon, ".mergeoptions"))
+		return;
+
+	/* k through eon is name or glob */
+	spec = xmemdupz(k, eon - k);
+	/*
+	 * NEEDSWORK: for now we say "*" matches; we would need
+	 * to turn the following into something like:
+	 *	if (has_wildcard(spec)
+	 *		? !glob_matches(spec, branch)
+	 *		: strcmp(spec, branch)) {
+	 *		free(spec);
+	 *		return;
+	 *	}
+	 */
+	if (strcmp(spec, "*") && strcmp(spec, branch)) {
+		free(spec);
+		return;
+	}
+
+	if (!merge_options->option ||
+			cmp_specificity(merge_options->spec, spec) < 0) {
+		free_merge_options_cb(merge_options);
+		merge_options->option = xstrdup(v);
+		merge_options->name = xstrdup(eon + 1);
+		merge_options->spec = spec;
+		return;
+	}
+	free(spec);
+}
+
 static int git_merge_config(const char *k, const char *v, void *cb)
 {
-	if (cb != NULL && branch && !prefixcmp(k, "branch."))
+	if (branch && !prefixcmp(k, "branch."))
 		parse_git_merge_options(k, v, cb);
 
 	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
@@ -1034,7 +1066,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 	const char *head_arg;
 	int flag, head_invalid = 0, i;
 	int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
-	struct merge_options_cb merge_options = {NULL, NULL, 0.0};
+	struct merge_options_cb merge_options = MERGE_OPTIONS_INIT;
 	struct commit_list *common = NULL;
 	const char *best_strategy = NULL, *wt_strategy = NULL;
 	struct commit_list **remotes = &remoteheads;
@@ -1053,8 +1085,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 		head_invalid = 1;
 
 	git_config(git_merge_config, &merge_options);
-	if (merge_options.key != NULL && merge_options.value != NULL)
+	if (merge_options.option) {
 		apply_merge_options(&merge_options);
+		free_merge_options_cb(&merge_options);
+	}
 
 	/* for color.ui */
 	if (diff_use_color_default == -1)
t/t7600-merge.sh +18 -9
index 5b1f8e1..d7a60b2 100755
@@ -417,37 +417,46 @@ test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'merge c0 with c1 (default no-ff)' '
 	git reset --hard c0 &&
-	test_might_fail git config --unset branch.master.mergeoptions &&
-	git config "branch.*.mergeoptions" "--no-ff" &&
+	cat >.git/config <<-\EOF &&
+	[branch "*"]
+		mergeoptions = --no-ff
+	EOF
 	test_tick &&
 	git merge c1 &&
 	git config --remove-section "branch.*" &&
 	verify_merge file result.1 &&
 	verify_parents $c0 $c1
 '
-
 test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'combine branch.*.mergeoptions with branch.x.mergeoptions' '
 	git reset --hard c0 &&
-	test_might_fail git config --remove-section branch.master &&
-	git config "branch.*.mergeoptions" "--no-ff" &&
-	git config branch.master.mergeoptions "--ff" &&
+	cat >.git/config <<-\EOF &&
+	[branch "*"]
+		mergeoptions = --no-ff
+	[branch "master"]
+		mergeoptions = --ff
+	EOF
 	test_tick &&
 	git merge c1 &&
 	git config --remove-section "branch.*" &&
+	git config --remove-section "branch.master" &&
 	verify_merge file result.1 &&
 	verify_parents "$c0"
 '
 
 test_expect_success 'reverse branch.x.mergeoptions with branch.*.mergeoptions' '
 	git reset --hard c0 &&
-	test_might_fail git config --remove-section branch.master &&
-	git config branch.master.mergeoptions "--ff" &&
-	git config "branch.*.mergeoptions" "--no-ff" &&
+	cat >.git/config <<-\EOF &&
+	[branch "master"]
+		mergeoptions = --ff
+	[branch "*"]
+		mergeoptions = --no-ff
+	EOF
 	test_tick &&
 	git merge c1 &&
 	git config --remove-section "branch.*" &&
+	git config --remove-section "branch.master" &&
 	verify_merge file result.1 &&
 	verify_parents "$c0"
 '
Signature · 1 line
1.7.5
Quoted 19 lines · Jonathan Nieder
Quoted 1 line · Michael Grubb

I take this to mean that my patch is no longer needed/wanted?

No, as usual it means that Junio was thinking and sent us his thoughts.

Do you like it? If so, the way to move it forward would be to add tests and write a commit log message. If not, the way to move things forward would be to explain what use case it misses and work on a better patch that takes care of them.

Hope that helps,
Jonathan
--
To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#17 lore raw
Quoted 4 lines · Michael Grubb

Well, it certainly serves *my* immediate needs and addresses the specific use case that I was originally working on. Though I think that what we've come up with would benefit the codebase in general if for no other reason than it lays some ground work for future features...

The discussion was fruitful, I think, and it may have laid the groundwork at the _conceptual level_. I however do not think that the approach the patch takes is generic enough, even if we add globbing of the branch name, to build on other things that can come out of the future directions the discussion suggested.

For example, why does "merge.c" hold the parser for "branch.*.*", when there are a lot more configuration variables that apply per branch that are totally unrelated to merge? Don't these branch.*.* variables benefit from having a wildcard support as well? If we wanted to add such support, which configuration parser should be called by many pgorams that deal with branches (e.g. "checkout -b", "branch", "fetch", ...), and where that parser should be defined in? I suspect the answer might be "branch.c", but I do not htink we explored the uses widely enough to make that decision yet.

Even if I limit the discussion to "mergeoptions" [*1*], why can I specify the merge options based on what branch I am currently on, but not based on what branch I am merging to my current branch? When on master branch, should branch.*.mo augument what I have in branch.master.mo, or should it overwrite it? I may want to say "all my merges should use --log" and "when on master I want --no-ff". Should branch.master.mo repeat "--log", or should the values on the two variables automatically combine? If so in what order? Am I allowed to say "Use 5 lines of --log" for generic one, and then "Use 10 more lines of --log than generic" for branch.master.mo, so that later I can easily change my mind and update branch.*.mo to use 10 lines, and I get automatically 20 lines for the master branch? If not why not?

etc.etc.etc....

I am not saying some of these issues are unsolvable, nor we should solve all these problems right now, but I do not think these issues are something you are trying to solve with this patch, nor the approach this patch happens to use was designed with these problems in mind (I certainly didn't, when I made many suggestions I see in this round of your patch) to become a foundation to solve them in the future.

The suggestion by Jonathan does not have such a design issue that requires us to open a huge can of worms right now and potentially result in a solution that is overengineered to address a wrong problem [*2*].

If it solves the original issue without such downsides, that would be more preferrable, I think; no?

[Footnote]

*1* I personally think "branch.<name>.mergeoptions" was a mistake. If it were separate "branch.<name>.merge-ff", "branch.<name>.merge-log", ... it might have been more clear what the combining semantics should be. But that is not going to change, so I'd rather not to extend the support for it, until we come up with something more sensible.

*2* For example, globbing to match the current branch name could become an overengineered solution to a wrong problem, if it turns out that it is not useful to decide things based solely on the current branch name.

#18 lore raw
In reply to Junio C Hamano
Quoted 13 lines · Junio C Hamano
Quoted 11 lines · Junio C Hamano
Quoted 2 lines · Jonathan Nieder

I'm starting to suspect it might be simpler to add a new "[merge] no-ff" configuration item, like the existing "[merge] log".

Surely

      [merge]
              log = false
              ff = false

Quoted 5 lines
Quoted 1 line

would be a lot simpler and probably far easier to explain.

Yes, it is far simpler and easier to explain.  I'll leave the tests and the commit log message to people who are more interested in this topic than I am ;-)

I like the idea. I assume it can be overridden on the command line too?

-John

#19 lore raw
In reply to Michael Grubb

This patch has a trailing whitespace on one line.
I'm resubmitting to fix it.

Quoted 215 lines · Michael Grubb

Add support for branch.*.mergeoptions for setting default options for all branches. This new value shares semantics with the existing branch.<name>.mergeoptions variable. If a branch specific value is found, that value will be used.

The need for this arises from the fact that there is currently not an easy way to set merge options for all branches. Instead of having to specify merge options for each individual branch there should be a way to set defaults for all branches and then override a specific branch's options.

In order to facilitate future features centered around this new "globlike" syntax a new struct has been created to keep track of the branch.*.* options. Currently it only supports branch.*.mergeoptions, but can be easily modified in the future to support other branch specific options as well. The mechanism will "vote" on specific-"ness" of the configuration key and ultimately only use the most specific options. This is not cumulative but overriding.

---
 Documentation/git-merge.txt |    3 +
 builtin/merge.c             |   90 +++++++++++++++++++++++++++++++++---------
 t/t7600-merge.sh            |   39 ++++++++++++++++++
 3 files changed, 112 insertions(+), 20 deletions(-)
Documentation/git-merge.txt +3 -0
index e2e6aba..eaab3e4 100644
@@ -307,6 +307,9 @@ branch.<name>.mergeoptions::
 	Sets default options for merging into branch <name>. The syntax and
 	supported options are the same as those of 'git merge', but option
 	values containing whitespace characters are currently not supported.
+	The special value '*' for <name> may be used to configure default
+	options for all branches.  Values for specific branch names will
+	override the this default.
 
 SEE ALSO
 --------
builtin/merge.c +70 -20
index d171c63..d6ce85e 100644
@@ -32,6 +32,21 @@
 #define NO_FAST_FORWARD (1<<2)
 #define NO_TRIVIAL      (1<<3)
 
+/* This is for branch.<foo>. blocks
+ * the vote member holds a value between
+ * 0.0 and 1.0 which measures how closely
+ * a branch name matches the key member.
+ * where branch.*.mergeoptions would be 0.1 and
+ * branch.<name>.mergeoptions would be 1.0
+ * Also it is called vote because I couldn't come
+ * up with a better name.
+ */
+struct merge_options_cb {
+	char *key;
+	char *value;
+	float vote;
+};
+
 struct strategy {
 	const char *name;
 	unsigned attr;
@@ -503,28 +518,60 @@ cleanup:
 	strbuf_release(&bname);
 }
 
-static int git_merge_config(const char *k, const char *v, void *cb)
+static void parse_git_merge_options(const char *k, const char *v,
+			void *cb)
 {
-	if (branch && !prefixcmp(k, "branch.") &&
-		!prefixcmp(k + 7, branch) &&
-		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
-		const char **argv;
-		int argc;
-		char *buf;
-
-		buf = xstrdup(v);
-		argc = split_cmdline(buf, &argv);
-		if (argc < 0)
-			die(_("Bad branch.%s.mergeoptions string: %s"), branch,
-			    split_cmdline_strerror(argc));
-		argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
-		memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
-		argc++;
-		parse_options(argc, argv, NULL, builtin_merge_options,
-			      builtin_merge_usage, 0);
-		free(buf);
+	struct merge_options_cb *merge_options = cb;
+	int changed = 0;
+
+	/* We only handle mergeoptions for now */
+	if (suffixcmp(k, ".mergeoptions"))
+		return;
+
+	if (!prefixcmp(k, "branch.*") && merge_options->vote <= 0.1 ) {
+		merge_options->vote = 0.1;
+		changed = 1;
+	} else if (branch && !prefixcmp(k, "branch.") &&
+				!prefixcmp(k + 7, branch) &&
+				merge_options->vote < 1.0) {
+		merge_options->vote = 1.0;
+		changed = 1;
 	}
 
+	if (changed) {
+		merge_options->key = (char *)k;
+		merge_options->value = (char *)v;
+	}
+}
+
+static void apply_merge_options(struct merge_options_cb *opts)
+{
+	const char **argv;
+	int argc;
+	char *buf;
+
+	if ( opts == NULL )
+		return;
+
+	buf = xstrdup(opts->value);
+	argc = split_cmdline(buf, &argv);
+	if (argc < 0)
+		die(_("Bad %s string: %s"), 
+			opts->key, split_cmdline_strerror(argc));
+
+	argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+	memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+	argc++;
+	parse_options(argc, argv, NULL, builtin_merge_options,
+			  builtin_merge_usage, 0);
+	free(buf);
+}
+
+static int git_merge_config(const char *k, const char *v, void *cb)
+{
+	if (cb != NULL && branch && !prefixcmp(k, "branch."))
+		parse_git_merge_options(k, v, cb);
+
 	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
 		show_diffstat = git_config_bool(k, v);
 	else if (!strcmp(k, "pull.twohead"))
@@ -987,6 +1034,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 	const char *head_arg;
 	int flag, head_invalid = 0, i;
 	int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0;
+	struct merge_options_cb merge_options = {NULL, NULL, 0.0};
 	struct commit_list *common = NULL;
 	const char *best_strategy = NULL, *wt_strategy = NULL;
 	struct commit_list **remotes = &remoteheads;
@@ -1004,7 +1052,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 	if (is_null_sha1(head))
 		head_invalid = 1;
 
-	git_config(git_merge_config, NULL);
+	git_config(git_merge_config, &merge_options);
+	if (merge_options.key != NULL && merge_options.value != NULL)
+		apply_merge_options(&merge_options);
 
 	/* for color.ui */
 	if (diff_use_color_default == -1)
t/t7600-merge.sh +39 -0
index e84e822..5b1f8e1 100755
@@ -415,6 +415,45 @@ test_expect_success 'merge c0 with c1 (no-ff)' '
 
 test_debug 'git log --graph --decorate --oneline --all'
 
+test_expect_success 'merge c0 with c1 (default no-ff)' '
+	git reset --hard c0 &&
+	test_might_fail git config --unset branch.master.mergeoptions &&
+	git config "branch.*.mergeoptions" "--no-ff" &&
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section "branch.*" &&
+	verify_merge file result.1 &&
+	verify_parents $c0 $c1
+'
+
+test_debug 'git log --graph --decorate --oneline --all'
+
+test_expect_success 'combine branch.*.mergeoptions with branch.x.mergeoptions' '
+	git reset --hard c0 &&
+	test_might_fail git config --remove-section branch.master &&
+	git config "branch.*.mergeoptions" "--no-ff" &&
+	git config branch.master.mergeoptions "--ff" &&
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section "branch.*" &&
+	verify_merge file result.1 &&
+	verify_parents "$c0"
+'
+
+test_expect_success 'reverse branch.x.mergeoptions with branch.*.mergeoptions' '
+	git reset --hard c0 &&
+	test_might_fail git config --remove-section branch.master &&
+	git config branch.master.mergeoptions "--ff" &&
+	git config "branch.*.mergeoptions" "--no-ff" &&
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section "branch.*" &&
+	verify_merge file result.1 &&
+	verify_parents "$c0"
+'
+
+test_debug 'git log --graph --decorate --oneline --all'
+
 test_expect_success 'combining --squash and --no-ff is refused' '
 	test_must_fail git merge --squash --no-ff c1 &&
 	test_must_fail git merge --no-ff --squash c1
#20 lore raw
In reply to Michael Grubb

Michael Grubb <[email protected]> writes:

/*
* Our Multi-line comments begin with a line with
* slash asterisk then newline.
*/

Quoted 9 lines

+/* This is for branch.<foo>. blocks
+ * the vote member holds a value between
+ * 0.0 and 1.0 which measures how closely
+ * a branch name matches the key member.
+ * where branch.*.mergeoptions would be 0.1 and
+ * branch.<name>.mergeoptions would be 1.0
+ * Also it is called vote because I couldn't come
+ * up with a better name.
+ */

How about simply dropping that "vote" thing? I do not want to see unnecessary float creeping into our codebase.

The k and v parameters are volatile from the point of view of this function. You need to xstrdup() them to keep a copy.

There is no need to store "branch." part in cb->key, as it is common across the variables.

The logic would probably look like this:

	if (prefixcmp(k, "branch."))
		return;
	k += 7; /* past "branch." part */
	eon = strrchr(k, '.'); /* end-of-name 8/
        if (!eon || strcmp(eon, ".mergeoptions"))
        	return;

	/* k thru eon is the name or wildcard */
	spec = xmemdupz(k, eon - k);
        /*
         * NEEDSWORK: for now we say "*" matches; we would need
         * to turn the following into something like:
         *	if (has_wildcard(spec) 
	 *		? !glob_matches(spec, branch)
	 *		: strcmp(spec, branch)) {
         *		free(spec);
         *		return;
         *	}
         */
	if (strcmp(spec, "*") && strcmp(spec, branch)) {
        	free(spec);
                return;
	}

        if (!merge_options->option ||
             cmp_specificity(merge_options->spec, spec) < 0) {
		/* use this one */
                free(merge_options->spec);
                free(merge_options->option);
                merge_options->option = xstrdup(v);
                merge_options->spec = spec;
		return;
	}
        free(spec);

And then cmp_specificity() would say something like:

	static int cmp_specificity(const char *a, const char *b)
        {
        	switch ((!strcmp(a, "*") ? 2 : 0) |
                	(!strcmp(b, "*") ? 1 : 0)) {
		case 3:
                        /*
                         * NEEDSWORK: when we start truly globbing,
                         * we need to decide "foo/*" is more specific than
                         * "*" and the like. But for now we do not have to
                         * worry about that case.
                         */
		case 0:
                        return -1; /* later one wins if they are the same */
		case 1:
			return 1;
		case 2:
			return -1;
		}
	}

meaning, the ones with wildcard are weaker than the ones without.

#21 lore raw

PATCH 1/1 Add default merge options for all branches

In reply to Jonathan Nieder

Add support for branch.*.mergeoptions for setting default options for all branches. This new value shares semantics with the existing branch.<name>.mergeoptions variable. If a branch specific value is found, that value will be used.

The need for this arises from the fact that there is currently not an easy way to set merge options for all branches. Instead of having to specify merge options for each individual branch there should be a way to set defaults for all branches and then override a specific branch's options.

In order to facilitate future features centered around this new "globlike" syntax a new struct has been created to keep track of the branch.*.* options. Currently it only supports branch.*.mergeoptions, but can be easily modified in the future to support other branch specific options as well. The mechanism will "vote" on specific-"ness" of the configuration key and ultimately only use the most specific options. This is not cumulative but overriding.

---
 Documentation/git-merge.txt |    3 +
 builtin/merge.c             |   90 +++++++++++++++++++++++++++++++++---------
 t/t7600-merge.sh            |   39 ++++++++++++++++++
 3 files changed, 112 insertions(+), 20 deletions(-)
Documentation/git-merge.txt +3 -0
index e2e6aba..eaab3e4 100644
@@ -307,6 +307,9 @@ branch.<name>.mergeoptions::
 	Sets default options for merging into branch <name>. The syntax and
 	supported options are the same as those of 'git merge', but option
 	values containing whitespace characters are currently not supported.
+	The special value '*' for <name> may be used to configure default
+	options for all branches.  Values for specific branch names will
+	override the this default.
 
 SEE ALSO
 --------
builtin/merge.c +70 -20

117 lines not on this page · raw message

t/t7600-merge.sh +39 -0
index e84e822..5b1f8e1 100755
@@ -415,6 +415,45 @@ test_expect_success 'merge c0 with c1 (no-ff)' '
 
 test_debug 'git log --graph --decorate --oneline --all'
 
+test_expect_success 'merge c0 with c1 (default no-ff)' '
+	git reset --hard c0 &&
+	test_might_fail git config --unset branch.master.mergeoptions &&
+	git config "branch.*.mergeoptions" "--no-ff" &&
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section "branch.*" &&
+	verify_merge file result.1 &&
+	verify_parents $c0 $c1
+'
+
+test_debug 'git log --graph --decorate --oneline --all'
+
+test_expect_success 'combine branch.*.mergeoptions with branch.x.mergeoptions' '
+	git reset --hard c0 &&
+	test_might_fail git config --remove-section branch.master &&
+	git config "branch.*.mergeoptions" "--no-ff" &&
+	git config branch.master.mergeoptions "--ff" &&
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section "branch.*" &&
+	verify_merge file result.1 &&
+	verify_parents "$c0"
+'
+
+test_expect_success 'reverse branch.x.mergeoptions with branch.*.mergeoptions' '
+	git reset --hard c0 &&
+	test_might_fail git config --remove-section branch.master &&
+	git config branch.master.mergeoptions "--ff" &&
+	git config "branch.*.mergeoptions" "--no-ff" &&
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section "branch.*" &&
+	verify_merge file result.1 &&
+	verify_parents "$c0"
+'
+
+test_debug 'git log --graph --decorate --oneline --all'
+
 test_expect_success 'combining --squash and --no-ff is refused' '
 	test_must_fail git merge --squash --no-ff c1 &&
 	test_must_fail git merge --no-ff --squash c1
Signature · 1 line
1.7.5
#22 lore raw

PATCH v5 1/1 Add default merge options for all branches

In reply to Jonathan Nieder

Add merge.ff flag to support turning off fast-forward merges by default. This flag is overridden if any branch.x.mergeoptions turn it back on.

 Documentation/merge-config.txt |    6 ++++++
 builtin/merge.c                |    3 +++
 2 files changed, 9 insertions(+), 0 deletions(-)
---
 Documentation/merge-config.txt |    6 ++++++
 builtin/merge.c                |    3 +++
 t/t7600-merge.sh               |   23 +++++++++++++++++++++++
 3 files changed, 32 insertions(+), 0 deletions(-)
Documentation/merge-config.txt +6 -0
index 8920258..2aa4408 100644
@@ -16,6 +16,12 @@ merge.defaultToUpstream::
 	to their corresponding remote tracking branches, and the tips of
 	these tracking branches are merged.
 
+merge.ff::
+	Do not generate a merge commit if the merge resolved as a
+	fast-forward; only update the branch pointer instead.  Setting
+	this to `false` would be equivalent to giving `--no-ff` from
+	the command line.
+
 merge.log::
 	In addition to branch names, populate the log message with at
 	most the specified number of one-line descriptions from the
builtin/merge.c +3 -0
index d171c63..5194f04 100644
@@ -541,6 +541,9 @@ static int git_merge_config(const char *k, const char *v, void *cb)
 		if (is_bool && shortlog_len)
 			shortlog_len = DEFAULT_MERGE_LOG_LEN;
 		return 0;
+	} else if (!strcmp(k, "merge.ff")) {
+		allow_fast_forward = git_config_bool(k, v);
+		return 0;
 	} else if (!strcmp(k, "merge.defaulttoupstream")) {
 		default_to_upstream = git_config_bool(k, v);
 		return 0;
t/t7600-merge.sh +23 -0
index e84e822..21c25d4 100755
@@ -415,6 +415,29 @@ test_expect_success 'merge c0 with c1 (no-ff)' '
 
 test_debug 'git log --graph --decorate --oneline --all'
 
+test_expect_success 'merge c0 with c1 (merge.ff=false)' '
+	git reset --hard c0 &&
+	git config merge.ff false &&
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section merge &&
+	verify_merge file result.1 &&
+	verify_parents $c0 $c1
+'
+test_debug 'git log --graph --decorate --oneline --all'
+
+test_expect_success 'combine branch.master.mergeoptions with merge.ff' '
+	git reset --hard c0 &&
+	git config branch.master.mergeoptions --ff
+	git config merge.ff false
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section "branch.master" &&
+	git config --remove-section "merge" &&
+	verify_merge file result.1 &&
+	verify_parents "$c0"
+'
+
 test_expect_success 'combining --squash and --no-ff is refused' '
 	test_must_fail git merge --squash --no-ff c1 &&
 	test_must_fail git merge --no-ff --squash c1
#23 lore raw

Thanks.

I think we still need to work on this a bit more, but I found an unrelated and nastier issue before this patch can sanely be applied.

Review

Quoted 11 lines
(quoted diff) +11 -0
+test_expect_success 'combine branch.master.mergeoptions with merge.ff' '
+	git reset --hard c0 &&
+	git config branch.master.mergeoptions --ff
+	git config merge.ff false
+	test_tick &&
+	git merge c1 &&
+	git config --remove-section "branch.master" &&
+	git config --remove-section "merge" &&
+	verify_merge file result.1 &&
+	verify_parents "$c0"
+'

If you insert an "exit" after this test and inspect the resulting commit, you will see that it created a merge.

Side note: I think verify_parents is buggy. It only makes sure that the earlier parents of HEAD match the commits given, and does not care if there actually are more parents.

In any case, your test exposed an ancient breakage ever since the per-branch mergeoptions was introduced back when git-merge was a shell script (aec7b36 (git-merge: add support for branch.<name>.mergeoptions, 2007-09-24).

I am not going to fix verify_parents tonight, as I have other git things to do.

-- >8 --
Subject: [PATCH] merge: fix branch.<name>.mergeoptions

The parsing of the additional command line parameters supplied to the branch.<name>.mergeoptions configuration variable was implemented at the wrong stage. If any merge-related variable came after we read branch.<name>.mergeoptions, the earlier value was overwritten.

We should first read all the merge.* configuration, override them by reading from branch.<name>.mergeoptions and then finally read from the command line.

This patch should fix it, even though I now strongly suspect that branch.<name>.mergeoptions that gives a single command line that needs to be parsed was likely to be an ill-conceived idea to begin with. Sigh...

---
 builtin-merge.c  |   39 +++++++++++++++++++++++++--------------
 t/t7600-merge.sh |   32 ++++++++++++++++++++++++++++++++
 2 files changed, 57 insertions(+), 14 deletions(-)
builtin-merge.c +25 -14
index 3aaec7b..01389a3 100644
@@ -54,6 +54,7 @@ static size_t use_strategies_nr, use_strategies_alloc;
 static const char **xopts;
 static size_t xopts_nr, xopts_alloc;
 static const char *branch;
+static char *branch_mergeoptions;
 static int verbosity;
 static int allow_rerere_auto;
 
@@ -474,25 +475,33 @@ cleanup:
 	strbuf_release(&bname);
 }
 
+static void parse_branch_merge_options(char *bmo)
+{
+	const char **argv;
+	int argc;
+	char *buf;
+
+	if (!bmo)
+		return;
+	argc = split_cmdline(bmo, &argv);
+	if (argc < 0)
+		die("Bad branch.%s.mergeoptions string", branch);
+	argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+	memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
+	argc++;
+	parse_options(argc, argv, NULL, builtin_merge_options,
+		      builtin_merge_usage, 0);
+	free(buf);
+}
+
 static int git_merge_config(const char *k, const char *v, void *cb)
 {
 	if (branch && !prefixcmp(k, "branch.") &&
 		!prefixcmp(k + 7, branch) &&
 		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
-		const char **argv;
-		int argc;
-		char *buf;
-
-		buf = xstrdup(v);
-		argc = split_cmdline(buf, &argv);
-		if (argc < 0)
-			die("Bad branch.%s.mergeoptions string", branch);
-		argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
-		memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));
-		argc++;
-		parse_options(argc, argv, NULL, builtin_merge_options,
-			      builtin_merge_usage, 0);
-		free(buf);
+		free(branch_mergeoptions);
+		branch_mergeoptions = xstrdup(v);
+		return 0;
 	}
 
 	if (!strcmp(k, "merge.diffstat") || !strcmp(k, "merge.stat"))
@@ -918,6 +927,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
 	if (diff_use_color_default == -1)
 		diff_use_color_default = git_use_color_default;
 
+	if (branch_mergeoptions)
+		parse_branch_merge_options(branch_mergeoptions);
 	argc = parse_options(argc, argv, prefix, builtin_merge_options,
 			builtin_merge_usage, 0);
 	if (verbosity < 0)
t/t7600-merge.sh +32 -0
index 57f6d2b..56c653d 100755
@@ -372,6 +372,38 @@ test_expect_success 'merge c1 with c2 (no-commit in config)' '
 
 test_debug 'gitk --all'
 
+test_expect_success 'merge c1 with c2 (log in config)' '
+	git config branch.master.mergeoptions "" &&
+	git reset --hard c1 &&
+	git merge --log c2 &&
+	git show -s --pretty=tformat:%s%n%b >expect &&
+
+	git config branch.master.mergeoptions --log &&
+	git reset --hard c1 &&
+	git merge c2 &&
+	git show -s --pretty=tformat:%s%n%b >actual &&
+
+	test_cmp expect actual
+'
+
+test_expect_success 'merge c1 with c2 (log in config gets overridden)' '
+	(
+		git config --remove-section branch.master
+		git config --remove-section merge
+	)
+	git reset --hard c1 &&
+	git merge c2 &&
+	git show -s --pretty=tformat:%s%n%b >expect &&
+
+	git config branch.master.mergeoptions "--no-log" &&
+	git config merge.log true &&
+	git reset --hard c1 &&
+	git merge c2 &&
+	git show -s --pretty=tformat:%s%n%b >actual &&
+
+	test_cmp expect actual
+'
+
 test_expect_success 'merge c1 with c2 (squash in config)' '
 	git reset --hard c1 &&
 	git config branch.master.mergeoptions "--squash" &&
Signature · 1 line
1.7.5.284.g84c3a8
#24 lore raw

cover tests: make verify_merge check that the number of parents is right

Quoted 3 lines · Junio C Hamano

Side note: I think verify_parents is buggy. It only makes sure that the earlier parents of HEAD match the commits given, and does not care if there actually are more parents.

How about this?

Jonathan Nieder (2):
tests: eliminate unnecessary setup test assertions
tests: teach verify_parents to check for extra parents

 t/t6010-merge-base.sh |   62 +++++++++++-----------
 t/t7600-merge.sh      |  135 ++++++++++++++++++++++++-------------------------
 2 files changed, 98 insertions(+), 99 deletions(-)
#25 lore raw

PATCH 1/2 tests: eliminate unnecessary setup test assertions

Most of git's tests write files and define shell functions and variables that will last throughout a test script at the top of the script, before all test assertions:

	. ./test-lib.sh

	VAR='some value'
	export VAR

	>empty

	fn () {
		do something
	}

	test_expect_success 'setup' '
		... nontrivial commands go here ...
	'

Two scripts use a different style with this kind of trivial code enclosed by a test assertion; fix them. The usual style is easier to read since there is less indentation to keep track of and no need to worry about nested quotes; and on the other hand, because the commands in question are trivial, it should not make the test suite any worse at catching future bugs in git.

While at it, make some other small tweaks:

- spell function definitions with a space before () for consistency with other scripts;

- use the self-contained command "git mktree </dev/null" in preference to "git write-tree" which looks at the index when writing an empty tree.

---
I should have done this long ago. Sorry for the eyesore.

 t/t6010-merge-base.sh |   62 +++++++++++-----------
 t/t7600-merge.sh      |  134 ++++++++++++++++++++++++-------------------------
 2 files changed, 97 insertions(+), 99 deletions(-)
t/t6010-merge-base.sh +31 -31
index 082032e..f80bba8 100755
@@ -8,38 +8,38 @@ test_description='Merge base and parent list computation.
 
 . ./test-lib.sh
 
+M=1130000000
+Z=+0000
+
[email protected]
+GIT_COMMITTER_NAME='C O Mmiter'
+GIT_AUTHOR_NAME='A U Thor'
[email protected]
+export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL
+
+doit () {
+	OFFSET=$1 &&
+	NAME=$2 &&
+	shift 2 &&
+
+	PARENTS= &&
+	for P
+	do
+		PARENTS="${PARENTS}-p $P "
+	done &&
+
+	GIT_COMMITTER_DATE="$(($M + $OFFSET)) $Z" &&
+	GIT_AUTHOR_DATE=$GIT_COMMITTER_DATE &&
+	export GIT_COMMITTER_DATE GIT_AUTHOR_DATE &&
+
+	commit=$(echo $NAME | git commit-tree $T $PARENTS) &&
+
+	echo $commit >.git/refs/tags/$NAME &&
+	echo $commit
+}
+
 test_expect_success 'setup' '
-	T=$(git write-tree) &&
-
-	M=1130000000 &&
-	Z=+0000 &&
-
-	[email protected] &&
-	GIT_COMMITTER_NAME="C O Mmiter" &&
-	GIT_AUTHOR_NAME="A U Thor" &&
-	[email protected] &&
-	export GIT_COMMITTER_EMAIL GIT_COMMITTER_NAME GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL &&
-
-	doit() {
-		OFFSET=$1 &&
-		NAME=$2 &&
-		shift 2 &&
-
-		PARENTS= &&
-		for P
-		do
-			PARENTS="${PARENTS}-p $P "
-		done &&
-
-		GIT_COMMITTER_DATE="$(($M + $OFFSET)) $Z" &&
-		GIT_AUTHOR_DATE=$GIT_COMMITTER_DATE &&
-		export GIT_COMMITTER_DATE GIT_AUTHOR_DATE &&
-
-		commit=$(echo $NAME | git commit-tree $T $PARENTS) &&
-
-		echo $commit >.git/refs/tags/$NAME &&
-		echo $commit
-	}
+	T=$(git mktree </dev/null)
 '
 
 test_expect_success 'set up G and H' '
t/t7600-merge.sh +66 -68

146 lines not on this page · raw message

Signature · 1 line
1.7.5.1
#26 lore raw

Re: [PATCH 1/2] tests: eliminate unnecessary setup test assertions

Quoted 6 lines · Jonathan Nieder

Two scripts use a different style with this kind of trivial code enclosed by a test assertion; fix them. The usual style is easier to read since there is less indentation to keep track of and no need to worry about nested quotes; and on the other hand, because the commands in question are trivial, it should not make the test suite any worse at catching future bugs in git.

Thanks. Glancing at the first few hunks, this change didn't seem like a big cleanup, but then I got to the hunk with all the ugly '\'' bits. :)

The patch looks correct to me (reviewing with "git diff -b" was a big help).

Quoted 4 lines

While at it, make some other small tweaks:

- spell function definitions with a space before () for consistency with other scripts;

Hmm.

  $ cd t
  $ git grep ' ()' *.sh  | wc -l
  271
  $ git grep '[^ ]()' *.sh  | wc -l
  247

I'm not sure you are making things any more consistent. But I don't really care either way.

Just for giggles, I was curious who introduced the styles:

  pattern_authors() {
    git grep -n "$@" |
    while IFS=: read file line match; do
      git blame -L "$line,$line" "$file"
    done |
    perl -lpe '/\((.*?) \d+-\d+-\d+/; $_=$1'
  }
  $ pattern_authors ' ()' t/*.sh | sort | uniq -c | sort -rn
  84 Junio C Hamano
  32 Jonathan Nieder
  16 Thomas Rast
  16 Johan Herland
  12 Johannes Sixt
  12 Johannes Schindelin
  ...
  $ pattern_authors '[^ ]()' t/*.sh | sort | uniq -c | sort -rn
  52 Jeff King
  26 Jonathan Nieder
  18 Nguyễn Thái Ngọc Duy
  16 Wincent Colaiuta
  14 Jon Seymour
  10 Tay Ray Chuan
  ...

So there are definitely particular people who prefer different styles (and I recalled that Junio and I differed on this style point, which is confirmed here). Interestingly, you are the only person to fall right in the middle. I guess that means you are good at emulating surrounding code. :)

-Peff

#27 lore raw

Re: [PATCH 1/2] tests: eliminate unnecessary setup test assertions

Quoted 7 lines · Jeff King
  pattern_authors() {
    git grep -n "$@" |
    while IFS=: read file line match; do
      git blame -L "$line,$line" "$file"
    done |
    perl -lpe '/\((.*?) \d+-\d+-\d+/; $_=$1'
  }

Two minor complaints on git-blame; maybe somebody can point out something clever I've missed.

1. blame's "-L" understands patterns already. So in theory I could tell it "blame all lines that match pattern X". But I don't think there is a way to do that (it tries looking to for _one_ range to blame for each -L, not a set of ranges).

2. Parsing the human-readable output blame output sucks. But parsing --porcelain is annoyingly complex for quick-and-dirty things like this. It doesn't repeat the commit information per-line.

I guess we could have an inefficient --line-porcelain format that breaks down ranges into single lines and repeats the commit info for each one.

The clever among you may notice that in this particular case, though, I could have gotten away with regular --porcelain as I blame a single line at a time.

-Peff

#28 lore raw

Re: [PATCH 1/2] tests: eliminate unnecessary setup test assertions

Quoted 4 lines · Jeff King

Two minor complaints on git-blame; maybe somebody can point out something clever I've missed.

1. blame's "-L" understands patterns already.

Teaching blame to take multiple -L options has been one of many longstanding todo item for me. Someday.

Quoted 3 lines

2. Parsing the human-readable output blame output sucks. But parsing --porcelain is annoyingly complex for quick-and-dirty things like this. It doesn't repeat the commit information per-line.

Non-repetition was quite deliberate, as the reader was expected to have memory proportional to the number of lines in the range, but I agree it is not friendly for quick and dirty hack.

You should be able to add a command line option that disables the early return at the beginning of emit_one_suspect_detail() with a 5-6 lines of patch.

#29 lore raw

Re: [PATCH 1/2] tests: eliminate unnecessary setup test assertions

Quoted 9 lines · Junio C Hamano
Quoted 4 lines · Jeff King

Two minor complaints on git-blame; maybe somebody can point out something clever I've missed.

1. blame's "-L" understands patterns already.

Teaching blame to take multiple -L options has been one of many longstanding todo item for me. Someday.

I think multiple -L is not quite enough. I want a single "-L" that matches every instance of a pattern, like:

-L "/ ()/,+0"

Quoted 11 lines
Quoted 3 lines

2. Parsing the human-readable output blame output sucks. But parsing --porcelain is annoyingly complex for quick-and-dirty things like this. It doesn't repeat the commit information per-line.

Non-repetition was quite deliberate, as the reader was expected to have memory proportional to the number of lines in the range, but I agree it is not friendly for quick and dirty hack.

You should be able to add a command line option that disables the early return at the beginning of emit_one_suspect_detail() with a 5-6 lines of patch.

I tried that, and it is slightly more involved. You also need to break a multi-line run of lines that blame to a single suspect into its constituent lines. I am 75% of the way to such a patch if you are interested. It's not a lot of code, but it takes some refactoring of emit_porcelain.

-Peff

#30 lore raw

Re: [PATCH 1/2] tests: eliminate unnecessary setup test assertions

Quoted 4 lines · Jeff King

I think multiple -L is not quite enough. I want a single "-L" that matches every instance of a pattern, like:

-L "/ ()/,+0"

Of course I am aware that needs to be a part of the multiple -L topic; after all I was the one who invented -L "$regexp" support ;-)

#31 lore raw

cover blame --line-porcelain

In reply to Jeff King
Quoted 8 lines · Jeff King
Quoted 7 lines

Non-repetition was quite deliberate, as the reader was expected to have memory proportional to the number of lines in the range, but I agree it is not friendly for quick and dirty hack.

You should be able to add a command line option that disables the early return at the beginning of emit_one_suspect_detail() with a 5-6 lines of patch.

Quoted 5 lines

I tried that, and it is slightly more involved. You also need to break a multi-line run of lines that blame to a single suspect into its constituent lines. I am 75% of the way to such a patch if you are interested. It's not a lot of code, but it takes some refactoring of emit_porcelain.

It turned out to not be too bad. Here's the series.

[1/3]: add tests for various blame formats [2/3]: blame: refactor porcelain output [3/3]: blame: add --line-porcelain output format

-Peff

#32 lore raw

PATCH 1/3 add tests for various blame formats

We don't seem to have any tests for "blame --porcelain". Let's at least do a trivial test on a simple example.

---
I always feel funny putting human-readable output in a test. Maybe it is not worth including the "normal" output test below, but I assume it's going to remain pretty stable.

 t/t8008-blame-formats.sh |   71 ++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 71 insertions(+), 0 deletions(-)
 create mode 100755 t/t8008-blame-formats.sh
t/t8008-blame-formats.sh new file +71 -0
new file mode 100755
index 0000000..387d1a6
@@ -0,0 +1,71 @@
+#!/bin/sh
+
+test_description='blame output in various formats on a simple case'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	echo a >file &&
+	git add file
+	test_tick &&
+	git commit -m one &&
+	echo b >>file &&
+	echo c >>file &&
+	echo d >>file &&
+	test_tick &&
+	git commit -a -m two
+'
+
+cat >expect <<'EOF'
+^baf5e0b (A U Thor 2005-04-07 15:13:13 -0700 1) a
+8825379d (A U Thor 2005-04-07 15:14:13 -0700 2) b
+8825379d (A U Thor 2005-04-07 15:14:13 -0700 3) c
+8825379d (A U Thor 2005-04-07 15:14:13 -0700 4) d
+EOF
+test_expect_success 'normal blame output' '
+	git blame file >actual &&
+	test_cmp expect actual
+'
+
+ID1=baf5e0b3869e0b2b2beb395a3720c7b51eac94fc
+COMMIT1='author A U Thor
+author-mail <[email protected]>
+author-time 1112911993
+author-tz -0700
+committer C O Mitter
+committer-mail <[email protected]>
+committer-time 1112911993
+committer-tz -0700
+summary one
+boundary
+filename file'
+ID2=8825379dfb8a1267b58e8e5bcf69eec838f685ec
+COMMIT2='author A U Thor
+author-mail <[email protected]>
+author-time 1112912053
+author-tz -0700
+committer C O Mitter
+committer-mail <[email protected]>
+committer-time 1112912053
+committer-tz -0700
+summary two
+previous baf5e0b3869e0b2b2beb395a3720c7b51eac94fc file
+filename file'
+
+cat >expect <<EOF
+$ID1 1 1 1
+$COMMIT1
+	a
+$ID2 2 2 3
+$COMMIT2
+	b
+$ID2 3 3
+	c
+$ID2 4 4
+	d
+EOF
+test_expect_success 'blame --porcelain output' '
+	git blame --porcelain file >actual &&
+	test_cmp expect actual
+'
+
+test_done
Signature · 1 line
1.7.5.rc2.8.gc085
#33 lore raw

PATCH 2/3 blame: refactor porcelain output

In reply to Jeff King

This is in preparation for adding more porcelain output options. The three changes are:

1. emit_porcelain now receives the format option flags

2. emit_one_suspect_detail takes an optional "repeat" parameter to suppress the "show only once" behavior

3. The code for emitting porcelain suspect is factored into its own function for repeatability.

There should be no functional changes.

---
I broke this out for readability. I can break each of the 3 out into a separate patch if that helps, but it seemed excessive.

 builtin/blame.c |   25 ++++++++++++++++---------
 1 files changed, 16 insertions(+), 9 deletions(-)
builtin/blame.c +16 -9
index 4242e4b..d74e18f 100644
@@ -1484,13 +1484,14 @@ static void write_filename_info(const char *path)
 /*
  * Porcelain/Incremental format wants to show a lot of details per
  * commit.  Instead of repeating this every line, emit it only once,
- * the first time each commit appears in the output.
+ * the first time each commit appears in the output (unless the
+ * user has specifically asked for us to repeat).
  */
-static int emit_one_suspect_detail(struct origin *suspect)
+static int emit_one_suspect_detail(struct origin *suspect, int repeat)
 {
 	struct commit_info ci;
 
-	if (suspect->commit->object.flags & METAINFO_SHOWN)
+	if (!repeat && suspect->commit->object.flags & METAINFO_SHOWN)
 		return 0;
 
 	suspect->commit->object.flags |= METAINFO_SHOWN;
@@ -1529,7 +1530,7 @@ static void found_guilty_entry(struct blame_entry *ent)
 		printf("%s %d %d %d\n",
 		       sha1_to_hex(suspect->commit->object.sha1),
 		       ent->s_lno + 1, ent->lno + 1, ent->num_lines);
-		emit_one_suspect_detail(suspect);
+		emit_one_suspect_detail(suspect, 0);
 		write_filename_info(suspect->path);
 		maybe_flush_or_die(stdout, "stdout");
 	}
@@ -1619,7 +1620,15 @@ static const char *format_time(unsigned long time, const char *tz_str,
 #define OUTPUT_NO_AUTHOR       0200
 #define OUTPUT_SHOW_EMAIL	0400
 
-static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
+static void emit_porcelain_details(struct origin *suspect, int repeat)
+{
+	if (emit_one_suspect_detail(suspect, repeat) ||
+	    (suspect->commit->object.flags & MORE_THAN_ONE_PATH))
+		write_filename_info(suspect->path);
+}
+
+static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent,
+			   int opt)
 {
 	int cnt;
 	const char *cp;
@@ -1633,9 +1642,7 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
 	       ent->s_lno + 1,
 	       ent->lno + 1,
 	       ent->num_lines);
-	if (emit_one_suspect_detail(suspect) ||
-	    (suspect->commit->object.flags & MORE_THAN_ONE_PATH))
-		write_filename_info(suspect->path);
+	emit_porcelain_details(suspect, 0);
 
 	cp = nth_line(sb, ent->lno);
 	for (cnt = 0; cnt < ent->num_lines; cnt++) {
@@ -1756,7 +1763,7 @@ static void output(struct scoreboard *sb, int option)
 
 	for (ent = sb->ent; ent; ent = ent->next) {
 		if (option & OUTPUT_PORCELAIN)
-			emit_porcelain(sb, ent);
+			emit_porcelain(sb, ent, option);
 		else {
 			emit_other(sb, ent, option);
 		}
Signature · 1 line
1.7.5.rc2.8.gc085
#34 lore raw

Re: [PATCH 2/3] blame: refactor porcelain output

Quoted 30 lines · Jeff King

This is in preparation for adding more porcelain output options. The three changes are:

 1. emit_porcelain now receives the format option flags

 2. emit_one_suspect_detail takes an optional "repeat" parameter to suppress the "show only once" behavior

 3. The code for emitting porcelain suspect is factored into its own function for repeatability.

There should be no functional changes.

---
I broke this out for readability. I can break each of the 3 out into a separate patch if that helps, but it seemed excessive.

 builtin/blame.c |   25 ++++++++++++++++---------

 1 files changed, 16 insertions(+), 9 deletions(-)
builtin/blame.c +0 -1
index 4242e4b..d74e18f 100644
@@ -1484,13 +1484,14 @@ static void write_filename_info(const char *path)
  /*
  * Porcelain/Incremental format wants to show a lot of details per
  * commit.  Instead of repeating this every line, emit it only once,
- * the first time each commit appears in the output.

builtin/blame.c static void write_filename_info(const char *path) · line 1494

Quoted 10 lines
builtin/blame.c +4 -2
+ * the first time each commit appears in the output (unless the
+ * user has specifically asked for us to repeat).
  */
-static int emit_one_suspect_detail(struct origin *suspect)
+static int emit_one_suspect_detail(struct origin *suspect, int repeat)
  {
        struct commit_info ci;
 
-       if (suspect->commit->object.flags & METAINFO_SHOWN)
+       if (!repeat && suspect->commit->object.flags & METAINFO_SHOWN)

Maybe would be worth adding parentheses here:

if (!repeat && (...))
return 0;

?

Probably is fine as is though.

#35 lore raw

PATCH 3/3 blame: add --line-porcelain output format

In reply to Jeff King

This is just like --porcelain, except that we always output the commit information for each line, not just the first time it is referenced. This can make quick and dirty scripts much easier to write; see the example added to the blame documentation.

---
I'm not 100% happy with the name, but I couldn't think of anything better. Something like --verbose-porcelain works, but is a little too vague. Suggestions welcome.

 Documentation/blame-options.txt |    5 +++++
 Documentation/git-blame.txt     |   13 +++++++++++++
 builtin/blame.c                 |   10 ++++++++--
 t/t8008-blame-formats.sh        |   19 +++++++++++++++++++
 4 files changed, 45 insertions(+), 2 deletions(-)
Documentation/blame-options.txt +5 -0
index 16e3c68..e76195a 100644
@@ -52,6 +52,11 @@ of lines before or after the line given by <start>.
 --porcelain::
 	Show in a format designed for machine consumption.
 
+--line-porcelain::
+	Show the porcelain format, but output commit information for
+	each line, not just the first time a commit is referenced.
+	Implies --porcelain.
+
 --incremental::
 	Show the result incrementally in a format designed for
 	machine consumption.
Documentation/git-blame.txt +13 -0
index bb8edb4..9516914 100644
@@ -105,6 +105,19 @@ The contents of the actual line is output after the above
 header, prefixed by a TAB. This is to allow adding more
 header elements later.
 
+The porcelain format generally suppresses commit information that has
+already been seen. For example, two lines that are blamed to the same
+commit will both be shown, but the details for that commit will be shown
+only once. This is more efficient, but may require more state be kept by
+the reader. The `--line-porcelain` option can be used to output full
+commit information for each line, allowing simpler (but less efficient)
+usage like:
+
+	# count the number of lines attributed to each author
+	git blame --line-porcelain file |
+	sed -n 's/^author //p' |
+	sort | uniq -c | sort -rn
+
 
 SPECIFYING RANGES
 -----------------
builtin/blame.c +8 -2
index d74e18f..6c26672 100644
@@ -1619,6 +1619,7 @@ static const char *format_time(unsigned long time, const char *tz_str,
 #define OUTPUT_SHOW_SCORE      0100
 #define OUTPUT_NO_AUTHOR       0200
 #define OUTPUT_SHOW_EMAIL	0400
+#define OUTPUT_LINE_PORCELAIN 01000
 
 static void emit_porcelain_details(struct origin *suspect, int repeat)
 {
@@ -1630,6 +1631,7 @@ static void emit_porcelain_details(struct origin *suspect, int repeat)
 static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent,
 			   int opt)
 {
+	int repeat = opt & OUTPUT_LINE_PORCELAIN;
 	int cnt;
 	const char *cp;
 	struct origin *suspect = ent->suspect;
@@ -1642,15 +1644,18 @@ static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent,
 	       ent->s_lno + 1,
 	       ent->lno + 1,
 	       ent->num_lines);
-	emit_porcelain_details(suspect, 0);
+	emit_porcelain_details(suspect, repeat);
 
 	cp = nth_line(sb, ent->lno);
 	for (cnt = 0; cnt < ent->num_lines; cnt++) {
 		char ch;
-		if (cnt)
+		if (cnt) {
 			printf("%s %d %d\n", hex,
 			       ent->s_lno + 1 + cnt,
 			       ent->lno + 1 + cnt);
+			if (repeat)
+				emit_porcelain_details(suspect, 1);
+		}
 		putchar('\t');
 		do {
 			ch = *cp++;
@@ -2307,6 +2312,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
 		OPT_BIT('f', "show-name", &output_option, "Show original filename (Default: auto)", OUTPUT_SHOW_NAME),
 		OPT_BIT('n', "show-number", &output_option, "Show original linenumber (Default: off)", OUTPUT_SHOW_NUMBER),
 		OPT_BIT('p', "porcelain", &output_option, "Show in a format designed for machine consumption", OUTPUT_PORCELAIN),
+		OPT_BIT(0, "line-porcelain", &output_option, "Show porcelain format with per-line commit information", OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN),
 		OPT_BIT('c', NULL, &output_option, "Use the same output mode as git-annotate (Default: off)", OUTPUT_ANNOTATE_COMPAT),
 		OPT_BIT('t', NULL, &output_option, "Show raw timestamp (Default: off)", OUTPUT_RAW_TIMESTAMP),
 		OPT_BIT('l', NULL, &output_option, "Show long commit SHA1 (Default: off)", OUTPUT_LONG_OBJECT_NAME),
t/t8008-blame-formats.sh +19 -0
index 387d1a6..d15f8b3 100755
@@ -68,4 +68,23 @@ test_expect_success 'blame --porcelain output' '
 	test_cmp expect actual
 '
 
+cat >expect <<EOF
+$ID1 1 1 1
+$COMMIT1
+	a
+$ID2 2 2 3
+$COMMIT2
+	b
+$ID2 3 3
+$COMMIT2
+	c
+$ID2 4 4
+$COMMIT2
+	d
+EOF
+test_expect_success 'blame --line-porcelain output' '
+	git blame --line-porcelain file >actual &&
+	test_cmp expect actual
+'
+
 test_done
Signature · 1 line
1.7.5.rc2.8.gc085
#36 lore raw

Re: [PATCH 1/2] tests: eliminate unnecessary setup test assertions

In reply to Jeff King
Quoted 5 lines · Jeff King

So there are definitely particular people who prefer different styles (and I recalled that Junio and I differed on this style point, which is confirmed here). Interestingly, you are the only person to fall right in the middle. I guess that means you are good at emulating surrounding code. :)

Probably my older code leaves out the space more often, and newer code includes it. There is an odd kind of logic that can be used to justify including or not including the space, namely:

In C, a function definition starts with an expression that looks something like a function call, as in "double sin(double x);". So when you want to know everything there is to know about the sine function, you can do a "git grep -F -e 'sin('", and it will return to you its definition and a list of callers.

The shell function definition syntax looks oddly like an old-style C prototype "f()". But do not be misled: to duplicate the above property familiar from C, one needs to include a space before the parentheses, so "git grep -F -e 'f '" will return its definition and a list of callers.

Of course the same argument works backwards: if you want the definition without the callers, then only the spaceless syntax will allow you to grep for 'f()'.

Unlike brace placement, this seems to be a question of style with no right answer. :)

#37 lore raw

PATCH 2/2 tests: teach verify_parents to check for extra parents

In reply to Jonathan Nieder

Currently verify_parents only makes sure that the earlier parents of HEAD match the commits given, and does not care if there are more parents. This makes it harder than one would like to check that, for example, parent reduction works correctly when making an octopus.

Fix it by checking that HEAD^(n+1) is not a valid commit name. Noticed while working on a new test that was supposed to create a fast-forward one commit ahead but actually created a merge.

---
 t/t7600-merge.sh |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)
t/t7600-merge.sh +1 -0
index c665acd..9af748a 100755
@@ -89,6 +89,7 @@ verify_parents () {
 		i=$(expr $i + 1) ||
 		return 1
 	done &&
+	test_must_fail git rev-parse --verify HEAD^$(($# + 1)) &&
 	test_cmp parents.expected parents.actual
 }
 
Signature · 1 line
1.7.5.1
#38 lore raw

Re: [PATCH 2/2] tests: teach verify_parents to check for extra parents

Quoted 20 lines · Jonathan Nieder

Currently verify_parents only makes sure that the earlier parents of HEAD match the commits given, and does not care if there are more parents. This makes it harder than one would like to check that, for example, parent reduction works correctly when making an octopus.

Fix it by checking that HEAD^(n+1) is not a valid commit name. Noticed while working on a new test that was supposed to create a fast-forward one commit ahead but actually created a merge.

---
 t/t7600-merge.sh |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)
t/t7600-merge.sh +0 -0
index c665acd..9af748a 100755

t/t7600-merge.sh verify_parents () { · line 92

Quoted 4 lines
t/t7600-merge.sh +1 -0
@@ -89,6 +89,7 @@ verify_parents () {
 		i=$(expr $i + 1) ||
 		return 1
 	done &&
+	test_must_fail git rev-parse --verify HEAD^$(($# + 1)) &&

Isn't $i at this point the same as that complex $(()) line noise?

Quoted 2 lines
t/t7600-merge.sh +0 -0
 	test_cmp parents.expected parents.actual
 }
#39 lore raw

Re: [PATCH 2/2] tests: teach verify_parents to check for extra parents

Quoted 11 lines · Junio C Hamano

t/t7600-merge.sh verify_parents () { · line 92

Quoted 7 lines · Jonathan Nieder
t/t7600-merge.sh +1 -0
@@ -89,6 +89,7 @@ verify_parents () {
 		i=$(expr $i + 1) ||
 		return 1
 	done &&
+	test_must_fail git rev-parse --verify HEAD^$(($# + 1)) &&

Isn't $i at this point the same as that complex $(()) line noise?

Yes, that sounds better.

#40 lore raw
In reply to Junio C Hamano
Quoted 1 line · Junio C Hamano

Subject: [PATCH] merge: fix branch.<name>.mergeoptions

To be more precise, if I understand correctly:

merge: allow branch.<name>.mergeoptions to override merge.*

Quoted 4 lines

This patch should fix it, even though I now strongly suspect that branch.<name>.mergeoptions that gives a single command line that needs to be parsed was likely to be an ill-conceived idea to begin with. Sigh...

Yes, and introducing branch.<name>.ff and branch.<name>.log might still be a good idea.

The patch looks mostly good. I see only one actual problem, marked with [*] below.

Quoted 17 lines
builtin-merge.c +4 -0
@@ -54,6 +54,7 @@ static size_t use_strategies_nr, use_strategies_alloc;
 static const char **xopts;
 static size_t xopts_nr, xopts_alloc;
 static const char *branch;
+static char *branch_mergeoptions;
 static int verbosity;
 static int allow_rerere_auto;
 
@@ -474,25 +475,33 @@ cleanup:
 	strbuf_release(&bname);
 }
 
+static void parse_branch_merge_options(char *bmo)
+{
+	const char **argv;

builtin-merge.c cleanup: · line 490

Quoted 10 lines
builtin-merge.c +10 -0
+	int argc;
+	char *buf;
+
+	if (!bmo)
+		return;
+	argc = split_cmdline(bmo, &argv);
+	if (argc < 0)
+		die("Bad branch.%s.mergeoptions string", branch);
+	argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+	memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));

This is not new code, but it might make sense to do

	argv[0] = "merge.*.options";

for a saner error message when someone tries

	[branch "master"]
		mergeoptions = --nonsense

builtin-merge.c cleanup: · line 494

Quoted 4 lines
builtin-merge.c +4 -0
+	argc++;
+	parse_options(argc, argv, NULL, builtin_merge_options,
+		      builtin_merge_usage, 0);
+	free(buf);

[*]
This buf seems to be left over. (I don't think the intent is to call free on an uninitialized pointer. ;-))

[...]

builtin-merge.c cleanup: · line 503

Quoted 3 lines
builtin-merge.c +2 -1
-		free(buf);
+		free(branch_mergeoptions);
+		branch_mergeoptions = xstrdup(v);

It is tempting to do

	size_t len;

	len = strlen(v);
	branch_mergeoptions = xrealloc(branch_mergeoptions, len + 1);
	memcpy(branch_mergeoptions, v, len + 1);

but free + xstrdup is simpler and clearer. Makes sense.

builtin-merge.c cleanup: · line 501

Quoted 5 lines
builtin-merge.c +5 -0
+test_expect_success 'merge c1 with c2 (log in config gets overridden)' '
+	(
+		git config --remove-section branch.master
+		git config --remove-section merge
+	)

Since this patch is meant to apply to a very old git, we cannot use test_might_fail. Makes sense: it can be fixed up later to use &&-friendly syntax as part of a series introducing checks to make sure we don't regress in that.

Thanks and hope that helps,
Jonathan

#41 lore raw
Quoted 16 lines · Jonathan Nieder

builtin-merge.c cleanup: · line 490

Quoted 13 lines
builtin-merge.c +13 -0
+static void parse_branch_merge_options(char *bmo)
+{
+	const char **argv;
+	int argc;
+	char *buf;
+
+	if (!bmo)
+		return;
+	argc = split_cmdline(bmo, &argv);
+	if (argc < 0)
+		die("Bad branch.%s.mergeoptions string", branch);
+	argv = xrealloc(argv, sizeof(*argv) * (argc + 2));
+	memmove(argv + 1, argv, sizeof(*argv) * (argc + 1));

This is not new code, but it might make sense to do

Quoted 6 lines
	argv[0] = "merge.*.options";

for a saner error message when someone tries

	[branch "master"]
		mergeoptions = --nonsense

Yes, either we stuff a fixed string "branch.*.mergeoptions" to argv[0], or use another static to recall which variable gave us that value and use it. The former is of course easier and less nicer.

Quoted 8 lines

builtin-merge.c cleanup: · line 494

Quoted 4 lines
builtin-merge.c +4 -0
+	argc++;
+	parse_options(argc, argv, NULL, builtin_merge_options,
+		      builtin_merge_usage, 0);
+	free(buf);

[*]
This buf seems to be left over. (I don't think the intent is to call free on an uninitialized pointer. ;-))

I also forgot to free argv[].

#42 lore raw
In reply to Junio C Hamano
Quoted 8 lines · Junio C Hamano

In any case, your test exposed an ancient breakage ever since the per-branch mergeoptions was introduced back when git-merge was a shell script (aec7b36 (git-merge: add support for branch.<name>.mergeoptions, 2007-09-24).

-- >8 --
Subject: [PATCH] merge: fix branch.<name>.mergeoptions
...

And then on top of that fix, we can do this.

I have a seemingly unrelated change to the existing test but that was because it only made sure that the --ff-only option made the command fail when it should fail, without making sure that it does not interfere when it should succeed. A typical symptom of "showing off shiny new toy because I am too excited" developer disease, I would guess. I didn't want to forget to fix it.

-- >8 --
Subject: [PATCH] merge: introduce merge.ff configuration variable

This variable gives the default setting for --ff, --no-ff or --ff-only options of "git merge" command.

---

* If we were doing the command line option of "merge" from scratch today, we probably would have done --ff, --ff=no, and --ff=only, instead of a separate --ff-only. We could still add the latter two as a consistency synonyms without deprecating anything, though.

 Documentation/merge-config.txt |   10 +++++++++
 builtin/merge.c                |    9 ++++++++
 t/t7600-merge.sh               |   43 ++++++++++++++++++++++++++++++++++++++-
 3 files changed, 60 insertions(+), 2 deletions(-)
Documentation/merge-config.txt +10 -0
index 8920258..861bd6f 100644
@@ -16,6 +16,16 @@ merge.defaultToUpstream::
 	to their corresponding remote tracking branches, and the tips of
 	these tracking branches are merged.
 
+merge.ff::
+	By default, git does not create an extra merge commit when merging
+	a commit that is a descendant of the current commit. Instead, the
+	tip of the current branch is fast-forwarded. When set to `false`,
+	this variable tells git to create an extra merge commit in such
+	a case (equivalent to giving the `--no-ff` option from the command
+	line). When set to `only`, only such fast-forward merges are
+	allowed (equivalent to giving the `--ff-only` option from the
+	command line).
+
 merge.log::
 	In addition to branch names, populate the log message with at
 	most the specified number of one-line descriptions from the
builtin/merge.c +9 -0
index 4fa789a..1c3ff13 100644
@@ -550,6 +550,15 @@ static int git_merge_config(const char *k, const char *v, void *cb)
 		if (is_bool && shortlog_len)
 			shortlog_len = DEFAULT_MERGE_LOG_LEN;
 		return 0;
+	} else if (!strcmp(k, "merge.ff")) {
+		int boolval = git_config_maybe_bool(k, v);
+		if (0 <= boolval) {
+			allow_fast_forward = boolval;
+		} else if (v && !strcmp(v, "only")) {
+			allow_fast_forward = 1;
+			fast_forward_only = 1;
+		} /* do not barf on values from future versions of git */
+		return 0;
 	} else if (!strcmp(k, "merge.defaulttoupstream")) {
 		default_to_upstream = git_config_bool(k, v);
 		return 0;
t/t7600-merge.sh +41 -2

59 lines not on this page · raw message

Signature · 1 line
1.7.5.1.268.gce5bd
#43 lore raw
Quoted 7 lines · Junio C Hamano
Documentation/merge-config.txt +1 -0
@@ -16,6 +16,16 @@ merge.defaultToUpstream::
 	to their corresponding remote tracking branches, and the tips of
 	these tracking branches are merged.
 
+merge.ff::

[...]

Documentation/merge-config.txt

Quoted 3 lines
Documentation/merge-config.txt +3 -0
+	line). When set to `only`, only such fast-forward merges are
+	allowed (equivalent to giving the `--ff-only` option from the
+	command line).

A habitual "git pull" user that uses git in a read-only fashion to get the latest development snapshot of a project's code would probably find this handy.

Quoted 4 lines
builtin/merge.c +0 -0
@@ -550,6 +550,15 @@ static int git_merge_config(const char *k, const char *v, void *cb)
 		if (is_bool && shortlog_len)

builtin/merge.c static int git_merge_config(const char *k, const char *v, void *cb) · line 560

Quoted 10 lines
builtin/merge.c +8 -0
 			shortlog_len = DEFAULT_MERGE_LOG_LEN;
 		return 0;
+	} else if (!strcmp(k, "merge.ff")) {
+		int boolval = git_config_maybe_bool(k, v);
+		if (0 <= boolval) {
+			allow_fast_forward = boolval;
+		} else if (v && !strcmp(v, "only")) {
+			allow_fast_forward = 1;
+			fast_forward_only = 1;
+		} /* do not barf on values from future versions of git */

Perhaps deserves a test, like so (meant for squashing)?

-- >8 --
Subject: tests: check git does not barf on merge.ff values for future versions of git

Maybe some day in the future we will want to support a syntax like

[merge]
ff = branch1
ff = branch2
ff = branch3

in addition to the currently permitted "true", "false", and "only" values. So make sure we continue to treat such configurations as though an unknown variable had been defined rather than erroring out, until it is time to implement such a thing, so configuration files using such a facility can be shared between present and future git.

While at it, add a few missing && and start the "combining --squash and --no-ff" test with a known state so we can be sure it does not succeed or fail for the wrong reason.

---
 t/t7600-merge.sh |   16 ++++++++++++++--
 1 files changed, 14 insertions(+), 2 deletions(-)
t/t7600-merge.sh +14 -2
index 89e0b77..1e20f2e 100755
@@ -35,6 +35,7 @@ printf '%s\n' 1 2 3 4 5 6 7 8 '9 X' >file.9
 printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >result.1
 printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 9 >result.1-5
 printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 '9 X' >result.1-5-9
+>empty
 
 create_merge_msgs () {
 	echo "Merge commit 'c2'" >msg.1-5 &&
@@ -475,8 +476,8 @@ test_debug 'git log --graph --decorate --oneline --all'
 
 test_expect_success 'combine branch.master.mergeoptions with merge.ff' '
 	git reset --hard c0 &&
-	git config branch.master.mergeoptions --ff
-	git config merge.ff false
+	git config branch.master.mergeoptions --ff &&
+	git config merge.ff false &&
 	test_tick &&
 	git merge c1 &&
 	git config --remove-section "branch.master" &&
@@ -485,7 +486,18 @@ test_expect_success 'combine branch.master.mergeoptions with merge.ff' '
 	verify_parents "$c0"
 '
 
+test_expect_success 'tolerate unknown values for merge.ff' '
+	git reset --hard c0 &&
+	git config merge.ff something-new &&
+	test_tick &&
+	git merge c1 2>message &&
+	git config --remove-section "merge" &&
+	verify_head "$c1" &&
+	test_cmp empty message
+'
+
 test_expect_success 'combining --squash and --no-ff is refused' '
+	git reset --hard c0 &&
 	test_must_fail git merge --squash --no-ff c1 &&
 	test_must_fail git merge --no-ff --squash c1
 '
Signature · 1 line
1.7.5.1
#44 lore raw
In reply to Michael Grubb

builtin/merge.c cleanup: · line 508

Quoted 12 lines · Michael Grubb
builtin/merge.c +1 -3
index 0bdd19a..9e5b6bd 100644
@@ -505,9 +505,18 @@ cleanup:
 
 static int git_merge_config(const char *k, const char *v, void *cb)
 {
-	if (branch && !prefixcmp(k, "branch.") &&
-		!prefixcmp(k + 7, branch) &&
-		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+	static int branch_merge_options_set = 0;

I prefer to avoid "static int" that you cannot easily clear here. It would make it impossible to call the function twice.

I think it is easily doable by using the callback parameter (cb).

I am also wondering how this will scale, both in the direction of "later it is likely that we would want to support a glob not just '*' here", and also "later it is likely that we would want to support other per-branch variables, not just "mergeoptions" here".

#45 lore raw
Quoted 18 lines · Junio C Hamano

builtin/merge.c cleanup: · line 508

Quoted 12 lines · Michael Grubb
builtin/merge.c +1 -3
index 0bdd19a..9e5b6bd 100644
@@ -505,9 +505,18 @@ cleanup:
 
 static int git_merge_config(const char *k, const char *v, void *cb)
 {
-	if (branch && !prefixcmp(k, "branch.") &&
-		!prefixcmp(k + 7, branch) &&
-		!strcmp(k + 7 + strlen(branch), ".mergeoptions")) {
+	static int branch_merge_options_set = 0;

I prefer to avoid "static int" that you cannot easily clear here. It would make it impossible to call the function twice.

Quoted 2 lines

I think it is easily doable by using the callback parameter (cb).

Too right. I've reworked that bit to use the cb parameter. With an eye to the future I've created a new struct for this for future's sake, so that this one feature doesn't monopolize the cb parameter in the future.

Quoted 4 lines

I am also wondering how this will scale, both in the direction of "later it is likely that we would want to support a glob not just '*' here", and also "later it is likely that we would want to support other per-branch variables, not just "mergeoptions" here".

It seems that the easiest way would be to move this particular feature out into it's own function at some point to pave the way for doing more complex things with the branch configurations.

New patch forthcoming.

Quoted 5 lines

--
To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html