diff options
author | Paul Eggert <eggert@cs.ucla.edu> | 2010-08-12 17:55:05 -0700 |
---|---|---|
committer | Paul Eggert <eggert@cs.ucla.edu> | 2010-08-12 17:55:38 -0700 |
commit | f2ad578b241713fa81d98b3573fa42397d2ea3f8 (patch) | |
tree | a1ccadcb8e7dc17f82929425e120208891304509 | |
parent | a0e9e5e67a6a34e131eb392ddb99569bc973748e (diff) | |
download | diffutils-f2ad578b241713fa81d98b3573fa42397d2ea3f8.tar.gz |
diff: avoid spurious diffs when two distinct dir entries compare equal
Problem reported by Christoph Anton Mitterer in:
http://lists.gnu.org/archive/html/bug-diffutils/2010-08/msg00000.html
* NEWS: Mention this bug fix.
* src/dir.c (compare_names_for_qsort): Fall back on file_name_cmp
if two distinct entries in the same directory compare equal.
(diff_dirs): Prefer a file_name_cmp match when available.
* tests/Makefile.am (TESTS): New test colliding-file-names.
* tests/colliding-file-names: New file.
-rw-r--r-- | NEWS | 5 | ||||
-rw-r--r-- | src/dir.c | 41 | ||||
-rw-r--r-- | tests/Makefile.am | 1 | ||||
-rw-r--r-- | tests/colliding-file-names | 20 |
4 files changed, 65 insertions, 2 deletions
@@ -2,6 +2,11 @@ GNU diffutils NEWS -*- outline -*- * Noteworthy changes in release ?.? (????-??-??) [?] +** Bug fixes + + diff no longer reports spurious differences merely because two entries + in the same directory have names that compare equal in the current + locale, or compare equal because --ignore-file-name-case was given. * Noteworthy changes in release 3.0 (2010-05-03) [stable] @@ -166,14 +166,16 @@ compare_names (char const *name1, char const *name2) : file_name_cmp (name1, name2)); } -/* A wrapper for compare_names suitable as an argument for qsort. */ +/* Compare names FILE1 and FILE2 when sorting a directory. + Prefer filtered comparison, breaking ties with file_name_cmp. */ static int compare_names_for_qsort (void const *file1, void const *file2) { char const *const *f1 = file1; char const *const *f2 = file2; - return compare_names (*f1, *f2); + int diff = compare_names (*f1, *f2); + return diff ? diff : file_name_cmp (*f1, *f2); } /* Compare the contents of two directories named in CMP. @@ -253,6 +255,41 @@ diff_dirs (struct comparison const *cmp, pretend the "next name" in that dir is very large. */ int nameorder = (!*names[0] ? 1 : !*names[1] ? -1 : compare_names (*names[0], *names[1])); + + /* Prefer a file_name_cmp match if available. This algorithm is + O(N**2), where N is the number of names in a directory + that compare_names says are all equal, but in practice N + is so small it's not worth tuning. */ + if (nameorder == 0) + { + int raw_order = file_name_cmp (*names[0], *names[1]); + if (raw_order != 0) + { + int greater_side = raw_order < 0; + int lesser_side = 1 - greater_side; + char const **lesser = names[lesser_side]; + char const *greater_name = *names[greater_side]; + char const **p; + + for (p = lesser + 1; + *p && compare_names (*p, greater_name) == 0; + p++) + { + int cmp = file_name_cmp (*p, greater_name); + if (0 <= cmp) + { + if (cmp == 0) + { + memmove (lesser + 1, lesser, + (char *) p - (char *) lesser); + *lesser = greater_name; + } + break; + } + } + } + } + int v1 = (*handle_file) (cmp, 0 < nameorder ? 0 : *names[0]++, nameorder < 0 ? 0 : *names[1]++); diff --git a/tests/Makefile.am b/tests/Makefile.am index 6a4858c..242d501 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,6 +1,7 @@ TESTS = \ basic \ binary \ + colliding-file-names \ help-version \ function-line-vs-leading-space \ label-vs-func \ diff --git a/tests/colliding-file-names b/tests/colliding-file-names new file mode 100644 index 0000000..f14dce7 --- /dev/null +++ b/tests/colliding-file-names @@ -0,0 +1,20 @@ +#!/bin/sh +# Check that diff responds well if a directory has multiple file names +# that compare equal. + +: ${srcdir=.} +. "$srcdir/init.sh"; path_prepend_ ../src + +mkdir d1 d2 || fail=1 + +for i in abc abC aBc aBC Abc AbC ABc ABC; do + echo $i >d1/$i || fail=1 +done + +for i in ABC ABc AbC Abc aBC aBc abC abc; do + echo $i >d2/$i || fail=1 +done + +diff -r --ignore-file-name-case d1 d2 || fail=1 + +Exit $fail |