threads / patch / 41873

patchdiffcore: fix iteration order of identical files during rename detection

Subject: [PATCH] diffcore: fix iteration order of identical files during rename detection

## tl;dr

2 messages between Mar 30, 2016 and Mar 30, 2016. Diffs are folded; open one to read it.

replies: 1people: 2as markdown or json

SZEDER Gábor· Mar 30, 2016, 08:35 UTC · lore

If the two paths 'dir/A/file' and 'dir/B/file' have identical content and the parent directory is renamed, e.g. 'git mv dir other-dir', then diffcore reports the following exact renames:

    renamed:    dir/B/file -> other-dir/A/file
    renamed:    dir/A/file -> other-dir/B/file

While technically not wrong, this is confusing not only for the user, but also for git commands that make decisions based on rename information, e.g. 'git log --follow other-dir/A/file' follows 'dir/B/file' past the rename.

This behavior is a side effect of commit v2.0.0-rc4~8^2~14 (diffcore-rename.c: simplify finding exact renames, 2013-11-14): the hashmap storing sources returns entries from the same bucket, i.e. sources matching the current destination, in LIFO order. Thus the iteration first examines 'other-dir/A/file' and 'dir/B/file' and, upon finding identical content and basename, reports an exact rename.

Other hashmap users are apparently happy with the current iteration order over the entries of a bucket. Changing the iteration order would risk upsetting other hashmap users and would increase the memory footprint of each bucket by a pointer to the tail element.

Fill the hashmap with source entries in reverse order to restore the original exact rename detection behavior.

Reported-by: Bill Okara <billokara@gmail.com>
Signed-off-by: SZEDER Gábor <szeder@ira.uka.de>
---

Resend of the patch, with a slightly updated commit message, included in

  http://thread.gmane.org/gmane.comp.version-control.git/287281/focus=287570

Being embedded with scissors in an email without Junio among the recipients on the day the first -rc was tagged... no wonder it flew below the radar.

 diffcore-rename.c      |  6 ++++--
 t/t4001-diff-rename.sh | 11 +++++++++++
 2 files changed, 15 insertions(+), 2 deletions(-)
Show changes to 2 files +15 −2

diffcore-rename.c, t/t4001-diff-rename.sh

diff --git a/diffcore-rename.c b/diffcore-rename.c
index 3b3c1ed535e7..7f03eb5a0404 100644
--- a/diffcore-rename.c
+++ b/diffcore-rename.c
@@ -340,9 +340,11 @@ static int find_exact_renames(struct diff_options *options)
 	int i, renames = 0;
 	struct hashmap file_table;
 
-	/* Add all sources to the hash table */
+	/* Add all sources to the hash table in reverse order, because
+	 * later on they will be retrieved in LIFO order.
+	 */
 	hashmap_init(&file_table, NULL, rename_src_nr);
-	for (i = 0; i < rename_src_nr; i++)
+	for (i = rename_src_nr-1; i >= 0; i--)
 		insert_file_table(&file_table, i, rename_src[i].p->one);
 
 	/* Walk the destinations and find best source match */
diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh
index 2f327b749588..ed90c6c6f984 100755
--- a/t/t4001-diff-rename.sh
+++ b/t/t4001-diff-rename.sh
@@ -77,6 +77,17 @@ test_expect_success 'favour same basenames even with minor differences' '
 	git show HEAD:path1 | sed "s/15/16/" > subdir/path1 &&
 	git status | test_i18ngrep "renamed: .*path1 -> subdir/path1"'
 
+test_expect_success 'two files with same basename and same content' '
+	git reset --hard &&
+	mkdir -p dir/A dir/B &&
+	cp path1 dir/A/file &&
+	cp path1 dir/B/file &&
+	git add dir &&
+	git commit -m 2 &&
+	git mv dir other-dir &&
+	git status | test_i18ngrep "renamed: .*dir/A/file -> other-dir/A/file"
+'
+
 test_expect_success 'setup for many rename source candidates' '
 	git reset --hard &&
 	for i in 0 1 2 3 4 5 6 7 8 9;
-- 
2.8.0.46.gb821760
Junio C Hamano· Mar 30, 2016, 20:51 UTC · re: SZEDER Gábor · lore

Re: [PATCH] diffcore: fix iteration order of identical files during rename detection

SZEDER Gábor <szeder@ira.uka.de> writes:
> Fill the hashmap with source entries in reverse order to restore the
> original exact rename detection behavior.

Thanks for digging out and fixing this unintended regression that happened long time ago. Will queue.

Show 58 quoted lines
>
> Reported-by: Bill Okara <billokara@gmail.com>
> Signed-off-by: SZEDER Gábor <szeder@ira.uka.de>
> ---
>
> Resend of the patch, with a slightly updated commit message, included
> in
>
>   http://thread.gmane.org/gmane.comp.version-control.git/287281/focus=287570
>
> Being embedded with scissors in an email without Junio among the
> recipients on the day the first -rc was tagged...  no wonder it flew
> below the radar.
>
>  diffcore-rename.c      |  6 ++++--
>  t/t4001-diff-rename.sh | 11 +++++++++++
>  2 files changed, 15 insertions(+), 2 deletions(-)
>
> diff --git a/diffcore-rename.c b/diffcore-rename.c
> index 3b3c1ed535e7..7f03eb5a0404 100644
> --- a/diffcore-rename.c
> +++ b/diffcore-rename.c
> @@ -340,9 +340,11 @@ static int find_exact_renames(struct diff_options *options)
>  	int i, renames = 0;
>  	struct hashmap file_table;
>  
> -	/* Add all sources to the hash table */
> +	/* Add all sources to the hash table in reverse order, because
> +	 * later on they will be retrieved in LIFO order.
> +	 */
>  	hashmap_init(&file_table, NULL, rename_src_nr);
> -	for (i = 0; i < rename_src_nr; i++)
> +	for (i = rename_src_nr-1; i >= 0; i--)
>  		insert_file_table(&file_table, i, rename_src[i].p->one);
>  
>  	/* Walk the destinations and find best source match */
> diff --git a/t/t4001-diff-rename.sh b/t/t4001-diff-rename.sh
> index 2f327b749588..ed90c6c6f984 100755
> --- a/t/t4001-diff-rename.sh
> +++ b/t/t4001-diff-rename.sh
> @@ -77,6 +77,17 @@ test_expect_success 'favour same basenames even with minor differences' '
>  	git show HEAD:path1 | sed "s/15/16/" > subdir/path1 &&
>  	git status | test_i18ngrep "renamed: .*path1 -> subdir/path1"'
>  
> +test_expect_success 'two files with same basename and same content' '
> +	git reset --hard &&
> +	mkdir -p dir/A dir/B &&
> +	cp path1 dir/A/file &&
> +	cp path1 dir/B/file &&
> +	git add dir &&
> +	git commit -m 2 &&
> +	git mv dir other-dir &&
> +	git status | test_i18ngrep "renamed: .*dir/A/file -> other-dir/A/file"
> +'
> +
>  test_expect_success 'setup for many rename source candidates' '
>  	git reset --hard &&
>  	for i in 0 1 2 3 4 5 6 7 8 9;

← back to recent threads