[PATCH v2 0/2] Support marking .git/ (or all files) as hidden on Windows

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Windows does not share Unix' convention that files and directories whose
names start with a dot are hidden. Hence `.git/`, for example, is in
plain view, and caused quite a bit of trouble: some users wanted to peek
inside and did not understand what it contains, others modified files.

There was a stream of bug reports, until Git for Windows introduced the
(opt-out) option to hide at least the .git/ directory by default. The
option is configured via the config setting core.hideDotFiles, with the
possible values false, true and dotGitOnly (the latter being the
default).

This is a heavily version of patches we carried in Git for Windows for
way too long without submitting them upstream.

In this iteration, I also claim authorship for the patch because by now
Kusma's changes were so contorted and mutilated beyond recognition by me
that I do not want anybody to blame him for my sins.


Johannes Schindelin (2):
  mingw: introduce the 'core.hideDotFiles' setting
  mingw: remove unnecessary definition

 Documentation/config.txt |  6 ++++
 cache.h                  |  7 +++++
 compat/mingw.c           | 74 ++++++++++++++++++++++++++++++++++++++++++++++++
 compat/mingw.h           |  3 --
 config.c                 |  8 ++++++
 environment.c            |  1 +
 t/t0001-init.sh          | 30 ++++++++++++++++++++
 t/t5611-clone-config.sh  | 20 +++++++++++++
 8 files changed, 146 insertions(+), 3 deletions(-)

Published-As: https://github.com/dscho/git/releases/tag/hide-dotgit-v2
Interdiff vs v1:

 diff --git a/Documentation/config.txt b/Documentation/config.txt
 index 5d4e3b2..8747c2c 100644
 --- a/Documentation/config.txt
 +++ b/Documentation/config.txt
 @@ -270,10 +270,10 @@ See linkgit:git-update-index[1].
  The default is true (when core.filemode is not specified in the config file).
  
  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.
 +	(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 to mark only the `.git/` directory as hidden.
  
  core.ignoreCase::
  	If true, this option enables various workarounds to enable
 diff --git a/builtin/init-db.c b/builtin/init-db.c
 index c4269ac..b2d8d40 100644
 --- a/builtin/init-db.c
 +++ b/builtin/init-db.c
 @@ -370,7 +370,6 @@ 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/compat/mingw.c b/compat/mingw.c
 index 8b8b01c..3ecde84 100644
 --- a/compat/mingw.c
 +++ b/compat/mingw.c
 @@ -288,31 +288,47 @@ int mingw_rmdir(const char *pathname)
  
  static inline int needs_hiding(const char *path)
  {
 -	return hide_dotfiles == HIDE_DOTFILES_TRUE &&
 -		starts_with(basename((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 make_hidden(const wchar_t *path)
 +static int set_hidden_flag(const wchar_t *path, int set)
  {
  	DWORD attribs = GetFileAttributesW(path);
 -	if (SetFileAttributesW(path, FILE_ATTRIBUTE_HIDDEN | attribs))
 +	if (set)
 +		attribs |= FILE_ATTRIBUTE_HIDDEN;
 +	else
 +		attribs &= ~FILE_ATTRIBUTE_HIDDEN;
 +	if (SetFileAttributesW(path, 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;
 @@ -321,7 +337,7 @@ int mingw_mkdir(const char *path, int mode)
  		return -1;
  	ret = _wmkdir(wpath);
  	if (!ret && needs_hiding(path))
 -		return make_hidden(wpath);
 +		return set_hidden_flag(wpath, 1);
  	return ret;
  }
  
 @@ -348,9 +364,21 @@ 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 && needs_hiding(filename) &&
 -	    make_hidden(wfilename))
 -		warning("Could not mark '%s' as hidden.", filename);
 +	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;
  }
  
 @@ -382,7 +410,7 @@ int mingw_fgetc(FILE *stream)
  #undef fopen
  FILE *mingw_fopen (const char *filename, const char *otype)
  {
 -	int hide = needs_hiding(filename) && access(filename, F_OK);
 +	int hide = needs_hiding(filename);
  	FILE *file;
  	wchar_t wfilename[MAX_PATH], wotype[4];
  	if (filename && !strcmp(filename, "/dev/null"))
 @@ -390,15 +418,19 @@ FILE *mingw_fopen (const char *filename, const char *otype)
  	if (xutftowcs_path(wfilename, filename) < 0 ||
  		xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
  		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);
 -	if (file && hide && make_hidden(wfilename))
 +	if (file && hide && set_hidden_flag(wfilename, 1))
  		warning("Could not mark '%s' as hidden.", filename);
  	return file;
  }
  
  FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
  {
 -	int hide = needs_hiding(filename) && access(filename, F_OK);
 +	int hide = needs_hiding(filename);
  	FILE *file;
  	wchar_t wfilename[MAX_PATH], wotype[4];
  	if (filename && !strcmp(filename, "/dev/null"))
 @@ -406,8 +438,12 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
  	if (xutftowcs_path(wfilename, filename) < 0 ||
  		xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
  		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);
 -	if (file && hide && make_hidden(wfilename))
 +	if (file && hide && set_hidden_flag(wfilename, 1))
  		warning("Could not mark '%s' as hidden.", filename);
  	return file;
  }
 diff --git a/compat/mingw.h b/compat/mingw.h
 index 1de70ff..a1808b4 100644
 --- a/compat/mingw.h
 +++ b/compat/mingw.h
 @@ -416,9 +416,6 @@ int mingw_offset_1st_component(const char *path);
  void mingw_open_html(const char *path);
  #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.
   *
 diff --git a/git-compat-util.h b/git-compat-util.h
 index ea007e4..1f8b5f3 100644
 --- a/git-compat-util.h
 +++ b/git-compat-util.h
 @@ -1042,8 +1042,4 @@ struct tm *git_gmtime_r(const time_t *, struct tm *);
  #define getc_unlocked(fh) getc(fh)
  #endif
  
 -#ifndef mark_as_git_dir
 -#define mark_as_git_dir(x) /* noop */
 -#endif
 -
  #endif
 diff --git a/t/t5611-clone-config.sh b/t/t5611-clone-config.sh
 index 27d730c..e4850b7 100755
 --- a/t/t5611-clone-config.sh
 +++ b/t/t5611-clone-config.sh
 @@ -37,4 +37,24 @@ test_expect_success 'clone -c config is available during clone' '
  	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

-- 
2.8.2.463.g99156ee

--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]