win32: override `fspathcmp()` with a directory separator-aware version

On Windows, the backslash is the directory separator, even if the forward slash can be used, too, at least since Windows NT. This means that the paths `a/b` and `a\b` are equivalent, and `fspathcmp()` needs to be made aware of that fact. Note that we have to override both `fspathcmp()` and `fspathncmp()`, and the former cannot be a mere pre-processor constant that transforms calls to `fspathcmp(a, b)` into `fspathncmp(a, b, (size_t)-1)` because the function `report_collided_checkout()` in `unpack-trees.c` wants to assign `list.cmp = fspathcmp`. Also note that `fspatheq()` does _not_ need to be overridden because it calls `fspathcmp()` internally. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Johannes Schindelin committed Jul 13, 2024 at 21:08 UTC 193eda7507d0ccb2fe6fd42b403c56ecc205e546
5 files changed +53 -4
compat/win32/path-utils.c
+37
@@ -1,4 +1,5 @@
1 #include "../../git-compat-util.h"
2 +#include "../../environment.h"
3
4 int win32_has_dos_drive_prefix(const char *path)
5 {
@@ -50,3 +51,39 @@ int win32_offset_1st_component(const char *path)
51
52 return pos + is_dir_sep(*pos) - path;
53 }
54 +
55 +int win32_fspathncmp(const char *a, const char *b, size_t count)
56 +{
57 + int diff;
58 +
59 + for (;;) {
60 + if (!count--)
61 + return 0;
62 + if (!*a)
63 + return *b ? -1 : 0;
64 + if (!*b)
65 + return +1;
66 +
67 + if (is_dir_sep(*a)) {
68 + if (!is_dir_sep(*b))
69 + return -1;
70 + a++;
71 + b++;
72 + continue;
73 + } else if (is_dir_sep(*b))
74 + return +1;
75 +
76 + diff = ignore_case ?
77 + (unsigned char)tolower(*a) - (int)(unsigned char)tolower(*b) :
78 + (unsigned char)*a - (int)(unsigned char)*b;
79 + if (diff)
80 + return diff;
81 + a++;
82 + b++;
83 + }
84 +}
85 +
86 +int win32_fspathcmp(const char *a, const char *b)
87 +{
88 + return win32_fspathncmp(a, b, (size_t)-1);
89 +}
compat/win32/path-utils.h
+4
@@ -29,5 +29,9 @@ static inline int win32_has_dir_sep(const char *path)
29 #define has_dir_sep(path) win32_has_dir_sep(path)
30 int win32_offset_1st_component(const char *path);
31 #define offset_1st_component win32_offset_1st_component
32 +int win32_fspathcmp(const char *a, const char *b);
33 +#define fspathcmp win32_fspathcmp
34 +int win32_fspathncmp(const char *a, const char *b, size_t count);
35 +#define fspathncmp win32_fspathncmp
36
37 #endif
dir.c
+2 -2
@@ -95,7 +95,7 @@ int count_slashes(const char *s)
95 return cnt;
96 }
97
98 -int fspathcmp(const char *a, const char *b)
98 +int git_fspathcmp(const char *a, const char *b)
99 {
100 return ignore_case ? strcasecmp(a, b) : strcmp(a, b);
101 }
@@ -105,7 +105,7 @@ int fspatheq(const char *a, const char *b)
105 return !fspathcmp(a, b);
106 }
107
108 -int fspathncmp(const char *a, const char *b, size_t count)
108 +int git_fspathncmp(const char *a, const char *b, size_t count)
109 {
110 return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);
111 }
dir.h
+2 -2
@@ -541,9 +541,9 @@ int remove_dir_recursively(struct strbuf *path, int flag);
541 */
542 int remove_path(const char *path);
543
544 -int fspathcmp(const char *a, const char *b);
544 +int git_fspathcmp(const char *a, const char *b);
545 int fspatheq(const char *a, const char *b);
546 -int fspathncmp(const char *a, const char *b, size_t count);
546 +int git_fspathncmp(const char *a, const char *b, size_t count);
547 unsigned int fspathhash(const char *str);
548
549 /*
git-compat-util.h
+8
@@ -506,6 +506,14 @@ static inline int git_offset_1st_component(const char *path)
506 #define offset_1st_component git_offset_1st_component
507 #endif
508
509 +#ifndef fspathcmp
510 +#define fspathcmp git_fspathcmp
511 +#endif
512 +
513 +#ifndef fspathncmp
514 +#define fspathncmp git_fspathncmp
515 +#endif
516 +
517 #ifndef is_valid_path
518 #define is_valid_path(path) 1
519 #endif