Subject: [PATCH v4 2/3] merge: Add merge.renames config setting

From: Ben Peart <[email-masked]>
Date: Wed, 2 May 2018 16:01:14 +0000

Add the ability to control rename detection for merge via a config setting.
This setting behaves the same and defaults to the value of diff.renames but only
applies to merge.

Reviewed-by: Johannes Schindelin <[email-masked]>
Helped-by: Elijah Newren <[email-masked]>
Signed-off-by: Ben Peart <[email-masked]>
---
 Documentation/merge-config.txt            |  5 ++++
 Documentation/merge-strategies.txt        | 11 ++++++---
 diff.c                                    |  2 +-
 diff.h                                    |  1 +
 merge-recursive.c                         | 30 ++++++++++++++++++-----
 merge-recursive.h                         |  8 +++++-
 t/t3034-merge-recursive-rename-options.sh | 18 ++++++++++++++
 7 files changed, 63 insertions(+), 12 deletions(-)

diff --git a/Documentation/merge-config.txt b/Documentation/merge-config.txt
index 48ee3bce77..662c2713ca 100644
--- a/Documentation/merge-config.txt
+++ b/Documentation/merge-config.txt
@@ -38,6 +38,11 @@ merge.renameLimit::
 	diff.renameLimit. This setting has no effect if rename detection
 	is turned off.
 
+merge.renames::
+	Whether and how Git detects renames.  If set to "false",
+	rename detection is disabled. If set to "true", basic rename
+	detection is enabled.  Defaults to the value of diff.renames.
+
 merge.renormalize::
 	Tell Git that canonical representation of files in the
 	repository has changed over time (e.g. earlier commits record
diff --git a/Documentation/merge-strategies.txt b/Documentation/merge-strategies.txt
index fd5d748d1b..30acc99232 100644
--- a/Documentation/merge-strategies.txt
+++ b/Documentation/merge-strategies.txt
@@ -23,8 +23,9 @@ recursive::
 	causing mismerges by tests done on actual merge commits
 	taken from Linux 2.6 kernel development history.
 	Additionally this can detect and handle merges involving
-	renames.  This is the default merge strategy when
-	pulling or merging one branch.
+	renames, but currently cannot make use of detected
+	copies.  This is the default merge strategy when pulling
+	or merging one branch.
 +
 The 'recursive' strategy can take the following options:
 
@@ -84,12 +85,14 @@ no-renormalize;;
 	`merge.renormalize` configuration variable.
 
 no-renames;;
-	Turn off rename detection.
+	Turn off rename detection. This overrides the `merge.renames`
+	configuration variable.
 	See also linkgit:git-diff[1] `--no-renames`.
 
 find-renames[=<n>];;
 	Turn on rename detection, optionally setting the similarity
-	threshold.  This is the default.
+	threshold.  This is the default. This overrides the
+	'merge.renames' configuration variable.
 	See also linkgit:git-diff[1] `--find-renames`.
 
 rename-threshold=<n>;;
diff --git a/diff.c b/diff.c
index fb22b19f09..e744b35cdc 100644
--- a/diff.c
+++ b/diff.c
@@ -177,7 +177,7 @@ static int parse_submodule_params(struct diff_options *options, const char *valu
 	return 0;
 }
 
-static int git_config_rename(const char *var, const char *value)
+int git_config_rename(const char *var, const char *value)
 {
 	if (!value)
 		return DIFF_DETECT_RENAME;
diff --git a/diff.h b/diff.h
index 7cf276f077..966fc8fce6 100644
--- a/diff.h
+++ b/diff.h
@@ -321,6 +321,7 @@ extern int git_diff_ui_config(const char *var, const char *value, void *cb);
 extern void diff_setup(struct diff_options *);
 extern int diff_opt_parse(struct diff_options *, const char **, int, const char *);
 extern void diff_setup_done(struct diff_options *);
+extern int git_config_rename(const char *var, const char *value);
 
 #define DIFF_DETECT_RENAME	1
 #define DIFF_DETECT_COPY	2
diff --git a/merge-recursive.c b/merge-recursive.c
index 5f42c677d5..372ffbbacc 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -1524,7 +1524,15 @@ static struct diff_queue_struct *get_diffpairs(struct merge_options *o,
 	diff_setup(&opts);
 	opts.flags.recursive = 1;
 	opts.flags.rename_empty = 0;
-	opts.detect_rename = DIFF_DETECT_RENAME;
+	opts.detect_rename = merge_detect_rename(o);
+	/*
+	 * We do not have logic to handle the detection of copies.  In
+	 * fact, it may not even make sense to add such logic: would we
+	 * really want a change to a base file to be propagated through
+	 * multiple other files by a merge?
+	 */
+	if (opts.detect_rename > DIFF_DETECT_RENAME)
+		opts.detect_rename = DIFF_DETECT_RENAME;
 	opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit :
 			    o->diff_rename_limit >= 0 ? o->diff_rename_limit :
 			    1000;
@@ -2564,7 +2572,7 @@ static int handle_renames(struct merge_options *o,
 	ri->head_renames = NULL;
 	ri->merge_renames = NULL;
 
-	if (!o->detect_rename)
+	if (!merge_detect_rename(o))
 		return 1;
 
 	head_pairs = get_diffpairs(o, common, head);
@@ -3241,9 +3249,18 @@ int merge_recursive_generic(struct merge_options *o,
 
 static void merge_recursive_config(struct merge_options *o)
 {
+	char *value = NULL;
 	git_config_get_int("merge.verbosity", &o->verbosity);
 	git_config_get_int("diff.renamelimit", &o->diff_rename_limit);
 	git_config_get_int("merge.renamelimit", &o->merge_rename_limit);
+	if (!git_config_get_string("diff.renames", &value)) {
+		o->diff_detect_rename = git_config_rename("diff.renames", value);
+		free(value);
+	}
+	if (!git_config_get_string("merge.renames", &value)) {
+		o->merge_detect_rename = git_config_rename("merge.renames", value);
+		free(value);
+	}
 	git_config(git_xmerge_config, NULL);
 }
 
@@ -3256,7 +3273,8 @@ void init_merge_options(struct merge_options *o)
 	o->diff_rename_limit = -1;
 	o->merge_rename_limit = -1;
 	o->renormalize = 0;
-	o->detect_rename = 1;
+	o->diff_detect_rename = -1;
+	o->merge_detect_rename = -1;
 	merge_recursive_config(o);
 	merge_verbosity = getenv("GIT_MERGE_VERBOSITY");
 	if (merge_verbosity)
@@ -3307,16 +3325,16 @@ int parse_merge_opt(struct merge_options *o, const char *s)
 	else if (!strcmp(s, "no-renormalize"))
 		o->renormalize = 0;
 	else if (!strcmp(s, "no-renames"))
-		o->detect_rename = 0;
+		o->merge_detect_rename = 0;
 	else if (!strcmp(s, "find-renames")) {
-		o->detect_rename = 1;
+		o->merge_detect_rename = 1;
 		o->rename_score = 0;
 	}
 	else if (skip_prefix(s, "find-renames=", &arg) ||
 		 skip_prefix(s, "rename-threshold=", &arg)) {
 		if ((o->rename_score = parse_rename_score(&arg)) == -1 || *arg != 0)
 			return -1;
-		o->detect_rename = 1;
+		o->merge_detect_rename = 1;
 	}
 	else
 		return -1;
diff --git a/merge-recursive.h b/merge-recursive.h
index d863cf8867..c1d9b5b3d9 100644
--- a/merge-recursive.h
+++ b/merge-recursive.h
@@ -18,7 +18,8 @@ struct merge_options {
 	unsigned renormalize : 1;
 	long xdl_opts;
 	int verbosity;
-	int detect_rename;
+	int diff_detect_rename;
+	int merge_detect_rename;
 	int diff_rename_limit;
 	int merge_rename_limit;
 	int rename_score;
@@ -55,6 +56,11 @@ struct collision_entry {
 	struct string_list source_files;
 	unsigned reported_already:1;
 };
+inline int merge_detect_rename(struct merge_options *o)
+{
+	return o->merge_detect_rename >= 0 ? o->merge_detect_rename :
+		o->diff_detect_rename >= 0 ? o->diff_detect_rename : 1;
+}
 
 /* merge_trees() but with recursive ancestor consolidation */
 int merge_recursive(struct merge_options *o,
diff --git a/t/t3034-merge-recursive-rename-options.sh b/t/t3034-merge-recursive-rename-options.sh
index b9c4028496..3d9fae68c4 100755
--- a/t/t3034-merge-recursive-rename-options.sh
+++ b/t/t3034-merge-recursive-rename-options.sh
@@ -309,4 +309,22 @@ test_expect_success 'last wins in --find-renames=<m> --rename-threshold=<n>' '
 	check_threshold_0
 '
 
+test_expect_success 'merge.renames disables rename detection' '
+	git read-tree --reset -u HEAD &&
+	git -c merge.renames=false merge-recursive $tail &&
+	check_no_renames
+'
+
+test_expect_success 'merge.renames defaults to diff.renames' '
+	git read-tree --reset -u HEAD &&
+	git -c diff.renames=false merge-recursive $tail &&
+	check_no_renames
+'
+
+test_expect_success 'merge.renames overrides diff.renames' '
+	git read-tree --reset -u HEAD &&
+	test_must_fail git -c diff.renames=false -c merge.renames=true merge-recursive $tail &&
+	$check_50
+'
+
 test_done
-- 
2.17.0.windows.1