Commit 6b7991e264b2fb0448e3dc47f972bafabf38c1fa

Russell Belfer 2013-09-30T16:13:53

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.

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;