Commit bdec336301c348404b0590025025420febae5b98

Edward Thomson 2015-08-03T17:48:33

win32: ensure hidden files can be staged

diff --git a/src/repository.c b/src/repository.c
index 08f4baa..0f37cfb 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -1279,7 +1279,7 @@ static int repo_write_template(
 
 #ifdef GIT_WIN32
 	if (!error && hidden) {
-		if (git_win32__sethidden(path.ptr) < 0)
+		if (git_win32__set_hidden(path.ptr, true) < 0)
 			error = -1;
 	}
 #else
@@ -1373,7 +1373,7 @@ static int repo_init_structure(
 	/* Hide the ".git" directory */
 #ifdef GIT_WIN32
 	if ((opts->flags & GIT_REPOSITORY_INIT__HAS_DOTGIT) != 0) {
-		if (git_win32__sethidden(repo_dir) < 0) {
+		if (git_win32__set_hidden(repo_dir, true) < 0) {
 			giterr_set(GITERR_OS,
 				"Failed to mark Git repository folder as hidden");
 			return -1;
diff --git a/src/win32/w32_util.c b/src/win32/w32_util.c
index 2e52525..60311bb 100644
--- a/src/win32/w32_util.c
+++ b/src/win32/w32_util.c
@@ -48,10 +48,10 @@ bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src)
  * @param path The path which should receive the +H bit.
  * @return 0 on success; -1 on failure
  */
-int git_win32__sethidden(const char *path)
+int git_win32__set_hidden(const char *path, bool hidden)
 {
 	git_win32_path buf;
-	DWORD attrs;
+	DWORD attrs, newattrs;
 
 	if (git_win32_path_from_utf8(buf, path) < 0)
 		return -1;
@@ -62,11 +62,35 @@ int git_win32__sethidden(const char *path)
 	if (attrs == INVALID_FILE_ATTRIBUTES)
 		return -1;
 
-	/* If the item isn't already +H, add the bit */
-	if ((attrs & FILE_ATTRIBUTE_HIDDEN) == 0 &&
-		!SetFileAttributesW(buf, attrs | FILE_ATTRIBUTE_HIDDEN))
+	if (hidden)
+		newattrs = attrs | FILE_ATTRIBUTE_HIDDEN;
+	else
+		newattrs = attrs & ~FILE_ATTRIBUTE_HIDDEN;
+
+	if (attrs != newattrs && !SetFileAttributesW(buf, newattrs)) {
+		giterr_set(GITERR_OS, "Failed to %s hidden bit for '%s'",
+			hidden ? "set" : "unset", path);
+		return -1;
+	}
+
+	return 0;
+}
+
+int git_win32__hidden(bool *out, const char *path)
+{
+	git_win32_path buf;
+	DWORD attrs;
+
+	if (git_win32_path_from_utf8(buf, path) < 0)
+		return -1;
+
+	attrs = GetFileAttributesW(buf);
+
+	/* Ensure the path exists */
+	if (attrs == INVALID_FILE_ATTRIBUTES)
 		return -1;
 
+	*out = (attrs & FILE_ATTRIBUTE_HIDDEN) ? true : false;
 	return 0;
 }
 
diff --git a/src/win32/w32_util.h b/src/win32/w32_util.h
index 377d651..8db3afb 100644
--- a/src/win32/w32_util.h
+++ b/src/win32/w32_util.h
@@ -40,12 +40,22 @@ GIT_INLINE(bool) git_win32__isalpha(wchar_t c)
 bool git_win32__findfirstfile_filter(git_win32_path dest, const char *src);
 
 /**
- * Ensures the given path (file or folder) has the +H (hidden) attribute set.
+ * Ensures the given path (file or folder) has the +H (hidden) attribute set
+ * or unset.
  *
- * @param path The path which should receive the +H bit.
+ * @param path The path that should receive the +H bit.
+ * @param hidden true to set +H, false to unset it
  * @return 0 on success; -1 on failure
  */
-int git_win32__sethidden(const char *path);
+extern int git_win32__set_hidden(const char *path, bool hidden);
+
+/**
+ * Determines if the given file or folder has the hidden attribute set.
+ * @param hidden pointer to store hidden value
+ * @param path The path that should be queried for hiddenness.
+ * @return 0 on success or an error code.
+ */
+extern int git_win32__hidden(bool *hidden, const char *path);
 
 /**
  * Removes any trailing backslashes from a path, except in the case of a drive
diff --git a/tests/index/addall.c b/tests/index/addall.c
index 9ddb27f..7b7a178 100644
--- a/tests/index/addall.c
+++ b/tests/index/addall.c
@@ -307,6 +307,41 @@ void test_index_addall__files_in_folders(void)
 	git_index_free(index);
 }
 
+void test_index_addall__hidden_files(void)
+{
+	git_index *index;
+
+	GIT_UNUSED(index);
+
+#ifdef GIT_WIN32
+	addall_create_test_repo(true);
+
+	cl_git_pass(git_repository_index(&index, g_repo));
+
+	cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL));
+	check_stat_data(index, TEST_DIR "/file.bar", true);
+	check_status(g_repo, 2, 0, 0, 0, 0, 0, 1, 0);
+
+	cl_git_mkfile(TEST_DIR "/file.zzz", "yet another one");
+	cl_git_mkfile(TEST_DIR "/more.zzz", "yet another one");
+	cl_git_mkfile(TEST_DIR "/other.zzz", "yet another one");
+
+	check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0);
+
+	cl_git_pass(git_win32__set_hidden(TEST_DIR "/file.zzz", true));
+	cl_git_pass(git_win32__set_hidden(TEST_DIR "/more.zzz", true));
+	cl_git_pass(git_win32__set_hidden(TEST_DIR "/other.zzz", true));
+
+	check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0);
+
+	cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL));
+	check_stat_data(index, TEST_DIR "/file.bar", true);
+	check_status(g_repo, 5, 0, 0, 0, 0, 0, 1, 0);
+
+	git_index_free(index);
+#endif
+}
+
 static int addall_match_prefix(
 	const char *path, const char *matched_pathspec, void *payload)
 {
diff --git a/tests/index/bypath.c b/tests/index/bypath.c
index 9706a88..b607e17 100644
--- a/tests/index/bypath.c
+++ b/tests/index/bypath.c
@@ -46,3 +46,29 @@ void test_index_bypath__add_submodule_unregistered(void)
 	cl_assert_equal_s(sm_head, git_oid_tostr_s(&entry->id));
 	cl_assert_equal_s(sm_name, entry->path);
 }
+
+void test_index_bypath__add_hidden(void)
+{
+	const git_index_entry *entry;
+	bool hidden;
+
+	GIT_UNUSED(entry);
+	GIT_UNUSED(hidden);
+
+#ifdef GIT_WIN32
+	cl_git_mkfile("submod2/hidden_file", "you can't see me");
+
+	cl_git_pass(git_win32__hidden(&hidden, "submod2/hidden_file"));
+	cl_assert(!hidden);
+
+	cl_git_pass(git_win32__set_hidden("submod2/hidden_file", true));
+
+	cl_git_pass(git_win32__hidden(&hidden, "submod2/hidden_file"));
+	cl_assert(hidden);
+
+	cl_git_pass(git_index_add_bypath(g_idx, "hidden_file"));
+
+	cl_assert(entry = git_index_get_bypath(g_idx, "hidden_file", 0));
+	cl_assert_equal_i(GIT_FILEMODE_BLOB, entry->mode);
+#endif
+}