Commit 11d0e70578baf47fb1cb565e0336e18d417e5da6

Carlos Martín Nieto 2011-03-31T10:50:11

Add support for subsections A variable name is stored internally with its section the way it appeared in the configuration file in order to have the information about what parts are case-sensitive inline. Really implement parse_section_header_ext and move the assignment of variables to config_parse. The variable name matching is now done in a case-away way by cvar_name_match and cvar_section_match. Before the user sees it, it's normalized to the two- or three-dot version. Signed-off-by: Carlos Martín Nieto <cmn@elego.de>

diff --git a/src/config.c b/src/config.c
index 8ffbf53..c3f36f8 100644
--- a/src/config.c
+++ b/src/config.c
@@ -34,7 +34,7 @@
  * Forward declarations
  ***********************/
 static int config_parse(git_config *cfg_file);
-static int parse_variable(git_config *cfg, const char *section_name, const char *line);
+static int parse_variable(const char *line, char **var_name, char **var_value);
 void git_config_free(git_config *cfg);
 
 static git_cvar *cvar_free(git_cvar *var)
@@ -56,20 +56,112 @@ static void cvar_list_free(git_cvar *start)
 }
 
 /*
- * FIXME: Only the section name is case-insensitive
+ * The order is importart. The first parameter is the name we want to
+ * match against, and the second one is what we're looking for
  */
+static int cvar_section_match(const char *local, const char *input)
+{
+	char *input_dot = strrchr(input, '.');
+	char *local_last_dot = strrchr(local, '.');
+	char *local_sp = strchr(local, ' ');
+	int comparison_len;
+
+	/*
+	 * If the local section name doesn't contain a space, then we can
+	 * just do a case-insensitive compare.
+	 */
+	if (local_sp == NULL)
+		return !strncasecmp(local, input, local_last_dot - local);
+
+	/* Anything before the space in local is case-insensitive */
+	if (strncasecmp(local, input, local_sp - local)) {
+		fprintf(stderr, "copmparison of %s and %s failed\n", local, input);
+		return 0;
+	}
+
+	/*
+	 * We compare starting from the first character after the
+	 * quotation marks, which is two characters beyond the space. For
+	 * the input, we start one character beyond the first dot.
+	 * The length is given by the length between the quotation marks.
+	 *
+	 * this "that".var
+	 *       ^    ^
+	 *       a    b
+	 *
+	 * where a is (local_sp + 2) and b is local_last_dot. The comparison
+	 * length is given by b - 1 - a.
+	 */
+	input_dot = strchr(input, '.');
+	comparison_len = local_last_dot - 1 - (local_sp + 2);
+	return !strncmp(local_sp + 2, input_dot + 1, comparison_len);
+}
+
+static int cvar_name_match(const char *local, const char *input)
+{
+	char *input_dot = strrchr(input, '.');
+	char *local_dot = strrchr(local, '.');
+
+	/*
+	 * First try to match the section name
+	 */
+	if (!cvar_section_match(local, input))
+		return 0;
+
+	/*
+	 * Anything after the last (possibly only) dot is case-insensitive
+	 */
+	if (!strcmp(input_dot, local_dot))
+		return 1;
+
+	return 0;
+}
+
 static git_cvar *cvar_list_find(git_cvar *start, const char *name)
 {
 	git_cvar *iter;
 
 	CVAR_LIST_FOREACH (start, iter) {
-		if (!strcasecmp(name, iter->name))
+		if (cvar_name_match(iter->name, name))
 			return iter;
 	}
 
 	return NULL;
 }
 
+static int cvar_name_normalize(const char *input, char **output)
+{
+	char *input_sp = strchr(input, ' ');
+	char *quote, *str;
+	int i;
+
+	/* We need to make a copy anyway */
+	str = git__strdup(input);
+	if (str == NULL)
+		return GIT_ENOMEM;
+
+	*output = str;
+
+	/* If there aren't any spaces, we don't need to do anything */
+	if (input_sp == NULL)
+		return GIT_SUCCESS;
+
+	/*
+	 * If there are spaces, we replace the space by a dot, move the
+	 * variable name so that the dot before it replaces the last
+	 * quotation mark and repeat so that the first quotation mark
+	 * disappears.
+	 */
+	str[input_sp - input] = '.';
+
+	for (i = 0; i < 2; ++i) {
+		quote = strrchr(str, '"');
+		memmove(quote, quote + 1, strlen(quote));
+	}
+
+	return GIT_SUCCESS;
+}
+
 void strntolower(char *str, int len)
 {
 	int i;
@@ -145,9 +237,15 @@ int git_config_foreach(git_config *cfg, int (*fn)(const char *, void *), void *d
 {
 	int ret = GIT_SUCCESS;
 	git_cvar *var;
+	char *normalized;
 
 	CVAR_LIST_FOREACH(cfg->vars, var) {
-		ret = fn(var->name, data);
+		ret = cvar_name_normalize(var->name, &normalized);
+		if (ret < GIT_SUCCESS)
+			return ret;
+
+		ret = fn(normalized, data);
+		free(normalized);
 		if (ret)
 			break;
 	}
@@ -518,16 +616,88 @@ static char *build_varname(const char *section, const char *name)
 		return NULL;
 
 	ret = snprintf(varname, total_len, "%s.%s", section, name);
-	if(ret >= 0){
-		strtolower(varname + section_len + 1);
+	if(ret >= 0){ /* lowercase from the last dot onwards */
+		char *dot = strrchr(varname, '.');
+		if (dot != NULL)
+			strtolower(dot);
 	}
 
 	return varname;
 }
 
-static char *parse_section_header_ext(char *base_name, git_config *cfg)
+static int parse_section_header_ext(git_config *cfg, const char *base_name, const char *line, char **section_name)
 {
-	return base_name;
+	int buf_len, total_len, pos;
+	int c;
+	char *subsection;
+	int error = GIT_SUCCESS;
+	int quote_marks;
+	/*
+	 * base_name is what came before the space. We should be at the
+	 * first quotation mark, except for now, line isn't being kept in
+	 * sync so we only really use it to calculate the length.
+	 */
+
+	buf_len = strrchr(line, '"') - strchr(line, '"') + 2;
+	if(!buf_len)
+		return GIT_EOBJCORRUPTED;
+
+	subsection = git__malloc(buf_len + 2);
+	if(subsection == NULL)
+		return GIT_ENOMEM;
+
+	pos = 0;
+	c = cfg_getchar(cfg, 0);
+	quote_marks = 0;
+
+	/*
+	 * At the end of each iteration, whatever is stored in c will be
+	 * added to the string. In case of error, jump to out
+	 */
+	do {
+		switch(c) {
+		case '"':
+			if (quote_marks++ >= 2)
+				return GIT_EOBJCORRUPTED;
+			break;
+		case '\\':
+			c = cfg_getchar(cfg, 0);
+			switch (c) {
+			case '"':
+			case '\\':
+				break;
+			default:
+				error = GIT_EOBJCORRUPTED;
+				goto out;
+			}
+		default:
+			break;
+		}
+
+		subsection[pos++] = c;
+	} while ((c = cfg_getchar(cfg, 0)) != ']');
+
+	subsection[pos] = '\0';
+
+	if (cfg_getchar(cfg, 0) != '\n'){
+		error = GIT_EOBJCORRUPTED;
+		goto out;
+	}
+
+	total_len = strlen(base_name) + strlen(subsection) + 2;
+	*section_name = git__malloc(total_len);
+	if (*section_name == NULL) {
+		error = GIT_ENOMEM;
+		goto out;
+	}
+
+	sprintf(*section_name, "%s %s", base_name, subsection);
+	strntolower(*section_name, strchr(*section_name, ' ') - *section_name);
+
+ out:
+	free(subsection);
+
+	return error;
 }
 
 static int parse_section_header(git_config *cfg, char **section_out, const char *line)
@@ -563,8 +733,10 @@ static int parse_section_header(git_config *cfg, char **section_out, const char 
 		}
 
 		if (isspace(c)){
-			*section_out = parse_section_header_ext(name, cfg);
-			return GIT_SUCCESS;
+			name[name_length] = '\0';
+			error = parse_section_header_ext(cfg, name, line, section_out);
+			free(name);
+			return error;
 		}
 
 		if (!config_keychar(c) && c != '.'){
@@ -672,6 +844,9 @@ static int config_parse(git_config *cfg_file)
 {
 	int error = GIT_SUCCESS;
 	char *current_section = NULL;
+	char *var_name;
+	char *var_value;
+	char *full_name;
 
 	/* Initialise the reading position */
 	cfg_file->reader.read_ptr = cfg_file->reader.buffer.data;
@@ -699,8 +874,25 @@ static int config_parse(git_config *cfg_file)
 			break;
 
 		default: /* assume variable declaration */
-			error = parse_variable(cfg_file, current_section, line);
+			error = parse_variable(line, &var_name, &var_value);
 			cfg_consume_line(cfg_file);
+
+			if (error < GIT_SUCCESS)
+				break;
+
+			full_name = build_varname(current_section, var_name);
+			if (full_name == NULL) {
+				error = GIT_ENOMEM;
+				free(var_name);
+				free(var_value);
+				break;
+			}
+
+			config_set(cfg_file, full_name, var_value);
+			free(var_name);
+			free(var_value);
+			free(full_name);
+
 			break;
 		}
 
@@ -713,11 +905,9 @@ static int config_parse(git_config *cfg_file)
 	return error;
 }
 
-static int parse_variable(git_config *cfg, const char *section_name, const char *line)
+static int parse_variable(const char *line, char **var_name, char **var_value)
 {
-	int error = GIT_SUCCESS;
-	int has_value = 1;
-	char *varname;
+	char *tmp;
 
 	const char *var_end = NULL;
 	const char *value_start = NULL;
@@ -743,15 +933,23 @@ static int parse_variable(git_config *cfg, const char *section_name, const char 
 			goto error;
 	}
 
-	varname = build_varname(section_name, line, var_end - line + 1);
-	if(varname == NULL)
+	tmp = strndup(line, var_end - line + 1);
+	if (tmp == NULL)
 		return GIT_ENOMEM;
 
-	config_set(cfg, varname, value_start);
+	*var_name = tmp;
+
+	if (value_start != NULL) {
+		tmp = strdup(value_start);
+		if (tmp == NULL) {
+			free(*var_name);
+			return GIT_ENOMEM;
+		}
 
-	free(varname);
+		*var_value = tmp;
+	}
 
-	return error;
+	return GIT_SUCCESS;
 
 error:
 	return GIT_EOBJCORRUPTED;