Commit Diff


commit - 63c5ca5de411be54e75480b0efec04014ffab46e
commit + 1d126e2d216c06991bbc586d796b3d002b2bd7d6
blob - 4d887779da3647bcf38fded4c8db1d120fb3aaaf
blob + 8c1fca65ca8a824451198211eb32ae34620b0f8f
--- got/Makefile
+++ got/Makefile
@@ -8,7 +8,7 @@ SRCS=		got.c blame.c commit_graph.c delta.c diff.c \
 		object_idset.c object_parse.c opentemp.c path.c pack.c \
 		privsep.c reference.c repository.c sha1.c worktree.c \
 		inflate.c buf.c worklist.c rcsutil.c diff3.c lockfile.c \
-		deflate.c object_create.c
+		deflate.c object_create.c gitconfig.c
 MAN =		${PROG}.1 got-worktree.5 git-repository.5
 
 CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib
blob - /dev/null
blob + 754cbc5403b1c92e536fca4f373dea3a300c73d4 (mode 644)
--- /dev/null
+++ lib/gitconfig.c
@@ -0,0 +1,669 @@
+/* $OpenBSD: conf.c,v 1.107 2017/10/27 08:29:32 mpi Exp $	 */
+/* $EOM: conf.c,v 1.48 2000/12/04 02:04:29 angelos Exp $	 */
+
+/*
+ * Copyright (c) 1998, 1999, 2000, 2001 Niklas Hallqvist.  All rights reserved.
+ * Copyright (c) 2000, 2001, 2002 Håkan Olsson.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "got_error.h"
+
+#include "got_lib_gitconfig.h"
+
+#ifndef nitems
+#define nitems(_a) (sizeof(_a) / sizeof((_a)[0]))
+#endif
+
+#define LOG_MISC	0
+#define LOG_REPORT	1
+#ifdef GITCONFIG_DEBUG
+#define LOG_DBG(x) log_debug x
+#else
+#define LOG_DBG(x)
+#endif
+
+#define log_print printf
+#define log_error printf
+
+#ifdef GITCONFIG_DEBUG
+static void
+log_debug(int cls, int level, const char *fmt, ...)
+{
+	va_list         ap;
+
+	va_start(ap, fmt);
+	vfprintf(stderr, fmt, ap);
+	va_end(ap);
+}
+#endif
+
+struct got_gitconfig_trans {
+	TAILQ_ENTRY(got_gitconfig_trans) link;
+	int	 trans;
+	enum got_gitconfig_op {
+		CONF_SET, CONF_REMOVE, CONF_REMOVE_SECTION
+	}	 op;
+	char	*section;
+	char	*tag;
+	char	*value;
+	int	 override;
+	int	 is_default;
+};
+
+TAILQ_HEAD(got_gitconfig_trans_head, got_gitconfig_trans);
+
+struct got_gitconfig_binding {
+	LIST_ENTRY(got_gitconfig_binding) link;
+	char	*section;
+	char	*tag;
+	char	*value;
+	int	 is_default;
+};
+
+LIST_HEAD(got_gitconfig_bindings, got_gitconfig_binding);
+
+struct got_gitconfig {
+	struct got_gitconfig_bindings bindings[256];
+	struct got_gitconfig_trans_head trans_queue;
+	char	*addr;
+	int	seq;
+};
+
+static __inline__ u_int8_t
+conf_hash(char *s)
+{
+	u_int8_t hash = 0;
+
+	while (*s) {
+		hash = ((hash << 1) | (hash >> 7)) ^ tolower((unsigned char)*s);
+		s++;
+	}
+	return hash;
+}
+
+/*
+ * Insert a tag-value combination from LINE (the equal sign is at POS)
+ */
+static int
+conf_remove_now(struct got_gitconfig *conf, char *section, char *tag)
+{
+	struct got_gitconfig_binding *cb, *next;
+
+	for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
+	    cb = next) {
+		next = LIST_NEXT(cb, link);
+		if (strcasecmp(cb->section, section) == 0 &&
+		    strcasecmp(cb->tag, tag) == 0) {
+			LIST_REMOVE(cb, link);
+			LOG_DBG((LOG_MISC, 95, "[%s]:%s->%s removed", section,
+			    tag, cb->value));
+			free(cb->section);
+			free(cb->tag);
+			free(cb->value);
+			free(cb);
+			return 0;
+		}
+	}
+	return 1;
+}
+
+static int
+conf_remove_section_now(struct got_gitconfig *conf, char *section)
+{
+	struct got_gitconfig_binding *cb, *next;
+	int	unseen = 1;
+
+	for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
+	    cb = next) {
+		next = LIST_NEXT(cb, link);
+		if (strcasecmp(cb->section, section) == 0) {
+			unseen = 0;
+			LIST_REMOVE(cb, link);
+			LOG_DBG((LOG_MISC, 95, "[%s]:%s->%s removed", section,
+			    cb->tag, cb->value));
+			free(cb->section);
+			free(cb->tag);
+			free(cb->value);
+			free(cb);
+		}
+	}
+	return unseen;
+}
+
+/*
+ * Insert a tag-value combination from LINE (the equal sign is at POS)
+ * into SECTION of our configuration database.
+ */
+static int
+conf_set_now(struct got_gitconfig *conf, char *section, char *tag,
+    char *value, int override, int is_default)
+{
+	struct got_gitconfig_binding *node = 0;
+
+	if (override)
+		conf_remove_now(conf, section, tag);
+	else if (got_gitconfig_get_str(conf, section, tag)) {
+		if (!is_default)
+			log_print("conf_set_now: duplicate tag [%s]:%s, "
+			    "ignoring...\n", section, tag);
+		return 1;
+	}
+	node = calloc(1, sizeof *node);
+	if (!node) {
+		log_error("conf_set_now: calloc (1, %lu) failed",
+		    (unsigned long)sizeof *node);
+		return 1;
+	}
+	node->section = node->tag = node->value = NULL;
+	if ((node->section = strdup(section)) == NULL)
+		goto fail;
+	if ((node->tag = strdup(tag)) == NULL)
+		goto fail;
+	if ((node->value = strdup(value)) == NULL)
+		goto fail;
+	node->is_default = is_default;
+
+	LIST_INSERT_HEAD(&conf->bindings[conf_hash(section)], node, link);
+	LOG_DBG((LOG_MISC, 95, "conf_set_now: [%s]:%s->%s", node->section,
+	    node->tag, node->value));
+	return 0;
+fail:
+	free(node->value);
+	free(node->tag);
+	free(node->section);
+	free(node);
+	return 1;
+}
+
+/*
+ * Parse the line LINE of SZ bytes.  Skip Comments, recognize section
+ * headers and feed tag-value pairs into our configuration database.
+ */
+static const struct got_error *
+conf_parse_line(char **section, struct got_gitconfig *conf, int trans,
+    char *line, int ln, size_t sz)
+{
+	char	*val;
+	size_t	 i;
+	int	 j;
+
+	/* Lines starting with '#' or ';' are comments.  */
+	if (*line == '#' || *line == ';')
+		return NULL;
+
+	/* '[section]' parsing...  */
+	if (*line == '[') {
+		for (i = 1; i < sz; i++)
+			if (line[i] == ']')
+				break;
+		free(*section);
+		if (i == sz) {
+			log_print("conf_parse_line: %d:"
+			    "unmatched ']', ignoring until next section", ln);
+			*section = NULL;
+			return NULL;
+		}
+		*section = malloc(i);
+		if (*section == NULL)
+			return got_error_from_errno("malloc");
+		strlcpy(*section, line + 1, i);
+		return NULL;
+	}
+	while (isspace((unsigned char)*line))
+		line++;
+
+	/* Deal with assignments.  */
+	for (i = 0; i < sz; i++)
+		if (line[i] == '=') {
+			/* If no section, we are ignoring the lines.  */
+			if (!*section) {
+				log_print("conf_parse_line: %d: ignoring line "
+				    "due to no section", ln);
+				return NULL;
+			}
+			line[strcspn(line, " \t=")] = '\0';
+			val = line + i + 1 + strspn(line + i + 1, " \t");
+			/* Skip trailing whitespace, if any */
+			for (j = sz - (val - line) - 1; j > 0 &&
+			    isspace((unsigned char)val[j]); j--)
+				val[j] = '\0';
+			/* XXX Perhaps should we not ignore errors?  */
+			got_gitconfig_set(conf, trans, *section, line, val,
+			    0, 0);
+			return NULL;
+		}
+	/* Other non-empty lines are weird.  */
+	i = strspn(line, " \t");
+	if (line[i])
+		log_print("conf_parse_line: %d: syntax error", ln);
+
+	return NULL;
+}
+
+/* Parse the mapped configuration file.  */
+static const struct got_error *
+conf_parse(struct got_gitconfig *conf, int trans, char *buf, size_t sz)
+{
+	const struct got_error *err = NULL;
+	char	*cp = buf;
+	char	*bufend = buf + sz;
+	char	*line, *section = NULL;
+	int	ln = 1;
+
+	line = cp;
+	while (cp < bufend) {
+		if (*cp == '\n') {
+			/* Check for escaped newlines.  */
+			if (cp > buf && *(cp - 1) == '\\')
+				*(cp - 1) = *cp = ' ';
+			else {
+				*cp = '\0';
+				err = conf_parse_line(&section, conf, trans,
+				    line, ln, cp - line);
+				if (err)
+					return err;
+				line = cp + 1;
+			}
+			ln++;
+		}
+		cp++;
+	}
+	if (cp != line)
+		log_print("conf_parse: last line unterminated, ignored.");
+	return NULL;
+}
+
+const struct got_error *
+got_gitconfig_open(struct got_gitconfig **conf, const char *gitconfig_path)
+{
+	unsigned int i;
+
+	*conf = calloc(1, sizeof(**conf));
+	if (*conf == NULL)
+		return got_error_from_errno("malloc");
+
+	for (i = 0; i < nitems((*conf)->bindings); i++)
+		LIST_INIT(&(*conf)->bindings[i]);
+	TAILQ_INIT(&(*conf)->trans_queue);
+	return got_gitconfig_reinit(*conf, gitconfig_path);
+}
+
+static void
+conf_clear(struct got_gitconfig *conf)
+{
+	struct got_gitconfig_binding *cb;
+	int i;
+
+	if (conf->addr) {
+		for (i = 0; i < nitems(conf->bindings); i++)
+			for (cb = LIST_FIRST(&conf->bindings[i]); cb;
+			    cb = LIST_FIRST(&conf->bindings[i]))
+				conf_remove_now(conf, cb->section, cb->tag);
+		free(conf->addr);
+		conf->addr = NULL;
+	}
+}
+
+/* Execute all queued operations for this transaction.  Cleanup.  */
+static int
+conf_end(struct got_gitconfig *conf, int transaction, int commit)
+{
+	struct got_gitconfig_trans *node, *next;
+
+	for (node = TAILQ_FIRST(&conf->trans_queue); node; node = next) {
+		next = TAILQ_NEXT(node, link);
+		if (node->trans == transaction) {
+			if (commit)
+				switch (node->op) {
+				case CONF_SET:
+					conf_set_now(conf, node->section,
+					    node->tag, node->value,
+					    node->override, node->is_default);
+					break;
+				case CONF_REMOVE:
+					conf_remove_now(conf, node->section,
+					    node->tag);
+					break;
+				case CONF_REMOVE_SECTION:
+					conf_remove_section_now(conf, node->section);
+					break;
+				default:
+					log_print("got_gitconfig_end: unknown "
+					    "operation: %d", node->op);
+				}
+			TAILQ_REMOVE(&conf->trans_queue, node, link);
+			free(node->section);
+			free(node->tag);
+			free(node->value);
+			free(node);
+		}
+	}
+	return 0;
+}
+
+
+void
+got_gitconfig_close(struct got_gitconfig *conf)
+{
+	conf_clear(conf);
+	free(conf);
+}
+
+static int
+conf_begin(struct got_gitconfig *conf)
+{
+	return ++conf->seq;
+}
+
+/* Open the config file and map it into our address space, then parse it.  */
+const struct got_error *
+got_gitconfig_reinit(struct got_gitconfig *conf, const char *gitconfig_path)
+{
+	const struct got_error *err = NULL;
+	int	 fd, trans;
+	size_t	 sz;
+	char	*new_conf_addr = 0;
+	struct stat st;
+
+	fd = open(gitconfig_path, O_RDONLY, 0);
+	if (fd == -1) {
+		if (errno != ENOENT)
+			return got_error_from_errno2("open", gitconfig_path);
+		return NULL;
+	}
+
+	if (fstat(fd, &st)) {
+		err = got_error_from_errno2("fstat", gitconfig_path);
+		goto fail;
+	}
+
+	sz = st.st_size;
+	new_conf_addr = malloc(sz);
+	if (new_conf_addr == NULL) {
+		err = got_error_from_errno("malloc");
+		goto fail;
+	}
+	/* XXX I assume short reads won't happen here.  */
+	if (read(fd, new_conf_addr, sz) != (int)sz) {
+		err = got_error_from_errno("read");
+		goto fail;
+	}
+	close(fd);
+	fd = -1;
+
+	trans = conf_begin(conf);
+
+	err = conf_parse(conf, trans, new_conf_addr, sz);
+	if (err)
+		goto fail;
+
+	/* Free potential existing configuration.  */
+	conf_clear(conf);
+	conf_end(conf, trans, 1);
+	conf->addr = new_conf_addr;
+	return NULL;
+
+fail:
+	free(new_conf_addr);
+	if (fd != -1)
+		close(fd);
+	return err;
+}
+
+/*
+ * Return the numeric value denoted by TAG in section SECTION or DEF
+ * if that tag does not exist.
+ */
+int
+got_gitconfig_get_num(struct got_gitconfig *conf, char *section, char *tag,
+    int def)
+{
+	char	*value = got_gitconfig_get_str(conf, section, tag);
+
+	if (value)
+		return atoi(value);
+	return def;
+}
+
+/* Validate X according to the range denoted by TAG in section SECTION.  */
+int
+got_gitconfig_match_num(struct got_gitconfig *conf, char *section, char *tag,
+    int x)
+{
+	char	*value = got_gitconfig_get_str(conf, section, tag);
+	int	 val, min, max, n;
+
+	if (!value)
+		return 0;
+	n = sscanf(value, "%d,%d:%d", &val, &min, &max);
+	switch (n) {
+	case 1:
+		LOG_DBG((LOG_MISC, 95, "got_gitconfig_match_num: %s:%s %d==%d?",
+		    section, tag, val, x));
+		return x == val;
+	case 3:
+		LOG_DBG((LOG_MISC, 95, "got_gitconfig_match_num: %s:%s %d<=%d<=%d?",
+		    section, tag, min, x, max));
+		return min <= x && max >= x;
+	default:
+		log_error("got_gitconfig_match_num: section %s tag %s: invalid number "
+		    "spec %s", section, tag, value);
+	}
+	return 0;
+}
+
+/* Return the string value denoted by TAG in section SECTION.  */
+char *
+got_gitconfig_get_str(struct got_gitconfig *conf, char *section, char *tag)
+{
+	struct got_gitconfig_binding *cb;
+
+	for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
+	    cb = LIST_NEXT(cb, link))
+		if (strcasecmp(section, cb->section) == 0 &&
+		    strcasecmp(tag, cb->tag) == 0) {
+			LOG_DBG((LOG_MISC, 95, "got_gitconfig_get_str: [%s]:%s->%s",
+			    section, tag, cb->value));
+			return cb->value;
+		}
+	LOG_DBG((LOG_MISC, 95,
+	    "got_gitconfig_get_str: configuration value not found [%s]:%s", section,
+	    tag));
+	return 0;
+}
+
+/*
+ * Build a list of string values out of the comma separated value denoted by
+ * TAG in SECTION.
+ */
+struct got_gitconfig_list *
+got_gitconfig_get_list(struct got_gitconfig *conf, char *section, char *tag)
+{
+	char	*liststr = 0, *p, *field, *t;
+	struct got_gitconfig_list *list = 0;
+	struct got_gitconfig_list_node *node = 0;
+
+	list = malloc(sizeof *list);
+	if (!list)
+		goto cleanup;
+	TAILQ_INIT(&list->fields);
+	list->cnt = 0;
+	liststr = got_gitconfig_get_str(conf, section, tag);
+	if (!liststr)
+		goto cleanup;
+	liststr = strdup(liststr);
+	if (!liststr)
+		goto cleanup;
+	p = liststr;
+	while ((field = strsep(&p, ",")) != NULL) {
+		/* Skip leading whitespace */
+		while (isspace((unsigned char)*field))
+			field++;
+		/* Skip trailing whitespace */
+		if (p)
+			for (t = p - 1; t > field && isspace((unsigned char)*t); t--)
+				*t = '\0';
+		if (*field == '\0') {
+			log_print("got_gitconfig_get_list: empty field, ignoring...");
+			continue;
+		}
+		list->cnt++;
+		node = calloc(1, sizeof *node);
+		if (!node)
+			goto cleanup;
+		node->field = strdup(field);
+		if (!node->field)
+			goto cleanup;
+		TAILQ_INSERT_TAIL(&list->fields, node, link);
+	}
+	free(liststr);
+	return list;
+
+cleanup:
+	free(node);
+	if (list)
+		got_gitconfig_free_list(list);
+	free(liststr);
+	return 0;
+}
+
+struct got_gitconfig_list *
+got_gitconfig_get_tag_list(struct got_gitconfig *conf, char *section)
+{
+	struct got_gitconfig_list *list = 0;
+	struct got_gitconfig_list_node *node = 0;
+	struct got_gitconfig_binding *cb;
+
+	list = malloc(sizeof *list);
+	if (!list)
+		goto cleanup;
+	TAILQ_INIT(&list->fields);
+	list->cnt = 0;
+	for (cb = LIST_FIRST(&conf->bindings[conf_hash(section)]); cb;
+	    cb = LIST_NEXT(cb, link))
+		if (strcasecmp(section, cb->section) == 0) {
+			list->cnt++;
+			node = calloc(1, sizeof *node);
+			if (!node)
+				goto cleanup;
+			node->field = strdup(cb->tag);
+			if (!node->field)
+				goto cleanup;
+			TAILQ_INSERT_TAIL(&list->fields, node, link);
+		}
+	return list;
+
+cleanup:
+	free(node);
+	if (list)
+		got_gitconfig_free_list(list);
+	return 0;
+}
+
+void
+got_gitconfig_free_list(struct got_gitconfig_list *list)
+{
+	struct got_gitconfig_list_node *node = TAILQ_FIRST(&list->fields);
+
+	while (node) {
+		TAILQ_REMOVE(&list->fields, node, link);
+		free(node->field);
+		free(node);
+		node = TAILQ_FIRST(&list->fields);
+	}
+	free(list);
+}
+
+static int
+got_gitconfig_trans_node(struct got_gitconfig *conf, int transaction,
+    enum got_gitconfig_op op, char *section, char *tag, char *value,
+    int override, int is_default)
+{
+	struct got_gitconfig_trans *node;
+
+	node = calloc(1, sizeof *node);
+	if (!node) {
+		log_error("got_gitconfig_trans_node: calloc (1, %lu) failed",
+		    (unsigned long)sizeof *node);
+		return 1;
+	}
+	node->trans = transaction;
+	node->op = op;
+	node->override = override;
+	node->is_default = is_default;
+	if (section && (node->section = strdup(section)) == NULL)
+		goto fail;
+	if (tag && (node->tag = strdup(tag)) == NULL)
+		goto fail;
+	if (value && (node->value = strdup(value)) == NULL)
+		goto fail;
+	TAILQ_INSERT_TAIL(&conf->trans_queue, node, link);
+	return 0;
+
+fail:
+	free(node->section);
+	free(node->tag);
+	free(node->value);
+	free(node);
+	return 1;
+}
+
+/* Queue a set operation.  */
+int
+got_gitconfig_set(struct got_gitconfig *conf, int transaction, char *section,
+    char *tag, char *value, int override, int is_default)
+{
+	return got_gitconfig_trans_node(conf, transaction, CONF_SET, section,
+	    tag, value, override, is_default);
+}
+
+/* Queue a remove operation.  */
+int
+got_gitconfig_remove(struct got_gitconfig *conf, int transaction,
+    char *section, char *tag)
+{
+	return got_gitconfig_trans_node(conf, transaction, CONF_REMOVE,
+	    section, tag, NULL, 0, 0);
+}
+
+/* Queue a remove section operation.  */
+int
+got_gitconfig_remove_section(struct got_gitconfig *conf, int transaction,
+    char *section)
+{
+	return got_gitconfig_trans_node(conf, transaction, CONF_REMOVE_SECTION,
+	    section, NULL, NULL, 0, 0);
+}
blob - /dev/null
blob + 828787b0e4bac8fc800346d343d9896bcd3e3801 (mode 644)
--- /dev/null
+++ lib/got_lib_gitconfig.h
@@ -0,0 +1,53 @@
+/* $OpenBSD: conf.h,v 1.34 2006/08/30 16:56:56 hshoexer Exp $	 */
+/* $EOM: conf.h,v 1.13 2000/09/18 00:01:47 ho Exp $	 */
+
+/*
+ * Copyright (c) 1998, 1999, 2001 Niklas Hallqvist.  All rights reserved.
+ * Copyright (c) 2000, 2003 Håkan Olsson.  All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+struct got_gitconfig_list_node {
+	TAILQ_ENTRY(got_gitconfig_list_node) link;
+	char	*field;
+};
+
+struct got_gitconfig_list {
+	size_t	cnt;
+	TAILQ_HEAD(got_gitconfig_list_fields_head, got_gitconfig_list_node) fields;
+};
+
+struct got_gitconfig;
+
+void     got_gitconfig_free_list(struct got_gitconfig_list *);
+struct got_gitconfig_list *got_gitconfig_get_list(struct got_gitconfig *, char *, char *);
+struct got_gitconfig_list *got_gitconfig_get_tag_list(struct got_gitconfig *, char *);
+int got_gitconfig_get_num(struct got_gitconfig *, char *, char *, int);
+char *got_gitconfig_get_str(struct got_gitconfig *, char *, char *);
+const struct got_error *got_gitconfig_open(struct got_gitconfig **,
+    const char *);
+void got_gitconfig_close(struct got_gitconfig *);
+int      got_gitconfig_match_num(struct got_gitconfig *, char *, char *, int);
+const struct got_error *got_gitconfig_reinit(struct got_gitconfig *, const char *);
+int      got_gitconfig_remove(struct got_gitconfig *, int, char *, char *);
+int      got_gitconfig_remove_section(struct got_gitconfig *, int, char *);
+int      got_gitconfig_set(struct got_gitconfig *, int, char *, char *, char *, int, int);
blob - 4379222e6238860a7965b41526f817c2614aa6e5
blob + 23ca2d585f593cbd7176990ce665928bea219089
--- lib/got_lib_repository.h
+++ lib/got_lib_repository.h
@@ -40,6 +40,9 @@ struct got_repository {
 	struct got_object_cache treecache;
 	struct got_object_cache commitcache;
 	struct got_object_cache tagcache;
+
+	/* Settings read from Git configuration files. */
+	struct got_gitconfig *gitconfig;
 };
 
 const struct got_error*got_repo_cache_object(struct got_repository *,
blob - eeffa65542fb24958cb041d67effcdf464713ded
blob + f7044b4650178445f7122537b90eeb272ddafe4b
--- lib/repository.c
+++ lib/repository.c
@@ -56,6 +56,7 @@
 #include "got_lib_sha1.h"
 #include "got_lib_object_cache.h"
 #include "got_lib_repository.h"
+#include "got_lib_gitconfig.h"
 
 #ifndef nitems
 #define nitems(_a) (sizeof(_a) / sizeof((_a)[0]))
@@ -67,6 +68,7 @@
 #define GOT_OBJECTS_DIR		"objects"
 #define GOT_REFS_DIR		"refs"
 #define GOT_HEAD_FILE		"HEAD"
+#define GOT_GITCONFIG		"config"
 
 /* Other files and directories inside the git directory. */
 #define GOT_FETCH_HEAD_FILE	"FETCH_HEAD"
@@ -132,6 +134,15 @@ static char *
 get_path_head(struct got_repository *repo)
 {
 	return get_path_git_child(repo, GOT_HEAD_FILE);
+}
+
+static const struct got_error *
+get_path_gitconfig(char **p, struct got_repository *repo)
+{
+	*p = get_path_git_child(repo, GOT_GITCONFIG);
+	if (*p == NULL)
+		return got_error_from_errno("asprintf");
+	return NULL;
 }
 
 static int
@@ -330,7 +341,7 @@ got_repo_open(struct got_repository **repop, const cha
 {
 	struct got_repository *repo = NULL;
 	const struct got_error *err = NULL;
-	char *abspath;
+	char *abspath, *gitconfig_path = NULL;
 	int i, tried_root = 0;
 
 	*repop = NULL;
@@ -394,12 +405,25 @@ got_repo_open(struct got_repository **repop, const cha
 		if (path == NULL)
 			err = got_error_from_errno2("dirname", path);
 	} while (path);
+
+	err = get_path_gitconfig(&gitconfig_path, repo);
+	if (err)
+		goto done;
+
+#ifdef notyet
+	err = got_gitconfig_open(&repo->gitconfig, gitconfig_path);
+	if (err)
+		goto done;
+#else
+	repo->gitconfig = NULL;
+#endif
 done:
 	if (err)
 		got_repo_close(repo);
 	else
 		*repop = repo;
 	free(abspath);
+	free(gitconfig_path);
 	return err;
 }
 
@@ -443,6 +467,8 @@ got_repo_close(struct got_repository *repo)
 		    err == NULL)
 			err = got_error_from_errno("close");
 	}
+	if (repo->gitconfig)
+		got_gitconfig_close(repo->gitconfig);
 	free(repo);
 
 	return err;
blob - 85f9a8965833c7ec25df706646762df09e50327b
blob + c4fa14f3feb7a9f4a7b01276e5b724b478cab932
--- tog/Makefile
+++ tog/Makefile
@@ -8,7 +8,7 @@ SRCS=		tog.c blame.c commit_graph.c delta.c diff.c \
 		object_idset.c object_parse.c opentemp.c path.c pack.c \
 		privsep.c reference.c repository.c sha1.c worktree.c \
 		utf8.c inflate.c buf.c worklist.c rcsutil.c diff3.c \
-		lockfile.c deflate.c object_create.c
+		lockfile.c deflate.c object_create.c gitconfig.c
 MAN =		${PROG}.1
 
 CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib