Subject: [PATCH] merge: ignore whitespace changes when detecting renames

From: "Stefan Muenzel via GitGitGadget" <[email-masked]>
Date: Wed, 25 Oct 2023 12:14:16 +0000

From: Stefan Muenzel <[email-masked]>

The options ignore-space-changes and ignore-all-space for
merge strategies based on "ort" are now propagated
to the computation of file renames, by normalizing spacing
changes when computing file hashes.

Signed-off-by: Stefan Muenzel <[email-masked]>
---
    merge: ignore whitespace changes when detecting renames

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1600%2Fsmuenzel%2Frenames-ignore-whitespace-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1600/smuenzel/renames-ignore-whitespace-v1
Pull-Request: https://github.com/git/git/pull/1600

 diff.c            |  4 ++++
 diffcore-delta.c  | 14 ++++++++++++++
 diffcore-rename.c |  9 +++++++++
 diffcore.h        |  2 ++
 merge-recursive.c |  1 +
 5 files changed, 30 insertions(+)

diff --git a/diff.c b/diff.c
index 2c602df10a3..916cc38f57c 100644
--- a/diff.c
+++ b/diff.c
@@ -4081,6 +4081,7 @@ int diff_populate_filespec(struct repository *r,
 {
 	int size_only = options ? options->check_size_only : 0;
 	int check_binary = options ? options->check_binary : 0;
+	int ignore_whitespace = options ? options->ignore_whitespace : 0;
 	int err = 0;
 	int conv_flags = global_conv_flags_eol;
 	/*
@@ -4090,6 +4091,9 @@ int diff_populate_filespec(struct repository *r,
 	if (conv_flags & CONV_EOL_RNDTRP_DIE)
 		conv_flags = CONV_EOL_RNDTRP_WARN;
 
+	if (ignore_whitespace)
+		s->ignore_whitespace = 1;
+
 	if (!DIFF_FILE_VALID(s))
 		die("internal error: asking to populate invalid file.");
 	if (S_ISDIR(s->mode))
diff --git a/diffcore-delta.c b/diffcore-delta.c
index c30b56e983b..e2bb5f9133a 100644
--- a/diffcore-delta.c
+++ b/diffcore-delta.c
@@ -130,6 +130,7 @@ static struct spanhash_top *hash_chars(struct repository *r,
 	unsigned char *buf = one->data;
 	unsigned int sz = one->size;
 	int is_text = !diff_filespec_is_binary(r, one);
+	int ignore_whitespace = one->ignore_whitespace;
 
 	i = INITIAL_HASH_SIZE;
 	hash = xmalloc(st_add(sizeof(*hash),
@@ -149,6 +150,19 @@ static struct spanhash_top *hash_chars(struct repository *r,
 		if (is_text && c == '\r' && sz && *buf == '\n')
 			continue;
 
+		if (is_text && ignore_whitespace && isspace(c)) {
+			if (sz) {
+				char next = *buf;
+				if ( c == '\n' && next == '\n')
+					continue;
+				else if ( c != '\n' && isspace(next))
+					continue;
+			}
+			if ( c != '\n')
+				/* Normalize whitespace to space*/
+				c = ' ';
+		}
+
 		accum1 = (accum1 << 7) ^ (accum2 >> 25);
 		accum2 = (accum2 << 7) ^ (old_1 >> 25);
 		accum1 += c;
diff --git a/diffcore-rename.c b/diffcore-rename.c
index 5a6e2bcac71..ee68442afc3 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -14,6 +14,7 @@
 #include "string-list.h"
 #include "strmap.h"
 #include "trace2.h"
+#include "xdiff-interface.h"
 
 /* Table of rename/copy destinations */
 
@@ -950,6 +951,10 @@ static int find_basename_matches(struct diff_options *options,
 		.info = info
 	};
 
+	dpf_options.ignore_whitespace =
+		DIFF_XDL_TST(options, IGNORE_WHITESPACE_CHANGE)
+		|| DIFF_XDL_TST(options, IGNORE_WHITESPACE);
+
 	/*
 	 * Create maps of basename -> fullname(s) for remaining sources and
 	 * dests.
@@ -1402,6 +1407,10 @@ void diffcore_rename_extended(struct diff_options *options,
 		.repo = options->repo
 	};
 
+	dpf_options.ignore_whitespace =
+		DIFF_XDL_TST(options, IGNORE_WHITESPACE_CHANGE)
+		|| DIFF_XDL_TST(options, IGNORE_WHITESPACE);
+
 	trace2_region_enter("diff", "setup", options->repo);
 	info.setup = 0;
 	assert(!dir_rename_count || strmap_empty(dir_rename_count));
diff --git a/diffcore.h b/diffcore.h
index 5ffe4ec788f..d6bee67e646 100644
--- a/diffcore.h
+++ b/diffcore.h
@@ -61,6 +61,7 @@ struct diff_filespec {
 	unsigned has_more_entries : 1; /* only appear in combined diff */
 	/* data should be considered "binary"; -1 means "don't know yet" */
 	signed int is_binary : 2;
+	unsigned ignore_whitespace : 1;
 	struct userdiff_driver *driver;
 };
 
@@ -78,6 +79,7 @@ void diff_queued_diff_prefetch(void *repository);
 struct diff_populate_filespec_options {
 	unsigned check_size_only : 1;
 	unsigned check_binary : 1;
+	unsigned ignore_whitespace : 1;
 
 	/*
 	 * If an object is missing, diff_populate_filespec() will invoke this
diff --git a/merge-recursive.c b/merge-recursive.c
index e3beb0801b1..0e52c45158a 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -1894,6 +1894,7 @@ static struct diff_queue_struct *get_diffpairs(struct merge_options *opt,
 	repo_diff_setup(opt->repo, &opts);
 	opts.flags.recursive = 1;
 	opts.flags.rename_empty = 0;
+	opts.xdl_opts = opt->xdl_opts;
 	opts.detect_rename = merge_detect_rename(opt);
 	/*
 	 * We do not have logic to handle the detection of copies.  In

base-commit: 2e8e77cbac8ac17f94eee2087187fa1718e38b14
-- 
gitgitgadget