git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Add ".git/config" file parser

From
Linus Torvalds <torvalds@osdl.org>
Date
Oct 10, 2005, 21:35 UTC
Message-ID
<Pine.LNX.4.64.0510101415080.14597@g5.osdl.org>
In-Reply-To
<Pine.LNX.4.64.0510101120410.14597@g5.osdl.org>
This is a first cut at a very simple parser for a git config file.

The format of the file is a simple ini-file like thing, with simple variable/value pairs. You can (and should) make the variables have a simple single-level scope, ie a valid file looks something like this:

	#
	# This is the config file, and
	# a '#' or ';' character indicates
	# a comment
	#
	; core variables
	[core]
		; Don't trust file modes
		filemode = false
	; Our diff algorithm 
	[diff]
		external = "/usr/local/bin/gnu-diff -u"
		renames = true

which parses into two variables: "core.filemode" is associated with the string "false", and "diff.external" gets the appropriate quoted value.

Right now we only react to one variable: "core.filemode" is a boolean that decides if we should care about the 0100 (user-execute) bit of the stat information. Even that is just a parsing demonstration - this doesn't actually implement that st_mode compare logic itself.

Different programs can react to different config options, although they should always fall back to calling "git_default_config()" on any config option name that they don't recognize.

Signed-off-by: Linus Torvalds <torvalds@osdl.org>
----

Ok, so it's stupid. But quite frankly, I think the Windows ini-file format is a hell of a lot more readable than something over-engineered like XML files or other crap.

The interface is really easy to use, imho. You can do things like
	static int enable_renames = 0;
	static int my_options(const char *var, const char *value)
	{
		if (!strcmp("diff.renames", var)) {
			enable_renames = git_config_bool(var, value);
			return 0;
		}
		/*
		 * Put other local option parsing for this program
		 * here .. 
		 */
		/* Fall back on the default ones */
		return git_default_config(var, value);
	}
and then in the "main()" routine you just do
		git_config(my_options);

at the top (or, more precisely, just after the "git_setup_directory()" if you have one).

And as usual, it's not like this has gotten a whole lot of testing.

Flames, comments, whatever? The code is actually written so that the config file parsing should really be pretty neutral. It doesn't even have any git-specific in it, except for the naming and the actual initial "fopen()" pathname used, I think.

---
diff --git a/Makefile b/Makefile
index a201187..e8b46f1 100644
--- a/Makefile
+++ b/Makefile
@@ -158,7 +158,7 @@ LIB_OBJS = \
 	object.o pack-check.o patch-delta.o path.o pkt-line.o \
 	quote.o read-cache.o refs.o run-command.o \
 	server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
-	tag.o tree.o usage.o $(DIFF_OBJS)
+	tag.o tree.o usage.o config.o $(DIFF_OBJS)
 
 LIBS = $(LIB_FILE)
 LIBS += -lz
diff --git a/cache.h b/cache.h
index 5987d4c..0571282 100644
--- a/cache.h
+++ b/cache.h
@@ -178,6 +178,8 @@ extern int hold_index_file_for_update(st
 extern int commit_index_file(struct cache_file *);
 extern void rollback_index_file(struct cache_file *);
 
+extern int trust_executable_bit;
+
 #define MTIME_CHANGED	0x0001
 #define CTIME_CHANGED	0x0002
 #define OWNER_CHANGED	0x0004
@@ -372,4 +374,10 @@ extern int gitfakemunmap(void *start, si
 
 #endif
 
+typedef int (*config_fn_t)(const char *, const char *);
+extern int git_default_config(const char *, const char *);
+extern int git_config(config_fn_t fn);
+extern int git_config_int(const char *, const char *);
+extern int git_config_bool(const char *, const char *);
+
 #endif /* CACHE_H */
diff --git a/config.c b/config.c
new file mode 100644
index 0000000..f3c4fa4
--- /dev/null
+++ b/config.c
@@ -0,0 +1,222 @@
+#include <ctype.h>
+
+#include "cache.h"
+
+#define MAXNAME (256)
+
+static FILE *config_file;
+static int config_linenr;
+static int get_next_char(void)
+{
+	int c;
+	FILE *f;
+
+	c = '\n';
+	if ((f = config_file) != NULL) {
+		c = fgetc(f);
+		if (c == '\n')
+			config_linenr++;
+		if (c == EOF) {
+			config_file = NULL;
+			c = '\n';
+		}
+	}
+	return c;
+}
+
+static char *parse_value(void)
+{
+	static char value[1024];
+	int quote = 0, comment = 0, len = 0, space = 0;
+
+	for (;;) {
+		int c = get_next_char();
+		if (len >= sizeof(value))
+			return NULL;
+		if (c == '\n') {
+			if (quote)
+				return NULL;
+			value[len] = 0;
+			return value;
+		}
+		if (comment)
+			continue;
+		if (isspace(c) && !quote) {
+			space = 1;
+			continue;
+		}
+		if (space) {
+			if (len)
+				value[len++] = ' ';
+			space = 0;
+		}
+		if (c == '\\') {
+			c = get_next_char();
+			switch (c) {
+			case '\n':
+				continue;
+			case 't':
+				c = '\t';
+				break;
+			case 'b':
+				c = '\b';
+				break;
+			case 'n':
+				c = '\n';
+				break;
+			return NULL;
+			}
+			value[len++] = c;
+			continue;
+		}
+		if (c == '"') {
+			quote = 1-quote;
+			continue;
+		}
+		if (!quote) {
+			if (c == ';' || c == '#') {
+				comment = 1;
+				continue;
+			}
+		}
+		value[len++] = c;
+	}
+}
+
+static int get_value(config_fn_t fn, char *name, unsigned int len)
+{
+	int c;
+	char *value;
+
+	/* Get the full name */
+	for (;;) {
+		c = get_next_char();
+		if (c == EOF)
+			break;
+		if (!isalnum(c))
+			break;
+		name[len++] = tolower(c);
+		if (len >= MAXNAME)
+			return -1;
+	}
+	name[len] = 0;
+	while (c == ' ' || c == '\t')
+		c = get_next_char();
+
+	value = NULL;
+	if (c != '\n') {
+		if (c != '=')
+			return -1;
+		value = parse_value();
+		if (!value)
+			return -1;
+	}
+	return fn(name, value);
+}
+
+static int get_base_var(char *name)
+{
+	int baselen = 0;
+
+	for (;;) {
+		int c = get_next_char();
+		if (c == EOF)
+			return -1;
+		if (c == ']')
+			return baselen;
+		if (!isalnum(c))
+			return -1;
+		if (baselen > MAXNAME / 2)
+			return -1;
+		name[baselen++] = tolower(c);
+	}
+}
+
+static int git_parse_file(config_fn_t fn)
+{
+	int comment = 0;
+	int baselen = 0;
+	static char var[MAXNAME];
+
+	for (;;) {
+		int c = get_next_char();
+		if (c == '\n') {
+			/* EOF? */
+			if (!config_file)
+				return 0;
+			comment = 0;
+			continue;
+		}
+		if (comment || isspace(c))
+			continue;
+		if (c == '#' || c == ';') {
+			comment = 1;
+			continue;
+		}
+		if (c == '[') {
+			baselen = get_base_var(var);
+			if (baselen <= 0)
+				break;
+			var[baselen++] = '.';
+			var[baselen] = 0;
+			continue;
+		}
+		if (!isalpha(c))
+			break;
+		var[baselen] = c;
+		if (get_value(fn, var, baselen+1) < 0)
+			break;
+	}
+	die("bad config file line %d", config_linenr);
+}
+
+int git_config_int(const char *name, const char *value)
+{
+	if (value && *value) {
+		char *end;
+		int val = strtol(value, &end, 0);
+		if (!*end)
+			return val;
+	}
+	die("bad config value for '%s'", name);
+}
+
+int git_config_bool(const char *name, const char *value)
+{
+	if (!value)
+		return 1;
+	if (!*value)
+		return 0;
+	if (!strcasecmp(value, "true"))
+		return 1;
+	if (!strcasecmp(value, "false"))
+		return 0;
+	return git_config_int(name, value) != 0;
+}
+
+int git_default_config(const char *var, const char *value)
+{
+	/* This needs a better name */
+	if (!strcmp(var, "core.filemode")) {
+		trust_executable_bit = git_config_bool(var, value);
+		return 0;
+	}
+
+	/* Add other config variables here.. */
+	return 0;
+}
+
+int git_config(config_fn_t fn)
+{
+	int ret;
+	FILE *f = fopen(git_path("config"), "r");
+
+	ret = -1;
+	if (f) {
+		config_file = f;
+		config_linenr = 1;
+		ret = git_parse_file(fn);
+		fclose(f);
+	}
+	return ret;
+}
diff --git a/diff-files.c b/diff-files.c
index 5e59832..96d2c7f 100644
--- a/diff-files.c
+++ b/diff-files.c
@@ -38,6 +38,7 @@ int main(int argc, const char **argv)
 	const char *prefix = setup_git_directory();
 	int entries, i;
 
+	git_config(git_default_config);
 	diff_setup(&diff_options);
 	while (1 < argc && argv[1][0] == '-') {
 		if (!strcmp(argv[1], "-q"))
diff --git a/diff-tree.c b/diff-tree.c
index b2d74eb..2203fa5 100644
--- a/diff-tree.c
+++ b/diff-tree.c
@@ -408,6 +408,7 @@ int main(int argc, const char **argv)
 	unsigned char sha1[2][20];
 	const char *prefix = setup_git_directory();
 
+	git_config(git_default_config);
 	nr_sha1 = 0;
 	diff_setup(&diff_options);
 
diff --git a/read-cache.c b/read-cache.c
index d2aebdd..c7f3b26 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -5,6 +5,7 @@
  */
 #include "cache.h"
 
+int trust_executable_bit = 1;
 struct cache_entry **active_cache = NULL;
 unsigned int active_nr = 0, active_alloc = 0, active_cache_changed = 0;
 
Previous: Linus TorvaldsNext: Linus Torvalds
Message 11 of 18 in “Seeing various mode changes on cygwin”
  1. Jonas FonsecaOct 8, 2005
  2. Junio C HamanoOct 8, 2005
  3. Alex RiesenOct 8, 2005
  4. Junio C HamanoOct 8, 2005
  5. Linus TorvaldsOct 9, 2005
  6. Junio C HamanoOct 9, 2005
  7. Daniel BarkalowOct 10, 2005
  8. Junio C HamanoOct 10, 2005
  9. Daniel BarkalowOct 10, 2005
  10. Linus TorvaldsOct 10, 2005
  11. Add ".git/config" file parserLinus Torvalds, Oct 10, 2005
  12. Linus TorvaldsOct 10, 2005
  13. H. Peter AnvinOct 10, 2005
  14. Linus TorvaldsOct 10, 2005
  15. Junio C HamanoOct 12, 2005
  16. Use core.filemode.Junio C Hamano, Oct 12, 2005
  17. H. Peter AnvinOct 9, 2005
  18. Alex RiesenOct 9, 2005

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.