]> git.ipfire.org Git - thirdparty/git.git/commitdiff
entry: report more colliding paths
authorJohannes Schindelin <johannes.schindelin@gmx.de>
Thu, 28 Mar 2024 08:44:28 +0000 (09:44 +0100)
committerJohannes Schindelin <johannes.schindelin@gmx.de>
Wed, 17 Apr 2024 20:30:07 +0000 (22:30 +0200)
In b878579ae7 (clone: report duplicate entries on case-insensitive
filesystems, 2018-08-17) code was added to warn about index entries that
resolve to the same file system entity (usually the cause is a
case-insensitive filesystem).

In Git for Windows, where inodes are not trusted (because of a
performance trade-off, inodes are equal to 0 by default), that check
does not compare inode numbers but the verbatim path.

This logic works well when index entries' paths differ only in case.

However, for file/directory conflicts only the file's path was reported,
leaving the user puzzled with what that path collides.

Let's try ot catch colliding paths even if one path is the prefix of the
other. We do this also in setups where the file system is case-sensitive
because the inode check would not be able to catch those collisions.

While not a complete solution (for example, on macOS, Unicode
normalization could also lead to file/directory conflicts but be missed
by this logic), it is at least another defensive layer on top of what
the previous commits added.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
dir.c
dir.h
entry.c

diff --git a/dir.c b/dir.c
index f8a11aa1ec56c2184b8c8d012d9f9b6d833cad56..fd689bbe6661cbf3bf30e8c3c674c61acb6fa82c 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -88,6 +88,18 @@ int fspathncmp(const char *a, const char *b, size_t count)
        return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);
 }
 
+int paths_collide(const char *a, const char *b)
+{
+       size_t len_a = strlen(a), len_b = strlen(b);
+
+       if (len_a == len_b)
+               return fspatheq(a, b);
+
+       if (len_a < len_b)
+               return is_dir_sep(b[len_a]) && !fspathncmp(a, b, len_a);
+       return is_dir_sep(a[len_b]) && !fspathncmp(a, b, len_b);
+}
+
 unsigned int fspathhash(const char *str)
 {
        return ignore_case ? strihash(str) : strhash(str);
diff --git a/dir.h b/dir.h
index 674747d93af7ad903b1e78a1418ff07c914a69ec..62e89a053d9dc516267c5c8cf1b24f49f35c57d5 100644 (file)
--- a/dir.h
+++ b/dir.h
@@ -519,6 +519,13 @@ int fspatheq(const char *a, const char *b);
 int fspathncmp(const char *a, const char *b, size_t count);
 unsigned int fspathhash(const char *str);
 
+/*
+ * Reports whether paths collide. This may be because the paths differ only in
+ * case on a case-sensitive filesystem, or that one path refers to a symlink
+ * that collides with one of the parent directories of the other.
+ */
+int paths_collide(const char *a, const char *b);
+
 /*
  * The prefix part of pattern must not contains wildcards.
  */
diff --git a/entry.c b/entry.c
index 616e4f073c1d6bd4016252423d4ced0958612432..a4c18c56455574b8fac46ef49fb968d4dedeac89 100644 (file)
--- a/entry.c
+++ b/entry.c
@@ -454,7 +454,7 @@ static void mark_colliding_entries(const struct checkout *state,
                        continue;
 
                if ((trust_ino && !match_stat_data(&dup->ce_stat_data, st)) ||
-                   (!trust_ino && !fspathcmp(ce->name, dup->name))) {
+                   paths_collide(ce->name, dup->name)) {
                        dup->ce_flags |= CE_MATCHED;
                        break;
                }