summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohannes Schindelin <johannes.schindelin@gmx.de>2024-03-28 09:44:28 +0100
committerJohannes Schindelin <johannes.schindelin@gmx.de>2024-04-17 22:30:07 +0200
commit850c3a220e7a0b1bf740fba9ac8f3f2b0486a1af (patch)
treeeaf8e3015cc4464442c0bbda112d106aacf669ff
parentt5510: verify that D/F confusion cannot lead to an RCE (diff)
downloadgit-850c3a220e7a0b1bf740fba9ac8f3f2b0486a1af.tar.xz
git-850c3a220e7a0b1bf740fba9ac8f3f2b0486a1af.zip
entry: report more colliding paths
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>
-rw-r--r--dir.c12
-rw-r--r--dir.h7
-rw-r--r--entry.c2
3 files changed, 20 insertions, 1 deletions
diff --git a/dir.c b/dir.c
index f8a11aa1ec..fd689bbe66 100644
--- 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 674747d93a..62e89a053d 100644
--- a/dir.h
+++ b/dir.h
@@ -520,6 +520,13 @@ 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.
*/
struct pathspec_item;
diff --git a/entry.c b/entry.c
index 616e4f073c..a4c18c5645 100644
--- 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;
}