Skip to content

Commit bfc99b6

Browse files
committed
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
2 parents 5bfc50d + ebf31e7 commit bfc99b6

File tree

8 files changed

+147
-3
lines changed

8 files changed

+147
-3
lines changed

Documentation/config.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,12 @@ See linkgit:git-update-index[1].
279279
+
280280
The default is true (when core.filemode is not specified in the config file).
281281

282+
core.hideDotFiles::
283+
(Windows-only) If true, mark newly-created directories and files whose
284+
name starts with a dot as hidden. If 'dotGitOnly', only the `.git/`
285+
directory is hidden, but no other files starting with a dot. The
286+
default mode is 'dotGitOnly'.
287+
282288
core.ignoreCase::
283289
If true, this option enables various workarounds to enable
284290
Git to work better on filesystems that are not case sensitive,

cache.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,14 @@ extern int ref_paranoia;
701701
extern char comment_line_char;
702702
extern int auto_comment_line_char;
703703

704+
/* Windows only */
705+
enum hide_dotfiles_type {
706+
HIDE_DOTFILES_FALSE = 0,
707+
HIDE_DOTFILES_TRUE,
708+
HIDE_DOTFILES_DOTGITONLY
709+
};
710+
extern enum hide_dotfiles_type hide_dotfiles;
711+
704712
enum branch_track {
705713
BRANCH_TRACK_UNSPECIFIED = -1,
706714
BRANCH_TRACK_NEVER = 0,

compat/mingw.c

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,13 +286,58 @@ int mingw_rmdir(const char *pathname)
286286
return ret;
287287
}
288288

289+
static inline int needs_hiding(const char *path)
290+
{
291+
const char *basename;
292+
293+
if (hide_dotfiles == HIDE_DOTFILES_FALSE)
294+
return 0;
295+
296+
/* We cannot use basename(), as it would remove trailing slashes */
297+
mingw_skip_dos_drive_prefix((char **)&path);
298+
if (!*path)
299+
return 0;
300+
301+
for (basename = path; *path; path++)
302+
if (is_dir_sep(*path)) {
303+
do {
304+
path++;
305+
} while (is_dir_sep(*path));
306+
/* ignore trailing slashes */
307+
if (*path)
308+
basename = path;
309+
}
310+
311+
if (hide_dotfiles == HIDE_DOTFILES_TRUE)
312+
return *basename == '.';
313+
314+
assert(hide_dotfiles == HIDE_DOTFILES_DOTGITONLY);
315+
return !strncasecmp(".git", basename, 4) &&
316+
(!basename[4] || is_dir_sep(basename[4]));
317+
}
318+
319+
static int set_hidden_flag(const wchar_t *path, int set)
320+
{
321+
DWORD original = GetFileAttributesW(path), modified;
322+
if (set)
323+
modified = original | FILE_ATTRIBUTE_HIDDEN;
324+
else
325+
modified = original & ~FILE_ATTRIBUTE_HIDDEN;
326+
if (original == modified || SetFileAttributesW(path, modified))
327+
return 0;
328+
errno = err_win_to_posix(GetLastError());
329+
return -1;
330+
}
331+
289332
int mingw_mkdir(const char *path, int mode)
290333
{
291334
int ret;
292335
wchar_t wpath[MAX_PATH];
293336
if (xutftowcs_path(wpath, path) < 0)
294337
return -1;
295338
ret = _wmkdir(wpath);
339+
if (!ret && needs_hiding(path))
340+
return set_hidden_flag(wpath, 1);
296341
return ret;
297342
}
298343

@@ -319,6 +364,21 @@ int mingw_open (const char *filename, int oflags, ...)
319364
if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY))
320365
errno = EISDIR;
321366
}
367+
if ((oflags & O_CREAT) && needs_hiding(filename)) {
368+
/*
369+
* Internally, _wopen() uses the CreateFile() API which errors
370+
* out with an ERROR_ACCESS_DENIED if CREATE_ALWAYS was
371+
* specified and an already existing file's attributes do not
372+
* match *exactly*. As there is no mode or flag we can set that
373+
* would correspond to FILE_ATTRIBUTE_HIDDEN, let's just try
374+
* again *without* the O_CREAT flag (that corresponds to the
375+
* CREATE_ALWAYS flag of CreateFile()).
376+
*/
377+
if (fd < 0 && errno == EACCES)
378+
fd = _wopen(wfilename, oflags & ~O_CREAT, mode);
379+
if (fd >= 0 && set_hidden_flag(wfilename, 1))
380+
warning("could not mark '%s' as hidden.", filename);
381+
}
322382
return fd;
323383
}
324384

@@ -350,27 +410,41 @@ int mingw_fgetc(FILE *stream)
350410
#undef fopen
351411
FILE *mingw_fopen (const char *filename, const char *otype)
352412
{
413+
int hide = needs_hiding(filename);
353414
FILE *file;
354415
wchar_t wfilename[MAX_PATH], wotype[4];
355416
if (filename && !strcmp(filename, "/dev/null"))
356417
filename = "nul";
357418
if (xutftowcs_path(wfilename, filename) < 0 ||
358419
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
359420
return NULL;
421+
if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
422+
error("could not unhide %s", filename);
423+
return NULL;
424+
}
360425
file = _wfopen(wfilename, wotype);
426+
if (file && hide && set_hidden_flag(wfilename, 1))
427+
warning("could not mark '%s' as hidden.", filename);
361428
return file;
362429
}
363430

364431
FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
365432
{
433+
int hide = needs_hiding(filename);
366434
FILE *file;
367435
wchar_t wfilename[MAX_PATH], wotype[4];
368436
if (filename && !strcmp(filename, "/dev/null"))
369437
filename = "nul";
370438
if (xutftowcs_path(wfilename, filename) < 0 ||
371439
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
372440
return NULL;
441+
if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
442+
error("could not unhide %s", filename);
443+
return NULL;
444+
}
373445
file = _wfreopen(wfilename, wotype, stream);
446+
if (file && hide && set_hidden_flag(wfilename, 1))
447+
warning("could not mark '%s' as hidden.", filename);
374448
return file;
375449
}
376450

compat/mingw.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -416,9 +416,6 @@ int mingw_offset_1st_component(const char *path);
416416
void mingw_open_html(const char *path);
417417
#define open_html mingw_open_html
418418

419-
void mingw_mark_as_git_dir(const char *dir);
420-
#define mark_as_git_dir mingw_mark_as_git_dir
421-
422419
/**
423420
* Converts UTF-8 encoded string to UTF-16LE.
424421
*

config.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -915,6 +915,14 @@ static int git_default_core_config(const char *var, const char *value)
915915
return 0;
916916
}
917917

918+
if (!strcmp(var, "core.hidedotfiles")) {
919+
if (value && !strcasecmp(value, "dotgitonly"))
920+
hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
921+
else
922+
hide_dotfiles = git_config_bool(var, value);
923+
return 0;
924+
}
925+
918926
/* Add other config variables here and to Documentation/config.txt. */
919927
return 0;
920928
}

environment.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ int core_apply_sparse_checkout;
6464
int merge_log_config = -1;
6565
int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
6666
unsigned long pack_size_limit_cfg;
67+
enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
6768

6869
#ifndef PROTECT_HFS_DEFAULT
6970
#define PROTECT_HFS_DEFAULT 0

t/t0001-init.sh

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,4 +354,34 @@ test_expect_success SYMLINKS 're-init to move gitdir symlink' '
354354
test_path_is_dir realgitdir/refs
355355
'
356356

357+
# Tests for the hidden file attribute on windows
358+
is_hidden () {
359+
# Use the output of `attrib`, ignore the absolute path
360+
case "$(attrib "$1")" in *H*?:*) return 0;; esac
361+
return 1
362+
}
363+
364+
test_expect_success MINGW '.git hidden' '
365+
rm -rf newdir &&
366+
(
367+
unset GIT_DIR GIT_WORK_TREE
368+
mkdir newdir &&
369+
cd newdir &&
370+
git init &&
371+
is_hidden .git
372+
) &&
373+
check_config newdir/.git false unset
374+
'
375+
376+
test_expect_success MINGW 'bare git dir not hidden' '
377+
rm -rf newdir &&
378+
(
379+
unset GIT_DIR GIT_WORK_TREE GIT_CONFIG
380+
mkdir newdir &&
381+
cd newdir &&
382+
git --bare init
383+
) &&
384+
! is_hidden newdir
385+
'
386+
357387
test_done

t/t5611-clone-config.sh

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,24 @@ test_expect_success 'clone -c config is available during clone' '
3737
test_cmp expect child/file
3838
'
3939

40+
# Tests for the hidden file attribute on windows
41+
is_hidden () {
42+
# Use the output of `attrib`, ignore the absolute path
43+
case "$(attrib "$1")" in *H*?:*) return 0;; esac
44+
return 1
45+
}
46+
47+
test_expect_success MINGW 'clone -c core.hideDotFiles' '
48+
test_commit attributes .gitattributes "" &&
49+
rm -rf child &&
50+
git clone -c core.hideDotFiles=false . child &&
51+
! is_hidden child/.gitattributes &&
52+
rm -rf child &&
53+
git clone -c core.hideDotFiles=dotGitOnly . child &&
54+
! is_hidden child/.gitattributes &&
55+
rm -rf child &&
56+
git clone -c core.hideDotFiles=true . child &&
57+
is_hidden child/.gitattributes
58+
'
59+
4060
test_done

0 commit comments

Comments
 (0)