diff --git a/Documentation/config.txt b/Documentation/config.txt index 1932e9b9a2..28d57e3381 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -212,6 +212,12 @@ The default is true, except linkgit:git-clone[1] or linkgit:git-init[1] will probe and set core.fileMode false if appropriate when the repository is created. +core.hideDotFiles:: + (Windows-only) If true (which is the default), mark newly-created + directories and files whose name starts with a dot as hidden. + If 'dotGitOnly', only the .git/ directory is hidden, but no other + files starting with a dot. + core.ignorecase:: If true, this option enables various workarounds to enable Git to work better on filesystems that are not case sensitive, diff --git a/builtin/init-db.c b/builtin/init-db.c index 56f85e239a..ca433df331 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -385,6 +385,7 @@ int init_db(const char *template_dir, unsigned int flags) check_repository_format(); reinit = create_default_files(template_dir); + mark_as_git_dir(get_git_dir()); create_object_directory(); diff --git a/cache.h b/cache.h index 107ac61b68..96d231aee4 100644 --- a/cache.h +++ b/cache.h @@ -603,6 +603,13 @@ extern int precomposed_unicode; */ extern char comment_line_char; +enum hide_dotfiles_type { + HIDE_DOTFILES_FALSE = 0, + HIDE_DOTFILES_TRUE, + HIDE_DOTFILES_DOTGITONLY, +}; +extern enum hide_dotfiles_type hide_dotfiles; + enum branch_track { BRANCH_TRACK_UNSPECIFIED = -1, BRANCH_TRACK_NEVER = 0, diff --git a/compat/mingw.c b/compat/mingw.c index 757157980d..c584ef3c04 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -4,8 +4,10 @@ #include #include "../strbuf.h" #include "../run-command.h" +#include "../cache.h" static const int delay[] = { 0, 1, 10, 20, 40 }; +unsigned int _CRT_fmode = _O_BINARY; int err_win_to_posix(DWORD winerr) { @@ -283,6 +285,27 @@ int mingw_rmdir(const char *pathname) return ret; } +static int make_hidden(const wchar_t *path) +{ + DWORD attribs = GetFileAttributesW(path); + if (SetFileAttributesW(path, FILE_ATTRIBUTE_HIDDEN | attribs)) + return 0; + errno = err_win_to_posix(GetLastError()); + return -1; +} + +void mingw_mark_as_git_dir(const char *dir) +{ + wchar_t wdir[MAX_PATH]; + if (hide_dotfiles != HIDE_DOTFILES_FALSE && !is_bare_repository()) + if (xutftowcs_path(wdir, dir) < 0 || make_hidden(wdir)) + warning("Failed to make '%s' hidden", dir); + git_config_set("core.hideDotFiles", + hide_dotfiles == HIDE_DOTFILES_FALSE ? "false" : + (hide_dotfiles == HIDE_DOTFILES_DOTGITONLY ? + "dotGitOnly" : "true")); +} + int mingw_mkdir(const char *path, int mode) { int ret; @@ -290,6 +313,16 @@ int mingw_mkdir(const char *path, int mode) if (xutftowcs_path(wpath, path) < 0) return -1; ret = _wmkdir(wpath); + if (!ret && hide_dotfiles == HIDE_DOTFILES_TRUE) { + /* + * In Windows a file or dir starting with a dot is not + * automatically hidden. So lets mark it as hidden when + * such a directory is created. + */ + const char *start = basename((char*)path); + if (*start == '.') + return make_hidden(wpath); + } return ret; } @@ -316,6 +349,17 @@ int mingw_open (const char *filename, int oflags, ...) if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY)) errno = EISDIR; } + if ((oflags & O_CREAT) && fd >= 0 && + hide_dotfiles == HIDE_DOTFILES_TRUE) { + /* + * In Windows a file or dir starting with a dot is not + * automatically hidden. So lets mark it as hidden when + * such a file is created. + */ + const char *start = basename((char*)filename); + if (*start == '.' && make_hidden(wfilename)) + warning("Could not mark '%s' as hidden.", filename); + } return fd; } @@ -347,27 +391,39 @@ int mingw_fgetc(FILE *stream) #undef fopen FILE *mingw_fopen (const char *filename, const char *otype) { + int hide = 0; FILE *file; wchar_t wfilename[MAX_PATH], wotype[4]; + if (hide_dotfiles == HIDE_DOTFILES_TRUE && + basename((char*)filename)[0] == '.') + hide = access(filename, F_OK); if (filename && !strcmp(filename, "/dev/null")) filename = "nul"; if (xutftowcs_path(wfilename, filename) < 0 || xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0) return NULL; file = _wfopen(wfilename, wotype); + if (file && hide && make_hidden(wfilename)) + warning("Could not mark '%s' as hidden.", filename); return file; } FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream) { + int hide = 0; FILE *file; wchar_t wfilename[MAX_PATH], wotype[4]; + if (hide_dotfiles == HIDE_DOTFILES_TRUE && + basename((char*)filename)[0] == '.') + hide = access(filename, F_OK); if (filename && !strcmp(filename, "/dev/null")) filename = "nul"; if (xutftowcs_path(wfilename, filename) < 0 || xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0) return NULL; file = _wfreopen(wfilename, wotype, stream); + if (file && hide && make_hidden(wfilename)) + warning("Could not mark '%s' as hidden.", filename); return file; } @@ -1899,6 +1955,23 @@ pid_t waitpid(pid_t pid, int *status, int options) return -1; } +const char *get_windows_home_directory(void) +{ + static const char *home_directory = NULL; + struct strbuf buf = STRBUF_INIT; + + if (home_directory) + return home_directory; + + home_directory = getenv("HOME"); + if (home_directory && *home_directory) + return home_directory; + + strbuf_addf(&buf, "%s/%s", getenv("HOMEDRIVE"), getenv("HOMEPATH")); + home_directory = strbuf_detach(&buf, NULL); + + return home_directory; +} int xutftowcsn(wchar_t *wcs, const char *utfs, size_t wcslen, int utflen) { int upos = 0, wpos = 0; @@ -2088,3 +2161,27 @@ void mingw_startup() /* initialize Unicode console */ winansi_init(); } + +int mingw_offset_1st_component(const char *path) +{ + int offset = 0; + if (has_dos_drive_prefix(path)) + offset = 2; + + /* unc paths */ + else if (is_dir_sep(path[0]) && is_dir_sep(path[1])) { + + /* skip server name */ + char *pos = strpbrk(path + 2, "\\/"); + if (!pos) + return 0; /* Error: malformed unc path */ + + do { + pos++; + } while (*pos && !is_dir_sep(*pos)); + + offset = pos - path; + } + + return offset + is_dir_sep(path[offset]); +} diff --git a/compat/mingw.h b/compat/mingw.h index 969738ccff..db2bcf3121 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -503,3 +503,6 @@ static int mingw_main(c,v) * Used by Pthread API implementation for Windows */ extern int err_win_to_posix(DWORD winerr); + +extern const char *get_windows_home_directory(); +#define get_home_directory() get_windows_home_directory() diff --git a/config.c b/config.c index a30cb5c07d..e28f17c6bb 100644 --- a/config.c +++ b/config.c @@ -874,6 +874,15 @@ static int git_default_core_config(const char *var, const char *value) return 0; } + if (!strcmp(var, "core.hidedotfiles")) { + if (value && !strcasecmp(value, "dotgitonly")) { + hide_dotfiles = HIDE_DOTFILES_DOTGITONLY; + return 0; + } + hide_dotfiles = git_config_bool(var, value); + return 0; + } + /* Add other config variables here and to Documentation/config.txt. */ return 0; } diff --git a/environment.c b/environment.c index 5c4815dbe1..6366c85241 100644 --- a/environment.c +++ b/environment.c @@ -63,6 +63,7 @@ int merge_log_config = -1; int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */ struct startup_info *startup_info; unsigned long pack_size_limit_cfg; +enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY; /* * The character that begins a commented line in user-editable file diff --git a/git-compat-util.h b/git-compat-util.h index f6d3a46622..b7e145de62 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -723,4 +723,12 @@ struct tm *git_gmtime_r(const time_t *, struct tm *); #define gmtime_r git_gmtime_r #endif +#ifndef mark_as_git_dir +#define mark_as_git_dir(x) /* noop */ +#endif + +#ifndef get_home_directory +#define get_home_directory() getenv("HOME") +#endif + #endif diff --git a/path.c b/path.c index f9c5062427..a5bb08cc3b 100644 --- a/path.c +++ b/path.c @@ -133,7 +133,7 @@ char *git_path(const char *fmt, ...) void home_config_paths(char **global, char **xdg, char *file) { char *xdg_home = getenv("XDG_CONFIG_HOME"); - char *home = getenv("HOME"); + const char *home = get_home_directory(); char *to_free = NULL; if (!home) { @@ -274,7 +274,7 @@ char *expand_user_path(const char *path) const char *username = path + 1; size_t username_len = first_slash - username; if (username_len == 0) { - const char *home = getenv("HOME"); + const char *home = get_home_directory(); if (!home) goto return_null; strbuf_add(&user_path, home, strlen(home)); diff --git a/shell.c b/shell.c index 5c0d47a5cc..edd8c3a1c1 100644 --- a/shell.c +++ b/shell.c @@ -55,7 +55,7 @@ static char *make_cmd(const char *prog) static void cd_to_homedir(void) { - const char *home = getenv("HOME"); + const char *home = get_home_directory(); if (!home) die("could not determine user's home directory; HOME is unset"); if (chdir(home) == -1) diff --git a/t/t0001-init.sh b/t/t0001-init.sh index bbc9cb60dd..d356f2a327 100755 --- a/t/t0001-init.sh +++ b/t/t0001-init.sh @@ -332,4 +332,32 @@ test_expect_success SYMLINKS 're-init to move gitdir symlink' ' test_path_is_dir realgitdir/refs ' +# Tests for the hidden file attribute on windows +is_hidden () { + test "1" -eq "$(echo puts [file attributes $1 -hidden]|tclsh)" +} + +test_expect_success MINGW 'plain hidden' ' + rm -rf newdir && + ( + unset GIT_DIR GIT_WORK_TREE + mkdir newdir && + cd newdir && + git init && + is_hidden .git + ) && + check_config newdir/.git false unset +' + +test_expect_success MINGW 'plain bare not hidden' ' + rm -rf newdir + ( + unset GIT_DIR GIT_WORK_TREE GIT_CONFIG + mkdir newdir && + cd newdir && + git --bare init + ) && + ! is_hidden newdir +' + test_done