Add check if we need to precompose unicode on Mac This adds initialization of core.precomposeunicode to repo init on Mac. This is necessary because when a Mac accesses a repo on a VFAT or SAMBA file system, it will return directory entries in decomposed unicode even if the filesystem entry is precomposed. This also removes caching of a number of repo properties from the repo init pipeline because these are properties of the specific filesystem on which the repo is created, not of the system as a whole.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
diff --git a/src/repository.c b/src/repository.c
index 0d7c094..64f1397 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -843,10 +843,6 @@ fail:
static bool is_chmod_supported(const char *file_path)
{
struct stat st1, st2;
- static int _is_supported = -1;
-
- if (_is_supported > -1)
- return _is_supported;
if (p_stat(file_path, &st1) < 0)
return false;
@@ -857,27 +853,19 @@ static bool is_chmod_supported(const char *file_path)
if (p_stat(file_path, &st2) < 0)
return false;
- _is_supported = (st1.st_mode != st2.st_mode);
-
- return _is_supported;
+ return (st1.st_mode != st2.st_mode);
}
static bool is_filesystem_case_insensitive(const char *gitdir_path)
{
git_buf path = GIT_BUF_INIT;
- static int _is_insensitive = -1;
-
- if (_is_insensitive > -1)
- return _is_insensitive;
-
- if (git_buf_joinpath(&path, gitdir_path, "CoNfIg") < 0)
- goto cleanup;
+ int is_insensitive = -1;
- _is_insensitive = git_path_exists(git_buf_cstr(&path));
+ if (!git_buf_joinpath(&path, gitdir_path, "CoNfIg"))
+ is_insensitive = git_path_exists(git_buf_cstr(&path));
-cleanup:
git_buf_free(&path);
- return _is_insensitive;
+ return is_insensitive;
}
static bool are_symlinks_supported(const char *wd_path)
@@ -885,24 +873,69 @@ static bool are_symlinks_supported(const char *wd_path)
git_buf path = GIT_BUF_INIT;
int fd;
struct stat st;
- static int _symlinks_supported = -1;
-
- if (_symlinks_supported > -1)
- return _symlinks_supported;
+ int symlinks_supported = -1;
if ((fd = git_futils_mktmp(&path, wd_path)) < 0 ||
p_close(fd) < 0 ||
p_unlink(path.ptr) < 0 ||
p_symlink("testing", path.ptr) < 0 ||
p_lstat(path.ptr, &st) < 0)
- _symlinks_supported = false;
+ symlinks_supported = false;
else
- _symlinks_supported = (S_ISLNK(st.st_mode) != 0);
+ symlinks_supported = (S_ISLNK(st.st_mode) != 0);
(void)p_unlink(path.ptr);
git_buf_free(&path);
- return _symlinks_supported;
+ return symlinks_supported;
+}
+
+static const char *nfc_file = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D.XXXXXX";
+static const char *nfd_file = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D.XXXXXX";
+
+/* On Mac, HDFS always stores files using decomposed unicode, but when
+ * writing to VFAT or SAMBA file systems, filenames may be kept as
+ * precomposed unicode, but will be converted to decomposed form when
+ * reading the directory entries. This can cause file name mismatches.
+ * The solution is to convert directory entries to precomposed form if we
+ * cannot look up the file from the decomposed path.
+ */
+static bool should_precompose_unicode_paths(const char *wd_path)
+{
+ git_buf path = GIT_BUF_INIT;
+ int fd;
+ bool need_precompose = false;
+ char tmp[6];
+
+ /* Create a file using a precomposed path and then try to find it
+ * using the decomposed name. If the lookup fails, then we will mark
+ * that we should precompose unicode for this repository.
+ */
+ if (git_buf_joinpath(&path, wd_path, nfc_file) < 0 ||
+ (fd = p_mkstemp(path.ptr)) < 0)
+ goto fail;
+ p_close(fd);
+
+ /* record trailing digits generated by mkstemp */
+ memcpy(tmp, path.ptr + path.size - sizeof(tmp), sizeof(tmp));
+
+ /* try to look up as NFD path */
+ if (git_buf_joinpath(&path, wd_path, nfd_file) < 0)
+ goto fail;
+ memcpy(path.ptr + path.size - sizeof(tmp), tmp, sizeof(tmp));
+
+ need_precompose = !git_path_exists(path.ptr);
+
+ /* remove temporary file */
+ if (git_buf_joinpath(&path, wd_path, nfc_file) < 0)
+ goto fail;
+ memcpy(path.ptr + path.size - sizeof(tmp), tmp, sizeof(tmp));
+
+ (void)p_unlink(path.ptr);
+
+fail:
+ git_buf_free(&path);
+ return need_precompose;
}
static int create_empty_file(const char *path, mode_t mode)
@@ -930,6 +963,7 @@ static int repo_init_config(
int error = 0;
git_buf cfg_path = GIT_BUF_INIT;
git_config *config = NULL;
+ bool is_bare = ((opts->flags & GIT_REPOSITORY_INIT_BARE) != 0);
#define SET_REPO_CONFIG(TYPE, NAME, VAL) do {\
if ((error = git_config_set_##TYPE(config, NAME, VAL)) < 0) \
@@ -954,17 +988,23 @@ static int repo_init_config(
goto cleanup;
SET_REPO_CONFIG(
- bool, "core.bare", (opts->flags & GIT_REPOSITORY_INIT_BARE) != 0);
+ bool, "core.bare", is_bare);
SET_REPO_CONFIG(
int32, "core.repositoryformatversion", GIT_REPO_VERSION);
SET_REPO_CONFIG(
bool, "core.filemode", is_chmod_supported(git_buf_cstr(&cfg_path)));
- if (!(opts->flags & GIT_REPOSITORY_INIT_BARE)) {
- SET_REPO_CONFIG(bool, "core.logallrefupdates", true);
+#if __APPLE__
+ SET_REPO_CONFIG(
+ bool, "core.precomposeunicode",
+ should_precompose_unicode_paths(is_bare ? repo_dir : work_dir));
+#endif
- if (!are_symlinks_supported(work_dir))
- SET_REPO_CONFIG(bool, "core.symlinks", false);
+ if (!are_symlinks_supported(is_bare ? repo_dir : work_dir))
+ SET_REPO_CONFIG(bool, "core.symlinks", false);
+
+ if (!is_bare) {
+ SET_REPO_CONFIG(bool, "core.logallrefupdates", true);
if (!(opts->flags & GIT_REPOSITORY_INIT__NATURAL_WD)) {
SET_REPO_CONFIG(string, "core.worktree", work_dir);
@@ -973,9 +1013,6 @@ static int repo_init_config(
if (git_config_delete_entry(config, "core.worktree") < 0)
giterr_clear();
}
- } else {
- if (!are_symlinks_supported(repo_dir))
- SET_REPO_CONFIG(bool, "core.symlinks", false);
}
if (!(opts->flags & GIT_REPOSITORY_INIT__IS_REINIT) &&
diff --git a/tests-clar/repo/init.c b/tests-clar/repo/init.c
index 392be20..6f3c841 100644
--- a/tests-clar/repo/init.c
+++ b/tests-clar/repo/init.c
@@ -241,6 +241,16 @@ void test_repo_init__detect_ignorecase(void)
#endif
}
+void test_repo_init__detect_precompose_unicode_required(void)
+{
+#ifdef __APPLE__
+ /* hard to test "true" case without SAMBA or VFAT file system available */
+ assert_config_entry_on_init("core.precomposeunicode", false);
+#else
+ assert_config_entry_on_init("core.precomposeunicode", GIT_ENOTFOUND);
+#endif
+}
+
void test_repo_init__reinit_doesnot_overwrite_ignorecase(void)
{
git_config *config;