Merge branch 'js/windows-dotgit'

On Windows, .git and optionally any files whose name starts with a
dot are now marked as hidden, with a core.hideDotFiles knob to
customize this behaviour.

* js/windows-dotgit:
  mingw: remove unnecessary definition
  mingw: introduce the 'core.hideDotFiles' setting
This commit is contained in:
Junio C Hamano 2016-05-17 14:38:39 -07:00
commit bfc99b63fe
8 changed files with 147 additions and 3 deletions

View file

@ -279,6 +279,12 @@ See linkgit:git-update-index[1].
+ +
The default is true (when core.filemode is not specified in the config file). The default is true (when core.filemode is not specified in the config file).
core.hideDotFiles::
(Windows-only) If true, 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. The
default mode is 'dotGitOnly'.
core.ignoreCase:: core.ignoreCase::
If true, this option enables various workarounds to enable If true, this option enables various workarounds to enable
Git to work better on filesystems that are not case sensitive, Git to work better on filesystems that are not case sensitive,

View file

@ -701,6 +701,14 @@ extern int ref_paranoia;
extern char comment_line_char; extern char comment_line_char;
extern int auto_comment_line_char; extern int auto_comment_line_char;
/* Windows only */
enum hide_dotfiles_type {
HIDE_DOTFILES_FALSE = 0,
HIDE_DOTFILES_TRUE,
HIDE_DOTFILES_DOTGITONLY
};
extern enum hide_dotfiles_type hide_dotfiles;
enum branch_track { enum branch_track {
BRANCH_TRACK_UNSPECIFIED = -1, BRANCH_TRACK_UNSPECIFIED = -1,
BRANCH_TRACK_NEVER = 0, BRANCH_TRACK_NEVER = 0,

View file

@ -286,6 +286,49 @@ int mingw_rmdir(const char *pathname)
return ret; return ret;
} }
static inline int needs_hiding(const char *path)
{
const char *basename;
if (hide_dotfiles == HIDE_DOTFILES_FALSE)
return 0;
/* We cannot use basename(), as it would remove trailing slashes */
mingw_skip_dos_drive_prefix((char **)&path);
if (!*path)
return 0;
for (basename = path; *path; path++)
if (is_dir_sep(*path)) {
do {
path++;
} while (is_dir_sep(*path));
/* ignore trailing slashes */
if (*path)
basename = path;
}
if (hide_dotfiles == HIDE_DOTFILES_TRUE)
return *basename == '.';
assert(hide_dotfiles == HIDE_DOTFILES_DOTGITONLY);
return !strncasecmp(".git", basename, 4) &&
(!basename[4] || is_dir_sep(basename[4]));
}
static int set_hidden_flag(const wchar_t *path, int set)
{
DWORD original = GetFileAttributesW(path), modified;
if (set)
modified = original | FILE_ATTRIBUTE_HIDDEN;
else
modified = original & ~FILE_ATTRIBUTE_HIDDEN;
if (original == modified || SetFileAttributesW(path, modified))
return 0;
errno = err_win_to_posix(GetLastError());
return -1;
}
int mingw_mkdir(const char *path, int mode) int mingw_mkdir(const char *path, int mode)
{ {
int ret; int ret;
@ -293,6 +336,8 @@ int mingw_mkdir(const char *path, int mode)
if (xutftowcs_path(wpath, path) < 0) if (xutftowcs_path(wpath, path) < 0)
return -1; return -1;
ret = _wmkdir(wpath); ret = _wmkdir(wpath);
if (!ret && needs_hiding(path))
return set_hidden_flag(wpath, 1);
return ret; return ret;
} }
@ -319,6 +364,21 @@ int mingw_open (const char *filename, int oflags, ...)
if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY)) if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY))
errno = EISDIR; errno = EISDIR;
} }
if ((oflags & O_CREAT) && needs_hiding(filename)) {
/*
* Internally, _wopen() uses the CreateFile() API which errors
* out with an ERROR_ACCESS_DENIED if CREATE_ALWAYS was
* specified and an already existing file's attributes do not
* match *exactly*. As there is no mode or flag we can set that
* would correspond to FILE_ATTRIBUTE_HIDDEN, let's just try
* again *without* the O_CREAT flag (that corresponds to the
* CREATE_ALWAYS flag of CreateFile()).
*/
if (fd < 0 && errno == EACCES)
fd = _wopen(wfilename, oflags & ~O_CREAT, mode);
if (fd >= 0 && set_hidden_flag(wfilename, 1))
warning("could not mark '%s' as hidden.", filename);
}
return fd; return fd;
} }
@ -350,6 +410,7 @@ int mingw_fgetc(FILE *stream)
#undef fopen #undef fopen
FILE *mingw_fopen (const char *filename, const char *otype) FILE *mingw_fopen (const char *filename, const char *otype)
{ {
int hide = needs_hiding(filename);
FILE *file; FILE *file;
wchar_t wfilename[MAX_PATH], wotype[4]; wchar_t wfilename[MAX_PATH], wotype[4];
if (filename && !strcmp(filename, "/dev/null")) if (filename && !strcmp(filename, "/dev/null"))
@ -357,12 +418,19 @@ FILE *mingw_fopen (const char *filename, const char *otype)
if (xutftowcs_path(wfilename, filename) < 0 || if (xutftowcs_path(wfilename, filename) < 0 ||
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0) xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
return NULL; return NULL;
if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
error("could not unhide %s", filename);
return NULL;
}
file = _wfopen(wfilename, wotype); file = _wfopen(wfilename, wotype);
if (file && hide && set_hidden_flag(wfilename, 1))
warning("could not mark '%s' as hidden.", filename);
return file; return file;
} }
FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream) FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
{ {
int hide = needs_hiding(filename);
FILE *file; FILE *file;
wchar_t wfilename[MAX_PATH], wotype[4]; wchar_t wfilename[MAX_PATH], wotype[4];
if (filename && !strcmp(filename, "/dev/null")) if (filename && !strcmp(filename, "/dev/null"))
@ -370,7 +438,13 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
if (xutftowcs_path(wfilename, filename) < 0 || if (xutftowcs_path(wfilename, filename) < 0 ||
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0) xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
return NULL; return NULL;
if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
error("could not unhide %s", filename);
return NULL;
}
file = _wfreopen(wfilename, wotype, stream); file = _wfreopen(wfilename, wotype, stream);
if (file && hide && set_hidden_flag(wfilename, 1))
warning("could not mark '%s' as hidden.", filename);
return file; return file;
} }

View file

@ -416,9 +416,6 @@ int mingw_offset_1st_component(const char *path);
void mingw_open_html(const char *path); void mingw_open_html(const char *path);
#define open_html mingw_open_html #define open_html mingw_open_html
void mingw_mark_as_git_dir(const char *dir);
#define mark_as_git_dir mingw_mark_as_git_dir
/** /**
* Converts UTF-8 encoded string to UTF-16LE. * Converts UTF-8 encoded string to UTF-16LE.
* *

View file

@ -915,6 +915,14 @@ static int git_default_core_config(const char *var, const char *value)
return 0; return 0;
} }
if (!strcmp(var, "core.hidedotfiles")) {
if (value && !strcasecmp(value, "dotgitonly"))
hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
else
hide_dotfiles = git_config_bool(var, value);
return 0;
}
/* Add other config variables here and to Documentation/config.txt. */ /* Add other config variables here and to Documentation/config.txt. */
return 0; return 0;
} }

View file

@ -64,6 +64,7 @@ int core_apply_sparse_checkout;
int merge_log_config = -1; int merge_log_config = -1;
int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */ int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
unsigned long pack_size_limit_cfg; unsigned long pack_size_limit_cfg;
enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
#ifndef PROTECT_HFS_DEFAULT #ifndef PROTECT_HFS_DEFAULT
#define PROTECT_HFS_DEFAULT 0 #define PROTECT_HFS_DEFAULT 0

View file

@ -354,4 +354,34 @@ test_expect_success SYMLINKS 're-init to move gitdir symlink' '
test_path_is_dir realgitdir/refs test_path_is_dir realgitdir/refs
' '
# Tests for the hidden file attribute on windows
is_hidden () {
# Use the output of `attrib`, ignore the absolute path
case "$(attrib "$1")" in *H*?:*) return 0;; esac
return 1
}
test_expect_success MINGW '.git 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 'bare git dir 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 test_done

View file

@ -37,4 +37,24 @@ test_expect_success 'clone -c config is available during clone' '
test_cmp expect child/file test_cmp expect child/file
' '
# Tests for the hidden file attribute on windows
is_hidden () {
# Use the output of `attrib`, ignore the absolute path
case "$(attrib "$1")" in *H*?:*) return 0;; esac
return 1
}
test_expect_success MINGW 'clone -c core.hideDotFiles' '
test_commit attributes .gitattributes "" &&
rm -rf child &&
git clone -c core.hideDotFiles=false . child &&
! is_hidden child/.gitattributes &&
rm -rf child &&
git clone -c core.hideDotFiles=dotGitOnly . child &&
! is_hidden child/.gitattributes &&
rm -rf child &&
git clone -c core.hideDotFiles=true . child &&
is_hidden child/.gitattributes
'
test_done test_done