Skip to content

RFC: system-wide config file in %PROGRAMDATA% #157

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions Documentation/config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ the Git commands' behavior. The `.git/config` file in each repository
is used to store the configuration for that repository, and
`$HOME/.gitconfig` is used to store a per-user configuration as
fallback values for the `.git/config` file. The file `/etc/gitconfig`
can be used to store a system-wide default configuration. On Windows,
configuration can also be stored in `C:\ProgramData\Git\config`; This
file will be used also by libgit2-based software.
can be used to store a system-wide default configuration
(`%PROGRAMDATA%\Git\gitconfig` on Windows).

The configuration variables are used by both the Git plumbing
and the porcelains. The variables are divided into sections, wherein
Expand Down
26 changes: 19 additions & 7 deletions Documentation/git-config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,14 @@ See also <<FILES>>.

--system::
For writing options: write to system-wide
`$(prefix)/etc/gitconfig` rather than the repository
`/etc/gitconfig` rather than the repository
`.git/config`.
+
For reading options: read only from system-wide `$(prefix)/etc/gitconfig`
For reading options: read only from system-wide `/etc/gitconfig`
rather than from all available files.
+
On Windows, the system-wide config file is `%PROGRAMDATA%\Git\gitconfig`.
+
See also <<FILES>>.

--local::
Expand Down Expand Up @@ -221,11 +223,21 @@ See also <<FILES>>.
FILES
-----

If not set explicitly with '--file', there are four files where
If not set explicitly with '--file', there are five files where
'git config' will search for configuration options:

$(prefix)/etc/gitconfig::
System-wide configuration file.
Installation-specific configuration file, where '$(prefix)' is the
installation root directory specified via 'make prefix=...'. This allows
software distributions to provide installation-specific default values
(e.g. 'help.format=html' if the installation only includes html pages).

/etc/gitconfig::
System-wide configuration file (`%PROGRAMDATA%\Git\gitconfig` on Windows).
+
If git was built with relative `$(sysconfdir)`, this file will not be
used, and the '--system' option refers to the installation-specific
`$(prefix)/etc/gitconfig` instead.

$XDG_CONFIG_HOME/git/config::
Second user-specific configuration file. If $XDG_CONFIG_HOME is not set
Expand Down Expand Up @@ -268,11 +280,11 @@ ENVIRONMENT
GIT_CONFIG::
Take the configuration from the given file instead of .git/config.
Using the "--global" option forces this to ~/.gitconfig. Using the
"--system" option forces this to $(prefix)/etc/gitconfig.
"--system" option forces this to /etc/gitconfig.

GIT_CONFIG_NOSYSTEM::
Whether to skip reading settings from the system-wide
$(prefix)/etc/gitconfig file. See linkgit:git[1] for details.
Whether to skip reading settings from the installation-specific and
system-wide /etc/gitconfig files. See linkgit:git[1] for details.

See also <<FILES>>.

Expand Down
4 changes: 2 additions & 2 deletions Documentation/git.txt
Original file line number Diff line number Diff line change
Expand Up @@ -939,8 +939,8 @@ for further details.
on the terminal (e.g., when asking for HTTP authentication).

'GIT_CONFIG_NOSYSTEM'::
Whether to skip reading settings from the system-wide
`$(prefix)/etc/gitconfig` file. This environment variable can
Whether to skip reading settings from the installation-specific and
system-wide `/etc/gitconfig` files. This environment variable can
be used along with `$HOME` and `$XDG_CONFIG_HOME` to create a
predictable environment for a picky script, or you can set it
temporarily to avoid using a buggy `/etc/gitconfig` file while
Expand Down
2 changes: 1 addition & 1 deletion Documentation/gitattributes.txt
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ for a single user should be placed in a file specified by the
Its default value is $XDG_CONFIG_HOME/git/attributes. If $XDG_CONFIG_HOME
is either not set or empty, $HOME/.config/git/attributes is used instead.
Attributes for all users on a system should be placed in the
`$(prefix)/etc/gitattributes` file.
`/etc/gitattributes` file (`%PROGRAMDATA%\Git\gitattributes` on Windows).

Sometimes you would need to override an setting of an attribute
for a path to `Unspecified` state. This can be done by listing
Expand Down
101 changes: 65 additions & 36 deletions config.c
Original file line number Diff line number Diff line change
Expand Up @@ -1166,14 +1166,32 @@ static int git_config_from_blob_ref(config_fn_t fn,
return git_config_from_blob_sha1(fn, name, sha1, data);
}

static const char *etc_gitconfig = ETC_GITCONFIG;

const char *git_etc_gitconfig(void)
{
static const char *system_wide;
if (!system_wide)
system_wide = system_path(ETC_GITCONFIG);
system_wide = system_path(etc_gitconfig);
return system_wide;
}

static const char *git_inst_gitconfig(void)
{
static const char *installation_defaults;
if (!installation_defaults) {
/*
* if ETC_GITCONFIG as configured in the Makefile is an absolute path,
* also load installation-specific defaults (relative to $(prefix))
*/
if (is_dir_sep(*etc_gitconfig))
installation_defaults = system_path(etc_gitconfig + 1);
else
installation_defaults = "";
}
return *installation_defaults ? installation_defaults : NULL;
}

/*
* Parse environment variable 'k' as a boolean (in various
* possible spellings); if missing, use the default value 'def'.
Expand Down Expand Up @@ -1201,41 +1219,38 @@ int git_config_system(void)
return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
}

static inline int config_early_helper(config_fn_t fn, const char *filename,
void *data, unsigned access_flags, int count) {
if (!filename || access_or_die(filename, R_OK, access_flags))
/* no file: return unchanged */
return count;

if (git_config_from_file(fn, filename, data))
/* error: decrement or start counting errors at -1 */
return count < 0 ? count - 1 : -1;
else
/* ok: increment unless we had errors before */
return count < 0 ? count : count + 1;
}

int git_config_early(config_fn_t fn, void *data, const char *repo_config)
{
int ret = 0, found = 0;
const char *super_config = git_super_config();
/* count loaded files (> 0) or errors (< 0) */
int cnt = 0;
char *xdg_config = NULL;
char *user_config = NULL;

home_config_paths(&user_config, &xdg_config, "config");

if (super_config && git_config_system() &&
!access(super_config, R_OK)) {
ret += git_config_from_file(fn, super_config, data);
found += 1;
if (git_config_system()) {
cnt = config_early_helper(fn, git_inst_gitconfig(), data, 0, cnt);
cnt = config_early_helper(fn, git_etc_gitconfig(), data, 0, cnt);
}

if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0)) {
ret += git_config_from_file(fn, git_etc_gitconfig(),
data);
found += 1;
}
cnt = config_early_helper(fn, xdg_config, data, ACCESS_EACCES_OK, cnt);
cnt = config_early_helper(fn, user_config, data, ACCESS_EACCES_OK, cnt);

if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK)) {
ret += git_config_from_file(fn, xdg_config, data);
found += 1;
}

if (user_config && !access_or_die(user_config, R_OK, ACCESS_EACCES_OK)) {
ret += git_config_from_file(fn, user_config, data);
found += 1;
}

if (repo_config && !access_or_die(repo_config, R_OK, 0)) {
ret += git_config_from_file(fn, repo_config, data);
found += 1;
}
cnt = config_early_helper(fn, repo_config, data, 0, cnt);

switch (git_config_from_parameters(fn, data)) {
case -1: /* error */
Expand All @@ -1244,13 +1259,14 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
case 0: /* found nothing */
break;
default: /* found at least one item */
found++;
if (cnt >= 0)
cnt++;
break;
}

free(xdg_config);
free(user_config);
return ret == 0 ? found : ret;
return cnt;
}

int git_config_with_options(config_fn_t fn, void *data,
Expand Down Expand Up @@ -1932,6 +1948,24 @@ int git_config_parse_key(const char *key, char **store_key, int *baselen_)
return -CONFIG_INVALID_KEY;
}


static int lock_config_file(const char *config_filename,
struct lock_file **result)
{
int fd;
/* make sure the parent directory exists */
if (safe_create_leading_directories_const(config_filename)) {
error("could not create parent directory of %s", config_filename);
return -1;
}
*result = xcalloc(1, sizeof(struct lock_file));
fd = hold_lock_file_for_update(*result, config_filename, 0);
if (fd < 0)
error("could not lock config file %s: %s", config_filename,
strerror(errno));
return fd;
}

/*
* If value==NULL, unset in (remove from) config,
* if value_regex!=NULL, disregard key/value pairs where value does not match.
Expand Down Expand Up @@ -1980,10 +2014,8 @@ int git_config_set_multivar_in_file(const char *config_filename,
* The lock serves a purpose in addition to locking: the new
* contents of .git/config will be written into it.
*/
lock = xcalloc(1, sizeof(struct lock_file));
fd = hold_lock_file_for_update(lock, config_filename, 0);
fd = lock_config_file(config_filename, &lock);
if (fd < 0) {
error("could not lock config file %s: %s", config_filename, strerror(errno));
free(store.key);
ret = CONFIG_NO_LOCK;
goto out_free;
Expand Down Expand Up @@ -2251,12 +2283,9 @@ int git_config_rename_section_in_file(const char *config_filename,
if (!config_filename)
config_filename = filename_buf = git_pathdup("config");

lock = xcalloc(1, sizeof(struct lock_file));
out_fd = hold_lock_file_for_update(lock, config_filename, 0);
if (out_fd < 0) {
ret = error("could not lock config file %s", config_filename);
out_fd = lock_config_file(config_filename, &lock);
if (out_fd < 0)
goto out;
}

if (!(config_file = fopen(config_filename, "rb"))) {
/* no config file means nothing to rename, no error */
Expand Down
6 changes: 6 additions & 0 deletions config.mak.uname
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ endif
ifeq ($(uname_S),Windows)
GIT_VERSION := $(GIT_VERSION).MSVC
pathsep = ;
sysconfdir = /etc
HAVE_ALLOCA_H = YesPlease
NO_PREAD = YesPlease
NEEDS_CRYPTO_WITH_SSL = YesPlease
Expand Down Expand Up @@ -484,6 +485,7 @@ ifeq ($(uname_S),NONSTOP_KERNEL)
endif
ifneq (,$(findstring MINGW,$(uname_S)))
pathsep = ;
sysconfdir = /etc
HAVE_ALLOCA_H = YesPlease
NO_PREAD = YesPlease
NEEDS_CRYPTO_WITH_SSL = YesPlease
Expand Down Expand Up @@ -532,6 +534,10 @@ ifneq (,$(findstring MINGW,$(uname_S)))
ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
htmldir = share/doc/git/$(firstword $(subst -, ,$(GIT_VERSION)))/html
prefix =
# prevent conversion to Windows path on MSys1 (see
# http://www.mingw.org/wiki/Posix_path_conversion)
ETC_GITCONFIG = //etc\gitconfig
ETC_GITATTRIBUTES = //etc\gitattributes
INSTALL = /bin/install
EXTLIBS += /mingw/lib/libz.a
NO_R_TO_GCC_LINKER = YesPlease
Expand Down
18 changes: 17 additions & 1 deletion exec_cmd.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,24 @@ char *system_path(const char *path)
#endif
struct strbuf d = STRBUF_INIT;

if (is_absolute_path(path))
if (is_absolute_path(path)) {
#ifdef _WIN32
/*
* On Windows (all variants), replace '/etc/' with '%PROGRAMDATA%/Git/'
* (or '%ALLUSERSPROFILE%/Git' in case of Windows XP)
*/
if (!strncmp(path, "/etc/", 5)) {
const char *sysconfdir = getenv("PROGRAMDATA");
if (!sysconfdir)
sysconfdir = getenv("ALLUSERSPROFILE");
if (sysconfdir) {
strbuf_addf(&d, "%s/Git/%s", sysconfdir, path + 5);
return strbuf_detach(&d, NULL);
}
}
#endif
return xstrdup(path);
}

#ifdef RUNTIME_PREFIX
assert(argv0_path);
Expand Down