Re: [PATCH] config: read system-wide defaults from /etc/gitconfig
Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
Show 5 quoted lines
>> I think we need to have a way to disable this setting, perhaps
>> via an environment variable.
>
> Yes. Just set GIT_CONFIG=/what/ever, and it no longer reads from
> ~/.gitconfig or $prefix/etc/gitconfig.
I think GIT_CONFIG and GIT_LOCAL_CONFIG environment variables are seriously misdesigned. At least, I do not think of a sane way to make use of them. For one thing, when they are set, truly per-repository variables such as repositoryformatversion, legacyheaders and sharedrepository are all ignored.
What I would think is sensible would be simply to:
- $GIT_CONFIG_SYSTEM environment, if set, names a file to be
read first. It defaults to /etc/gitconfig. You can set it
to /dev/null to avoid using /etc/gitconfig if you have
specific need.
- Then $HOME/.gitconfig is read.
- Then $GIT_DIR/config is read.
Information read from later files overrides the earlier ones, as before.
I am not quite sure how $GIT_CONFIG and $GIT_CONFIG_LOCAL were meant to be used. Are there any *real* users? With lack of information on the intended uses of these two environment variables, I hacked up the following tweaks on top of the above defined semantics, to imitate what I _think_ was the original intention. The hacked one goes like this:
- $GIT_CONFIG environment, if set, names a file to be read
first. We read it first.
- If $GIT_CONFIG is unset, then $GIT_CONFIG_SYSTEM (or
/etc/gitconfig) and $HOME/.gitconfig are read, as above.
- Next file to be read is $GIT_CONFIG_LOCAL (if set) or
$GIT_DIR/config. When reading this file, if we read from
$GIT_CONFIG earlier, we read only "core.*" section from it.
The difference from the original, aside from the additional business with /etc/gitconfig, is that this updated one does read from $GIT_DIR/config (or $GIT_CONFIG_LOCAL) to avoid missing more important per-repo variables. Since I did not understand why $GIT_CONFIG makes the remaining configuration files to be totally skipped in the original, I added hacks to make the last step to read only minimally.
I'd rather not to have the hacks to deal with GIT_CONFIG and GIT_CONFIG_LOCAL I did in this patch, but I do not know enough to tell if they are meaningless hacks to imitate ill-defined original semantics, or if they still cover useful use cases these two environment variables were originally invented to satisfy.
---
Makefile | 10 +++++--
cache.h | 4 +++
config.c | 84 +++++++++++++++++++++++++++++++++++++++----------------------
3 files changed, 65 insertions(+), 33 deletions(-)
diff --git a/Makefile b/Makefile
index ebecbbd..4232465 100644
--- a/Makefile
+++ b/Makefile
@@ -122,6 +122,7 @@ STRIP ?= strip
prefix = $(HOME)
bindir = $(prefix)/bin
+etcdir = $(prefix)/etc
gitexecdir = $(bindir)
template_dir = $(prefix)/share/git-core/templates/
# DESTDIR=
@@ -142,7 +143,7 @@ GITWEB_FAVICON = git-favicon.png
GITWEB_SITE_HEADER =
GITWEB_SITE_FOOTER =
-export prefix bindir gitexecdir template_dir
+export prefix bindir etcdir gitexecdir template_dir
CC = gcc
AR = ar
@@ -587,6 +588,7 @@ SHA1_HEADER_SQ = $(subst ','\'',$(SHA1_HEADER))
DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
bindir_SQ = $(subst ','\'',$(bindir))
+etcdir_SQ = $(subst ','\'',$(etcdir))
gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
template_dir_SQ = $(subst ','\'',$(template_dir))
prefix_SQ = $(subst ','\'',$(prefix))
@@ -596,7 +598,9 @@ PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH))
LIBS = $(GITLIBS) $(EXTLIBS)
-BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' $(COMPAT_CFLAGS)
+BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)'
+BASIC_CFLAGS += -DGIT_CONFIG_SYSTEM='"$(etcdir_SQ)/gitconfig"'
+BASIC_CFLAGS += $(COMPAT_CFLAGS)
LIB_OBJS += $(COMPAT_OBJS)
ALL_CFLAGS += $(BASIC_CFLAGS)
diff --git a/cache.h b/cache.h
index c62b0b0..2b4e26d 100644
--- a/cache.h
+++ b/cache.h
@@ -125,6 +125,10 @@ extern int cache_errno;
#define TEMPLATE_DIR_ENVIRONMENT "GIT_TEMPLATE_DIR"
#define CONFIG_ENVIRONMENT "GIT_CONFIG"
#define CONFIG_LOCAL_ENVIRONMENT "GIT_CONFIG_LOCAL"
+#define CONFIG_SYSTEM_ENVIRONMENT "GIT_CONFIG_SYSTEM"
+#ifndef GIT_CONFIG_SYSTEM
+#define GIT_CONFIG_SYSTEM "/etc/gitconfig"
+#endif
#define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH"
extern int is_bare_repository_cfg;
diff --git a/config.c b/config.c
index c938aa0..7ec0d9a 100644
--- a/config.c
+++ b/config.c
@@ -107,7 +107,7 @@ static inline int iskeychar(int c)
return isalnum(c) || c == '-';
}
-static int get_value(config_fn_t fn, char *name, unsigned int len)
+static int get_value(config_fn_t fn, char *name, unsigned int len, int core_only)
{
int c;
char *value;
@@ -135,6 +135,8 @@ static int get_value(config_fn_t fn, char *name, unsigned int len)
if (!value)
return -1;
}
+ if (core_only && strncmp(name, "core.", 5))
+ return 0;
return fn(name, value);
}
@@ -193,7 +195,7 @@ static int get_base_var(char *name)
}
}
-static int git_parse_file(config_fn_t fn)
+static int git_parse_file(config_fn_t fn, int core_only)
{
int comment = 0;
int baselen = 0;
@@ -225,7 +227,8 @@ static int git_parse_file(config_fn_t fn)
if (!isalpha(c))
break;
var[baselen] = tolower(c);
- if (get_value(fn, var, baselen+1) < 0)
+
+ if (get_value(fn, var, baselen+1, core_only) < 0)
break;
}
die("bad config file line %d in %s", config_linenr, config_file_name);
@@ -356,50 +359,71 @@ int git_default_config(const char *var, const char *value)
return 0;
}
-int git_config_from_file(config_fn_t fn, const char *filename)
+static int git_config_from_file_1(config_fn_t fn, const char *filename, int core_only, int missing_ok)
{
int ret;
FILE *f = fopen(filename, "r");
- ret = -1;
+ ret = missing_ok ? 0 : -1;
if (f) {
config_file = f;
config_file_name = filename;
config_linenr = 1;
- ret = git_parse_file(fn);
+ ret = git_parse_file(fn, core_only);
fclose(f);
config_file_name = NULL;
}
return ret;
}
+int git_config_from_file(config_fn_t fn, const char *filename)
+{
+ return git_config_from_file_1(fn, filename, 0, 0);
+}
+
int git_config(config_fn_t fn)
{
+ /*
+ * If GIT_CONFIG environment is set, then it is read first.
+ * If GIT_CONFIG environment is not set,
+ * GIT_CONFIG_SYSTEM (default /etc/gitconfig) is read next.
+ * Then $HOME/.gitconfig is read.
+ *
+ * Then, if GIT_CONFIG_LOCAL is set, it is read.
+ * Otherwise "$GIT_DIR/config" is read.
+ *
+ * However, if we read from GIT_CONFIG environment earlier, we
+ * only read "core.*" section in the last step.
+ */
int ret = 0;
- char *repo_config = NULL;
- const char *home = NULL, *filename;
-
- /* $GIT_CONFIG makes git read _only_ the given config file,
- * $GIT_CONFIG_LOCAL will make it process it in addition to the
- * global config file, the same way it would the per-repository
- * config file otherwise. */
- filename = getenv(CONFIG_ENVIRONMENT);
- if (!filename) {
- home = getenv("HOME");
- filename = getenv(CONFIG_LOCAL_ENVIRONMENT);
- if (!filename)
- filename = repo_config = xstrdup(git_path("config"));
- }
-
- if (home) {
- char *user_config = xstrdup(mkpath("%s/.gitconfig", home));
- if (!access(user_config, R_OK))
- ret = git_config_from_file(fn, user_config);
- free(user_config);
- }
-
- ret += git_config_from_file(fn, filename);
- free(repo_config);
+ int core_only;
+ char *cfg;
+ char path[PATH_MAX];
+
+ cfg = getenv(CONFIG_ENVIRONMENT);
+ if (cfg) {
+ ret += git_config_from_file_1(fn, cfg, 0, 1);
+ core_only = 1;
+ }
+ else {
+ core_only = 0;
+ cfg = getenv(CONFIG_SYSTEM_ENVIRONMENT);
+ if (!cfg)
+ cfg = GIT_CONFIG_SYSTEM;
+ ret += git_config_from_file_1(fn, cfg, 0, 1);
+
+ cfg = getenv("HOME");
+ if (cfg) {
+ snprintf(path, sizeof(path), "%s/.gitconfig", cfg);
+ ret += git_config_from_file_1(fn, path, 0, 1);
+ }
+ }
+ cfg = getenv(CONFIG_LOCAL_ENVIRONMENT);
+ if (!cfg) {
+ strcpy(path, git_path("config"));
+ cfg = path;
+ }
+ ret += git_config_from_file_1(fn, cfg, core_only, 1);
return ret;
}