win32: ensure hidden files can be staged
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
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
+}