From 10c6dc89128fda0b4de89d69bd0ca2087f531a29 Mon Sep 17 00:00:00 2001
From: Alex Riesen <raa@steel.home>
Date: Thu, 15 Feb 2007 18:34:30 +0100
Subject: [PATCH] Allow config files to be included

The syntax is:

    [include "filename"]

which is somewhat branch/remote-alike. There are a few differences, though:
filenames happen to be long, so a backslash is supported to split the
names into multiple lines. No bare LF allowed, so this is illegal:
    [include "path/
    name"]
On the other hand, this is allowed:
    [include "path/\
    name"]

Backslash is just to quote characters (as in shell).
Only one quoted string allowed, so this is bad too:

    [include "path/" "name"]

Signed-off-by: Alex Riesen <raa.lkml@gmail.com>
---
 config.c |  103 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 100 insertions(+), 3 deletions(-)

diff --git a/config.c b/config.c
index d821071..b42a7ea 100644
--- a/config.c
+++ b/config.c
@@ -12,6 +12,57 @@
 static FILE *config_file;
 static const char *config_file_name;
 static int config_linenr;
+
+struct fileinfo
+{
+	char *name;
+	int linenr;
+	FILE *file;
+	struct fileinfo *prev;
+};
+static struct fileinfo *config_stack = NULL;
+
+static void include_file(const char *filename)
+{
+	struct fileinfo *prev;
+	FILE *file;
+	file = fopen(filename, "r");
+	if (!file) {
+		error("ignored \"%s\": %s", filename, strerror(errno));
+		return;
+	}
+	prev = malloc(sizeof(*prev));
+	prev->name = (char *)config_file_name;
+	prev->linenr = config_linenr;
+	prev->file = config_file;
+	prev->prev = config_stack;
+	config_stack = prev;
+
+	config_file = file;
+	config_file_name = xstrdup(filename);
+	config_linenr = 0;
+}
+
+static FILE *pop_file(void)
+{
+	struct fileinfo *prev;
+
+	if (!config_stack)
+		/* The last file on stack does not belong to us.
+		 * Free the names and close all included files. */
+		return NULL;
+
+	free((void*)config_file_name);
+	fclose(config_file);
+	config_file = config_stack->file;
+	config_file_name = config_stack->name;
+	config_linenr = 0;
+	prev = config_stack->prev;
+	free(config_stack);
+	config_stack = prev;
+	return config_file;
+}
+
 static int get_next_char(void)
 {
 	int c;
@@ -31,13 +82,51 @@ static int get_next_char(void)
 		if (c == '\n')
 			config_linenr++;
 		if (c == EOF) {
-			config_file = NULL;
+			config_file = pop_file();
 			c = '\n';
 		}
 	}
 	return c;
 }
 
+static int parse_include(void)
+{
+	char name[PATH_MAX];
+	int quote = 0, len = 0;
+
+	for (;;) {
+		int c = get_next_char();
+		if (len >= sizeof(name))
+			return -1;
+		if (c == '"') {
+			quote++;
+			continue;
+		}
+		if (c == '\n')
+			/* do not allow bare \n anywhere in path */
+			return -1;
+		if (quote == 1) {
+			if (c == '\\') {
+				c = get_next_char();
+				if (c == '\n')
+					continue;
+			}
+			name[len++] = c;
+		}
+		if (quote == 2 && c == ']') {
+			do
+				c = get_next_char();
+			while (c != '\n');
+			break;
+		}
+		if ((quote < 1 || quote >= 2) && !isspace(c) )
+			return -1;
+	}
+	name[len] = '\0';
+	include_file(name);
+	return 0;
+}
+
 static char *parse_value(void)
 {
 	static char value[1024];
@@ -181,8 +270,13 @@ static int get_base_var(char *name)
 		int c = get_next_char();
 		if (c == EOF)
 			return -1;
+		if (!isalpha(c) && !strncmp(name, "include", baselen) &&
+		    config_file) {
+			ungetc(c, config_file);
+			return parse_include();
+		}
 		if (c == ']')
-			return baselen;
+			return baselen ? baselen: -1;
 		if (isspace(c))
 			return get_extended_base_var(name, baselen, c);
 		if (!iskeychar(c) && c != '.')
@@ -216,8 +310,11 @@ static int git_parse_file(config_fn_t fn)
 		}
 		if (c == '[') {
 			baselen = get_base_var(var);
-			if (baselen <= 0)
+			if (baselen < 0)
 				break;
+			if (!baselen)
+				/* [include "..."]*/
+				continue;
 			var[baselen++] = '.';
 			var[baselen] = 0;
 			continue;
-- 
1.5.0.138.g36f81

