/*
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2,
 * as published by the Free Software Foundation.
 *
 * In addition to the permissions in the GNU General Public License,
 * the authors give you unlimited permission to link the compiled
 * version of this file into combinations with other programs,
 * and to distribute those combinations without any restriction
 * coming from the use of this file.  (The General Public License
 * restrictions do apply in other respects; for example, they cover
 * modification of the file, and distribution when not linked into
 * a combined executable.)
 *
 * This file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING.  If not, write to
 * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#include "common.h"
#include "fileops.h"
#include "hashtable.h"
#include "config.h"

#include <ctype.h>

/**********************
 * Forward declarations
 ***********************/
static int config_parse(git_config *cfg_file);
static int parse_variable(git_config *cfg, char **var_name, char **var_value);
void git_config_free(git_config *cfg);

static void cvar_free(git_cvar *var)
{
	if (var == NULL)
		return;

	free(var->name);
	free(var->value);
	free(var);
}

static void cvar_list_free(git_cvar_list *list)
{
	git_cvar *cur;

	while (!CVAR_LIST_EMPTY(list)) {
		cur = CVAR_LIST_HEAD(list);
		CVAR_LIST_REMOVE_HEAD(list);
		cvar_free(cur);
	}
}

/*
 * The order is important. 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))
		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
	 */
	return !strcasecmp(input_dot, local_dot);
}

static git_cvar *cvar_list_find(git_cvar_list *list, const char *name)
{
	git_cvar *iter;

	CVAR_LIST_FOREACH (list, iter) {
		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 git__strntolower(char *str, int len)
{
	int i;

	for (i = 0; i < len; ++i) {
		str[i] = tolower(str[i]);
	}
}

void git__strtolower(char *str)
{
	git__strntolower(str, strlen(str));
}

int git_config_open(git_config **cfg_out, const char *path)
{
	git_config *cfg;
	int error = GIT_SUCCESS;

	assert(cfg_out && path);

	cfg = git__malloc(sizeof(git_config));
	if (cfg == NULL)
		return GIT_ENOMEM;

	memset(cfg, 0x0, sizeof(git_config));

	cfg->file_path = git__strdup(path);
	if (cfg->file_path == NULL) {
		error = GIT_ENOMEM;
		goto cleanup;
	}

	error = gitfo_read_file(&cfg->reader.buffer, cfg->file_path);
	if(error < GIT_SUCCESS)
		goto cleanup;

	error = config_parse(cfg);
	if (error < GIT_SUCCESS)
		goto cleanup;
	else
		*cfg_out = cfg;

	gitfo_free_buf(&cfg->reader.buffer);

	return error;

 cleanup:
	cvar_list_free(&cfg->var_list);
	if (cfg->file_path)
		free(cfg->file_path);
	gitfo_free_buf(&cfg->reader.buffer);
	free(cfg);

	return error;
}

void git_config_free(git_config *cfg)
{
	if (cfg == NULL)
		return;

	free(cfg->file_path);
	cvar_list_free(&cfg->var_list);

	free(cfg);
}

/*
 * Loop over all the variables
 */

int git_config_foreach(git_config *cfg, int (*fn)(const char *, void *), void *data)
{
	int ret = GIT_SUCCESS;
	git_cvar *var;
	char *normalized;

	CVAR_LIST_FOREACH(&cfg->var_list, var) {
		ret = cvar_name_normalize(var->name, &normalized);
		if (ret < GIT_SUCCESS)
			return ret;

		ret = fn(normalized, data);
		free(normalized);
		if (ret)
			break;
	}

	return ret;
}

/**************
 * Setters
 **************/

/*
 * Internal function to actually set the string value of a variable
 */
static int config_set(git_config *cfg, const char *name, const char *value)
{
	git_cvar *var = NULL;
	git_cvar *existing = NULL;
	int error = GIT_SUCCESS;

	/*
	 * If it already exists, we just need to update its value.
	 */
	existing = cvar_list_find(&cfg->var_list, name);
	if (existing != NULL) {
		char *tmp = value ? git__strdup(value) : NULL;
		if (tmp == NULL && value != NULL)
			return GIT_ENOMEM;

		free(existing->value);
		existing->value = tmp;

		return GIT_SUCCESS;
	}

	/*
	 * Otherwise, create it and stick it at the end of the queue.
	 */

	var = git__malloc(sizeof(git_cvar));
	if (var == NULL)
		return GIT_ENOMEM;

	memset(var, 0x0, sizeof(git_cvar));

	var->name = git__strdup(name);
	if (var->name == NULL) {
		error = GIT_ENOMEM;
		goto out;
	}

	var->value = value ? git__strdup(value) : NULL;
	if (var->value == NULL && value != NULL) {
		error = GIT_ENOMEM;
		goto out;
	}

	CVAR_LIST_APPEND(&cfg->var_list, var);

 out:
	if (error < GIT_SUCCESS)
		cvar_free(var);

	return error;
}

int git_config_set_long(git_config *cfg, const char *name, long int value)
{
	char str_value[5]; /* Most numbers should fit in here */
	int buf_len = sizeof(str_value), ret;
	char *help_buf = NULL;

	if ((ret = snprintf(str_value, buf_len, "%ld", value)) >= buf_len - 1){
		/* The number is too large, we need to allocate more memory */
		buf_len = ret + 1;
		help_buf = git__malloc(buf_len);
		snprintf(help_buf, buf_len, "%ld", value);
		ret = config_set(cfg, name, help_buf);
		free(help_buf);
	} else {
		ret = config_set(cfg, name, str_value);
	}

	return ret;
}

int git_config_set_int(git_config *cfg, const char *name, int value)
{
	return git_config_set_long(cfg, name, value);
}

int git_config_set_bool(git_config *cfg, const char *name, int value)
{
	const char *str_value;

	if (value == 0)
		str_value = "false";
	else
		str_value = "true";

	return config_set(cfg, name, str_value);
}

int git_config_set_string(git_config *cfg, const char *name, const char *value)
{
	return config_set(cfg, name, value);
}

/***********
 * Getters
 ***********/

/*
 * Internal function that actually gets the value in string form
 */
static int config_get(git_config *cfg, const char *name, const char **out)
{
	git_cvar *var;
	int error = GIT_SUCCESS;

	var = cvar_list_find(&cfg->var_list, name);

	if (var == NULL)
		return GIT_ENOTFOUND;

	*out = var->value;

	return error;
}

int git_config_get_long(git_config *cfg, const char *name, long int *out)
{
	const char *value, *num_end;
	int ret;
	long int num;

	ret = config_get(cfg, name, &value);
	if (ret < GIT_SUCCESS)
		return ret;

	ret = git__strtol32(&num, value, &num_end, 0);
	if (ret < GIT_SUCCESS)
		return ret;

	switch (*num_end) {
	case '\0':
		break;
	case 'k':
	case 'K':
		num *= 1024;
		break;
	case 'm':
	case 'M':
		num *= 1024 * 1024;
		break;
	case 'g':
	case 'G':
		num *= 1024 * 1024 * 1024;
		break;
	default:
		return GIT_EINVALIDTYPE;
	}

	*out = num;

	return GIT_SUCCESS;
}

int git_config_get_int(git_config *cfg, const char *name, int *out)
{
	long int tmp;
	int ret;

	ret = git_config_get_long(cfg, name, &tmp);

	*out = (int) tmp;

	return ret;
}

int git_config_get_bool(git_config *cfg, const char *name, int *out)
{
	const char *value;
	int error = GIT_SUCCESS;

	error = config_get(cfg, name, &value);
	if (error < GIT_SUCCESS)
		return error;

	/* A missing value means true */
	if (value == NULL) {
		*out = 1;
		return GIT_SUCCESS;
	}

	if (!strcasecmp(value, "true") ||
		!strcasecmp(value, "yes") ||
		!strcasecmp(value, "on")) {
		*out = 1;
		return GIT_SUCCESS;
	}
	if (!strcasecmp(value, "false") ||
		!strcasecmp(value, "no") ||
		!strcasecmp(value, "off")) {
		*out = 0;
		return GIT_SUCCESS;
	}

	/* Try to parse it as an integer */
	error = git_config_get_int(cfg, name, out);
	if (error == GIT_SUCCESS)
		*out = !!(*out);

	return error;
}

int git_config_get_string(git_config *cfg, const char *name, const char **out)
{
	return config_get(cfg, name, out);
}

static int cfg_getchar_raw(git_config *cfg)
{
	int c;

	c = *cfg->reader.read_ptr++;

	/*
	Win 32 line breaks: if we find a \r\n sequence,
	return only the \n as a newline
	*/
	if (c == '\r' && *cfg->reader.read_ptr == '\n') {
		cfg->reader.read_ptr++;
		c = '\n';
	}

	if (c == '\n')
		cfg->reader.line_number++;

	if (c == 0) {
		cfg->reader.eof = 1;
		c = '\n';
	}

	return c;
}

#define SKIP_WHITESPACE (1 << 1)
#define SKIP_COMMENTS (1 << 2)

static int cfg_getchar(git_config *cfg_file, int flags)
{
	const int skip_whitespace = (flags & SKIP_WHITESPACE);
	const int skip_comments = (flags & SKIP_COMMENTS);
	int c;

	assert(cfg_file->reader.read_ptr);

	do c = cfg_getchar_raw(cfg_file);
	while (skip_whitespace && isspace(c));

	if (skip_comments && (c == '#' || c == ';')) {
		do c = cfg_getchar_raw(cfg_file);
		while (c != '\n');
	}

	return c;
}

/*
 * Read the next char, but don't move the reading pointer.
 */
static int cfg_peek(git_config *cfg, int flags)
{
	void *old_read_ptr;
	int old_lineno, old_eof;
	int ret;

	assert(cfg->reader.read_ptr);

	old_read_ptr = cfg->reader.read_ptr;
	old_lineno = cfg->reader.line_number;
	old_eof = cfg->reader.eof;

	ret = cfg_getchar(cfg, flags);

	cfg->reader.read_ptr = old_read_ptr;
	cfg->reader.line_number = old_lineno;
	cfg->reader.eof = old_eof;

	return ret;
}

static const char *LINEBREAK_UNIX = "\\\n";
static const char *LINEBREAK_WIN32 = "\\\r\n";

static int is_linebreak(const char *pos)
{
	return	memcmp(pos - 1, LINEBREAK_UNIX, sizeof(LINEBREAK_UNIX)) == 0 ||
			memcmp(pos - 2, LINEBREAK_WIN32, sizeof(LINEBREAK_WIN32)) == 0;
}

/*
 * Read and consume a line, returning it in newly-allocated memory.
 */
static char *cfg_readline(git_config *cfg)
{
	char *line = NULL;
	char *line_src, *line_end;
	int line_len;

	line_src = cfg->reader.read_ptr;
    line_end = strchr(line_src, '\n');

    /* no newline at EOF */
	if (line_end == NULL)
		line_end = strchr(line_src, 0);
	else
		while (is_linebreak(line_end))
			line_end = strchr(line_end + 1, '\n');


	while (line_src < line_end && isspace(*line_src))
		line_src++;

	line = (char *)git__malloc((size_t)(line_end - line_src) + 1);
	if (line == NULL)
		return NULL;

	line_len = 0;
	while (line_src < line_end) {

		if (memcmp(line_src, LINEBREAK_UNIX, sizeof(LINEBREAK_UNIX)) == 0) {
			line_src += sizeof(LINEBREAK_UNIX);
			continue;
		}

		if (memcmp(line_src, LINEBREAK_WIN32, sizeof(LINEBREAK_WIN32)) == 0) {
			line_src += sizeof(LINEBREAK_WIN32);
			continue;
		}

		line[line_len++] = *line_src++;
	}

	line[line_len] = '\0';

	while (--line_len >= 0 && isspace(line[line_len]))
		line[line_len] = '\0';

	if (*line_end == '\n')
		line_end++;

	if (*line_end == '\0')
		cfg->reader.eof = 1;

	cfg->reader.line_number++;
	cfg->reader.read_ptr = line_end;

	return line;
}

/*
 * Consume a line, without storing it anywhere
 */
void cfg_consume_line(git_config *cfg)
{
	char *line_start, *line_end;
	int len;

	line_start = cfg->reader.read_ptr;
	line_end = strchr(line_start, '\n');
	/* No newline at EOF */
	if(line_end == NULL){
		line_end = strchr(line_start, '\0');
	}

	len = line_end - line_start;

	if (*line_end == '\n')
		line_end++;

	if (*line_end == '\0')
		cfg->reader.eof = 1;

	cfg->reader.line_number++;
	cfg->reader.read_ptr = line_end;
}

static inline int config_keychar(int c)
{
	return isalnum(c) || c == '-';
}

/*
 * Returns $section.$name, using only name_len chars from the name,
 * which is useful so we don't have to copy the variable name
 * twice. The name of the variable is set to lowercase.
 * Don't forget to free the buffer.
 */
static char *build_varname(const char *section, const char *name)
{
	char *varname;
	int section_len, ret;
	int name_len;
	size_t total_len;

	name_len = strlen(name);
	section_len = strlen(section);
	total_len = section_len + name_len + 2;
	varname = malloc(total_len);
	if(varname == NULL)
		return NULL;

	ret = snprintf(varname, total_len, "%s.%s", section, name);
	if (ret >= 0) { /* lowercase from the last dot onwards */
		char *dot = strrchr(varname, '.');
		if (dot != NULL)
			git__strtolower(dot);
	}

	return varname;
}

static int parse_section_header_ext(const char *line, const char *base_name, char **section_name)
{
	int buf_len, total_len, pos, rpos;
	int c, ret;
	char *subsection, *first_quote, *last_quote;
	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.
	 */

	first_quote = strchr(line, '"');
	last_quote = strrchr(line, '"');

	if (last_quote - first_quote == 0)
		return GIT_EOBJCORRUPTED;

	buf_len = last_quote - first_quote + 2;

	subsection = git__malloc(buf_len + 2);
	if (subsection == NULL)
		return GIT_ENOMEM;

	pos = 0;
	rpos = 0;
	quote_marks = 0;

	line = first_quote;
	c = line[rpos++];

	/*
	 * 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 = line[rpos++];
			switch (c) {
			case '"':
			case '\\':
				break;
			default:
				error = GIT_EOBJCORRUPTED;
				goto out;
			}
		default:
			break;
		}

		subsection[pos++] = c;
	} while ((c = line[rpos++]) != ']');

	subsection[pos] = '\0';

	total_len = strlen(base_name) + strlen(subsection) + 2;
	*section_name = git__malloc(total_len);
	if (*section_name == NULL) {
		error = GIT_ENOMEM;
		goto out;
	}

	ret = snprintf(*section_name, total_len, "%s %s", base_name, subsection);
	if (ret >= total_len) {
		/* If this fails, we've checked the length wrong */
		error = GIT_ERROR;
		goto out;
	} else if (ret < 0) {
		error = GIT_EOSERR;
		goto out;
	}

	git__strntolower(*section_name, strchr(*section_name, ' ') - *section_name);

 out:
	free(subsection);

	return error;
}

static int parse_section_header(git_config *cfg, char **section_out)
{
	char *name, *name_end;
	int name_length, c, pos;
	int error = GIT_SUCCESS;
	char *line;

	line = cfg_readline(cfg);
	if (line == NULL)
		return GIT_ENOMEM;

	/* find the end of the variable's name */
	name_end = strchr(line, ']');
	if (name_end == NULL)
		return GIT_EOBJCORRUPTED;

	name = (char *)git__malloc((size_t)(name_end - line) + 1);
	if (name == NULL)
		return GIT_EOBJCORRUPTED;

	name_length = 0;
	pos = 0;

	/* Make sure we were given a section header */
	c = line[pos++];
	if (c != '[') {
		error = GIT_EOBJCORRUPTED;
		goto error;
	}

	c = line[pos++];

	do {
		if (cfg->reader.eof){
			error = GIT_EOBJCORRUPTED;
			goto error;
		}

		if (isspace(c)){
			name[name_length] = '\0';
			error = parse_section_header_ext(line, name, section_out);
			free(line);
			free(name);
			return error;
		}

		if (!config_keychar(c) && c != '.') {
			error = GIT_EOBJCORRUPTED;
			goto error;
		}

		name[name_length++] = tolower(c);

	} while ((c = line[pos++]) != ']');

	name[name_length] = 0;
	free(line);
	git__strtolower(name);
	*section_out = name;
	return GIT_SUCCESS;

error:
	free(line);
	free(name);
	return error;
}

static int skip_bom(git_config *cfg)
{
	static const unsigned char *utf8_bom = "\xef\xbb\xbf";

	if (memcmp(cfg->reader.read_ptr, utf8_bom, sizeof(utf8_bom)) == 0)
		cfg->reader.read_ptr += sizeof(utf8_bom);

	/*  TODO: the reference implementation does pretty stupid
		shit with the BoM
	*/

	return GIT_SUCCESS;
}

/*
	(* basic types *)
	digit = "0".."9"
	integer = digit { digit }
	alphabet = "a".."z" + "A" .. "Z"

	section_char = alphabet | "." | "-"
	extension_char = (* any character except newline *)
	any_char = (* any character *)
	variable_char = "alphabet" | "-"


	(* actual grammar *)
	config = { section }

	section = header { definition }

	header = "[" section [subsection | subsection_ext] "]"

	subsection = "." section
	subsection_ext = "\"" extension "\""

	section = section_char { section_char }
	extension = extension_char { extension_char }

	definition = variable_name ["=" variable_value] "\n"

	variable_name = variable_char { variable_char }
	variable_value = string | boolean | integer

	string = quoted_string | plain_string
	quoted_string = "\"" plain_string "\""
	plain_string = { any_char }

	boolean = boolean_true | boolean_false
	boolean_true = "yes" | "1" | "true" | "on"
	boolean_false = "no" | "0" | "false" | "off"
*/

static void strip_comments(char *line)
{
	int quote_count = 0;
	char *ptr;

	for (ptr = line; *ptr; ++ptr) {
		if (ptr[0] == '"' && ptr > line && ptr[-1] != '\\')
			quote_count++;

		if ((ptr[0] == ';' || ptr[0] == '#') && (quote_count % 2) == 0) {
			ptr[0] = '\0';
			break;
		}
	}

	if (isspace(ptr[-1])) {
		/* TODO skip whitespace */
	}
}

static int config_parse(git_config *cfg_file)
{
	int error = GIT_SUCCESS, c;
	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;
	cfg_file->reader.eof = 0;

	skip_bom(cfg_file);

	while (error == GIT_SUCCESS && !cfg_file->reader.eof) {

		c = cfg_peek(cfg_file, SKIP_WHITESPACE);

		switch (c) {
		case '\0': /* We've arrived at the end of the file */
			break;

		case '[': /* section header, new section begins */
			free(current_section);
			error = parse_section_header(cfg_file, &current_section);
			break;

		case ';':
		case '#':
			cfg_consume_line(cfg_file);
			break;

		default: /* assume variable declaration */
			error = parse_variable(cfg_file, &var_name, &var_value);

			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;
		}
	}

	if (current_section)
		free(current_section);

	return error;
}

static int is_multiline_var(const char *str)
{
	char *end = strrchr(str, '\0') - 1;

	while (isspace(*end))
		--end;

	return *end == '\\';
}

static int parse_multiline_variable(git_config *cfg, const char *first, char **out)
{
	char *line = NULL, *end;
	int error = GIT_SUCCESS, len, ret;
	char *buf;

	/* Check that the next line exists */
	line = cfg_readline(cfg);
	if (line == NULL)
		return GIT_ENOMEM;

	/* We've reached the end of the file, there is input missing */
	if (line[0] == '\0') {
		error = GIT_EOBJCORRUPTED;
		goto out;
	}

	strip_comments(line);

	/* If it was just a comment, pretend it didn't exist */
	if (line[0] == '\0') {
		error = parse_multiline_variable(cfg, first, out);
		goto out;
	}

	/* Find the continuation character '\' and strip the whitespace */
	end = strrchr(first, '\\');
	while (isspace(end[-1]))
		--end;

	*end = '\0'; /* Terminate the string here */

	len = strlen(first) + strlen(line) + 2;
	buf = git__malloc(len);
	if (buf == NULL) {
		error = GIT_ENOMEM;
		goto out;
	}

	ret = snprintf(buf, len, "%s %s", first, line);
	if (ret < 0) {
		error = GIT_EOSERR;
		free(buf);
		goto out;
	}

	/*
	 * If we need to continue reading the next line, pretend
	 * everything we've read up to now was in one line and call
	 * ourselves.
	 */
	if (is_multiline_var(buf)) {
		char *final_val;
		error = parse_multiline_variable(cfg, buf, &final_val);
		free(buf);
		buf = final_val;
	}

	*out = buf;

 out:
	free(line);
	return error;
}

static int parse_variable(git_config *cfg, char **var_name, char **var_value)
{
	char *tmp;
	int error = GIT_SUCCESS;
	const char *var_end = NULL;
	const char *value_start = NULL;
	char *line;

	line = cfg_readline(cfg);
	if (line == NULL)
		return GIT_ENOMEM;

	strip_comments(line);

	var_end = strchr(line, '=');

	if (var_end == NULL)
		var_end = strchr(line, '\0');
	else
		value_start = var_end + 1;

	if (isspace(var_end[-1])) {
		do var_end--;
		while (isspace(var_end[0]));
	}

	tmp = strndup(line, var_end - line + 1);
	if (tmp == NULL) {
		error = GIT_ENOMEM;
		goto out;
	}

	*var_name = tmp;

	/*
	 * Now, let's try to parse the value
	 */
	if (value_start != NULL) {

		while (isspace(value_start[0]))
			value_start++;

		if (value_start[0] == '\0')
			goto out;

		if (is_multiline_var(value_start)) {
			error = parse_multiline_variable(cfg, value_start, var_value);
			if (error < GIT_SUCCESS)
				free(*var_name);
			goto out;
		}

		tmp = strdup(value_start);
		if (tmp == NULL) {
			free(*var_name);
			error = GIT_ENOMEM;
			goto out;
		}

		*var_value = tmp;
	} else {
		/* If thre is no value, boolean true is assumed */
		*var_value = NULL;
	}

 out:
	free(line);
	return error;
}
