"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Stefan Sperling <stsp@stsp.name>
Subject:
add got.conf(5) to worktree meta-data area
To:
gameoftrees@openbsd.org
Date:
Fri, 11 Sep 2020 10:10:01 +0200

Download raw body.

Thread
This adds a per-worktree got.conf(5) file. This can be useful in cases
where repository-wide settings need to be overridden.

One hypothetical scenario is multiple user accounts sharing a repository.
In this use case author information configured in the repository's got.conf
will override everyone's GOT_AUTHOR environment variable. With the patch
below, each user can now configure author information in their respective
work trees in order to override any repository-wide default.

The repository-wide got.conf must override GOT_AUTHOR to support the use
case where a single user account uses multiple repositories with differing
author identities.

Based on the above considerations, I came up with the following priority
scheme, which is documented in a comment added by the patch (and documented
in relevant sections of man pages):

+	 * Priority of potential author information sources, from most
+	 * significant to least significant:
+	 * 1) work tree's .got/got.conf file
+	 * 2) repository's got.conf file
+	 * 3) repository's git config file
+	 * 4) environment variables
+	 * 5) global git config files (in user's home directory or /etc)

In the future, the same priority scheme could be applied to other settings,
such as the $EDITOR to use.

ok?

 M  got/Makefile
 M  got/git-repository.5
 M  got/got-worktree.5
 M  got/got.1
 M  got/got.c
 M  got/got.conf.5
 M  gotweb/Makefile
 A  include/got_gotconfig.h
 M  include/got_repository.h
 M  include/got_worktree.h
 A  lib/got_lib_gotconfig.h
 M  lib/got_lib_repository.h
 M  lib/got_lib_worktree.h
 A  lib/gotconfig.c
 M  lib/repository.c
 M  lib/worktree.c
 M  regress/cmdline/commit.sh
 M  regress/cmdline/fetch.sh
 M  regress/fetch/Makefile
 M  tog/Makefile

diff 1255748cb9b4a8d7ff0bdeea4ea3384865e4e1ab b610588364b6aef22a59c6ffd6d7063db1c04533
blob - 27b8780c69c81f08f6abd29001c803e5e4e2f601
blob + c244659a0df74322caa231ad293ecbe3d0c6656e
--- got/Makefile
+++ got/Makefile
@@ -8,7 +8,8 @@ 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 rcsutil.c diff3.c lockfile.c \
-		deflate.c object_create.c delta_cache.c fetch.c
+		deflate.c object_create.c delta_cache.c fetch.c \
+		gotconfig.c
 MAN =		${PROG}.1 got-worktree.5 git-repository.5
 
 CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib
blob - c3d8db9ba60678629dd9370b6ba329c9404d86e8
blob + bd2b0aec4cc1520b2e11a1932a8969d74cd65804
--- got/git-repository.5
+++ got/git-repository.5
@@ -162,6 +162,11 @@ See
 .Xr git-config 1 .
 .It Pa description
 A human-readable description of the repository.
+.It Pa got.conf
+Configuration file for
+.Xr got 1 .
+See
+.Xr got.conf 5 .
 .It Pa hooks/
 This directory contains hook scripts to run when certain events occur.
 .It Pa index
@@ -201,6 +206,7 @@ was bare.
 .Xr deflate 3 ,
 .Xr SHA1 3 ,
 .Xr got-worktree 5
+.Xr got.conf 5
 .Sh HISTORY
 The Git repository format was initially designed by Linus Torvalds in 2005
 and has since been extended by various people involved in the development
blob - 49ede63463e6753340669f22f44dd1d2dc419a94
blob + 8f8ac0e97ca4c5e796623f8c6c176dbabf331a52
--- got/got-worktree.5
+++ got/got-worktree.5
@@ -161,6 +161,11 @@ SHA1 hex-string representation of the current base com
 File status information.
 .It Pa format
 Work tree format number.
+.It Pa got.conf
+Configuration file for
+.Xr got 1 .
+See
+.Xr got.conf 5 .
 .It Pa head-ref
 Name of the reference to the current branch.
 .It Pa lock
@@ -178,4 +183,5 @@ A universal unique identifier for the work tree.
 .Xr stat 2 ,
 .Xr umask 2 ,
 .Xr lockf 3 ,
-.Xr git-repository 5
+.Xr git-repository 5 ,
+.Xr got.conf 5
blob - fc2c1967ab5a100d7afd85ed536d8bec33d4c31f
blob + 19a06c86f792b3c3404b00fa130431da3857d9c2
--- got/got.1
+++ got/got.1
@@ -315,9 +315,9 @@ If no
 is specified,
 .Dq origin
 will be used.
-The remote repository's URL is obtained from the corresponding entry in the
+The remote repository's URL is obtained from the corresponding entry in
 .Xr got.conf 5
-or
+or Git's
 .Pa config
 file of the local repository, as created by
 .Cm got clone .
@@ -1930,18 +1930,15 @@ attempts to reject
 .Ev GOT_AUTHOR
 environment variables with a missing email address.
 .Pp
-If present,
-configuration settings in
-.Xr got.conf 5 ,
-or Git's
+.Ev GOT_AUTHOR will be overriden by configuration settings in
+.Xr got.conf 5
+or by Git's
 .Dv user.name
 and
 .Dv user.email
 configuration settings in the repository's
 .Pa .git/config
-file,
-will override the value of
-.Ev GOT_AUTHOR .
+file.
 The
 .Dv user.name
 and
@@ -1970,8 +1967,24 @@ This variable will be silently ignored if it is set to
 .It Pa got.conf
 Repository-wide configuration settings for
 .Nm .
-If present, this configuration file is located in the root directory
-of a Git repository and supersedes any relevant settings in Git's
+If present, a
+.Xr got.conf 5
+configuration file located in the root directory of a Git repository
+supersedes any relevant settings in Git's
+.Pa config
+file.
+.Pp
+.It Pa .got/got.conf
+Worktree-specific configuration settings for
+.Nm .
+If present, a
+.Xr got.conf
+configuration file in the
+.Pa .got
+meta-data directory of a work tree supersedes any relevant settings in
+the repository's
+.Xr got.conf 5
+configuration file and Git's
 .Pa config
 file.
 .El
blob - 3a7013aa09b5952fd99ebb4cbf7e06235e769d42
blob + f51c668647291caa2517c4565190ec31f8c24aa5
--- got/got.c
+++ got/got.c
@@ -54,6 +54,7 @@
 #include "got_blame.h"
 #include "got_privsep.h"
 #include "got_opentemp.h"
+#include "got_gotconfig.h"
 
 #ifndef nitems
 #define nitems(_a)	(sizeof((_a)) / sizeof((_a)[0]))
@@ -516,14 +517,33 @@ import_progress(void *arg, const char *path)
 }
 
 static const struct got_error *
-get_author(char **author, struct got_repository *repo)
+get_author(char **author, struct got_repository *repo,
+    struct got_worktree *worktree)
 {
 	const struct got_error *err = NULL;
-	const char *got_author, *name, *email;
+	const char *got_author = NULL, *name, *email;
+	const struct got_gotconfig *worktree_conf = NULL, *repo_conf = NULL;
 
 	*author = NULL;
 
-	got_author = got_repo_get_gotconfig_author(repo);
+	if (worktree)
+		worktree_conf = got_worktree_get_gotconfig(worktree);
+	repo_conf = got_repo_get_gotconfig(repo);
+
+	/*
+	 * Priority of potential author information sources, from most
+	 * significant to least significant:
+	 * 1) work tree's .got/got.conf file
+	 * 2) repository's got.conf file
+	 * 3) repository's git config file
+	 * 4) environment variables
+	 * 5) global git config files (in user's home directory or /etc)
+	 */
+
+	if (worktree_conf)
+		got_author = got_gotconfig_get_author(worktree_conf);
+	if (got_author == NULL)
+		got_author = got_gotconfig_get_author(repo_conf);
 	if (got_author == NULL) {
 		name = got_repo_get_gitconfig_author_name(repo);
 		email = got_repo_get_gitconfig_author_email(repo);
@@ -672,7 +692,7 @@ cmd_import(int argc, char *argv[])
 	if (error)
 		goto done;
 
-	error = get_author(&author, repo);
+	error = get_author(&author, repo, NULL);
 	if (error)
 		return error;
 
@@ -1654,7 +1674,8 @@ done:
 
 static const struct got_error *
 delete_missing_refs(struct got_pathlist_head *their_refs,
-    struct got_pathlist_head *their_symrefs, struct got_remote_repo *remote,
+    struct got_pathlist_head *their_symrefs,
+    const struct got_remote_repo *remote,
     int verbosity, struct got_repository *repo)
 {
 	const struct got_error *err = NULL, *unlock_err;
@@ -1780,11 +1801,12 @@ cmd_fetch(int argc, char *argv[])
 	const char *remote_name;
 	char *proto = NULL, *host = NULL, *port = NULL;
 	char *repo_name = NULL, *server_path = NULL;
-	struct got_remote_repo *remotes, *remote = NULL;
+	const struct got_remote_repo *remotes, *remote = NULL;
 	int nremotes;
 	char *id_str = NULL;
 	struct got_repository *repo = NULL;
 	struct got_worktree *worktree = NULL;
+	const struct got_gotconfig *repo_conf = NULL, *worktree_conf = NULL;
 	struct got_pathlist_head refs, symrefs, wanted_branches, wanted_refs;
 	struct got_pathlist_entry *pe;
 	struct got_object_id *pack_hash = NULL;
@@ -1901,24 +1923,42 @@ cmd_fetch(int argc, char *argv[])
 	if (error)
 		goto done;
 
-	got_repo_get_gotconfig_remotes(&nremotes, &remotes, repo);
-	for (i = 0; i < nremotes; i++) {
-		remote = &remotes[i];
-		if (strcmp(remote->name, remote_name) == 0)
-			break;
+	if (worktree) {
+		worktree_conf = got_worktree_get_gotconfig(worktree);
+		if (worktree_conf) {
+			got_gotconfig_get_remotes(&nremotes, &remotes,
+			    worktree_conf);
+			for (i = 0; i < nremotes; i++) {
+				remote = &remotes[i];
+				if (strcmp(remote->name, remote_name) == 0)
+					break;
+			}
+		}
 	}
-	if (i == nremotes) {
+	if (remote == NULL) {
+		repo_conf = got_repo_get_gotconfig(repo);
+		if (repo_conf) {
+			got_gotconfig_get_remotes(&nremotes, &remotes,
+			    repo_conf);
+			for (i = 0; i < nremotes; i++) {
+				remote = &remotes[i];
+				if (strcmp(remote->name, remote_name) == 0)
+					break;
+			}
+		}
+	}
+	if (remote == NULL) {
 		got_repo_get_gitconfig_remotes(&nremotes, &remotes, repo);
 		for (i = 0; i < nremotes; i++) {
 			remote = &remotes[i];
 			if (strcmp(remote->name, remote_name) == 0)
 				break;
 		}
-		if (i == nremotes) {
-			error = got_error_path(remote_name, GOT_ERR_NO_REMOTE);
-			goto done;
-		}
 	}
+	if (remote == NULL) {
+		error = got_error_path(remote_name, GOT_ERR_NO_REMOTE);
+		goto done;
+	}
 
 	error = got_fetch_parse_uri(&proto, &host, &port, &server_path,
 	    &repo_name, remote->url);
@@ -5684,8 +5724,8 @@ done:
 }
 
 static const struct got_error *
-add_tag(struct got_repository *repo, const char *tag_name,
-    const char *commit_arg, const char *tagmsg_arg)
+add_tag(struct got_repository *repo, struct got_worktree *worktree,
+    const char *tag_name, const char *commit_arg, const char *tagmsg_arg)
 {
 	const struct got_error *err = NULL;
 	struct got_object_id *commit_id = NULL, *tag_id = NULL;
@@ -5703,7 +5743,7 @@ add_tag(struct got_repository *repo, const char *tag_n
 	if (tag_name[0] == '-')
 		return got_error_path(tag_name, GOT_ERR_REF_NAME_MINUS);
 
-	err = get_author(&tagger, repo);
+	err = get_author(&tagger, repo, worktree);
 	if (err)
 		return err;
 
@@ -5923,7 +5963,7 @@ cmd_tag(int argc, char *argv[])
 				goto done;
 		}
 
-		error = add_tag(repo, tag_name,
+		error = add_tag(repo, worktree, tag_name,
 		    commit_id_str ? commit_id_str : commit_id_arg, tagmsg);
 	}
 done:
@@ -6629,7 +6669,7 @@ cmd_commit(int argc, char *argv[])
 	if (error != NULL)
 		goto done;
 
-	error = get_author(&author, repo);
+	error = get_author(&author, repo, worktree);
 	if (error)
 		return error;
 
blob - d6d11e44ab3797c4469bc5d41a882b8be638c18d
blob + 734c2588bf80cb8172f32339ab787239afdda279
--- got/got.conf.5
+++ got/got.conf.5
@@ -24,6 +24,15 @@
 is the run-time configuration file for
 .Xr got 1 .
 .Pp
+.Nm
+may be present in the root directory of a Git repository for
+repository-wide settings, or in the
+.Pa .got
+meta-data directory of a work tree to override repository-wide
+settings for
+.Xr got 1
+commands executed within this work tree.
+.Pp
 The file format is line-based, with one configuration directive per line.
 Any lines beginning with a
 .Sq #
@@ -144,13 +153,27 @@ remote "origin" {
 .Sh FILES
 .Bl -tag -width Ds -compact
 .It Pa got.conf
-If present, the
+If present,
 .Nm
-configuration file is located in the root directory of a Git repository
-and supersedes any relevant settings in Git's
+located in the root directory of a Git repository supersedes any relevant
+settings in Git's
 .Pa config
 file.
+.Pp
+.It Pa .got/got.conf
+If present,
+.Nm
+located in the
+.Pa .got
+meta-data directory of a
+.Xr got 1
+work tree supersedes any relevant settings in the repository's
+.Nm
+configuration file and Git's
+.Pa config
+file.
 .El
 .Sh SEE ALSO
 .Xr got 1 ,
-.Xr git-repository 5
+.Xr git-repository 5,
+.Xr got-worktree 5
blob - f22b8bb81af7cfdf1f29b916d2c0fb8cfe1a58a4
blob + 86cf8bc92c4952d24ebe4dce99160c0be49ac598
--- gotweb/Makefile
+++ gotweb/Makefile
@@ -11,7 +11,7 @@ SRCS =		gotweb.c parse.y blame.c commit_graph.c delta.
 		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 rcsutil.c diff3.c lockfile.c \
-		deflate.c object_create.c delta_cache.c
+		deflate.c object_create.c delta_cache.c gotconfig.c
 MAN =		${PROG}.conf.5 ${PROG}.8
 
 CPPFLAGS +=	-I${.CURDIR}/../include -I${.CURDIR}/../lib -I${.CURDIR} \
blob - /dev/null
blob + 3dbe5d7d43cf45ec0e7997d43f266c3ce0c9fcbe (mode 644)
--- /dev/null
+++ include/got_gotconfig.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2020 Stefan Sperling <stsp@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+struct got_gotconfig;
+
+/*
+ * Obtain the commit author parsed from got.conf.
+ * Return NULL if no configuration file or author could be found.
+ */
+const char *got_gotconfig_get_author(const struct got_gotconfig *);
+
+/*
+ * Obtain the list of remote repositories parsed from got.conf.
+ * Return 0 and NULL if no configuration file or remote repository
+ * could be found.
+ */
+void got_gotconfig_get_remotes(int *, const struct got_remote_repo **,
+    const struct got_gotconfig *);
blob - bc9bddc1aebed32b0412d1f87f93b3ec0b753271
blob + 2b104798d08e7adbf100ae6c9d7a0877630dacdd
--- include/got_repository.h
+++ include/got_repository.h
@@ -59,16 +59,15 @@ struct got_remote_repo {
 	int mirror_references;
 };
 
-/* Obtain the commit author if parsed from got.conf, else NULL. */
-const char *got_repo_get_gotconfig_author(struct got_repository *);
-
 /* Obtain the list of remote repositories parsed from gitconfig. */ 
-void got_repo_get_gitconfig_remotes(int *, struct got_remote_repo **,
+void got_repo_get_gitconfig_remotes(int *, const struct got_remote_repo **,
     struct got_repository *);
 
-/* Obtain the list of remote repositories parsed from got.conf. */ 
-void got_repo_get_gotconfig_remotes(int *, struct got_remote_repo **,
-    struct got_repository *);
+/*
+ * Obtain a parsed representation of this repository's got.conf file.
+ * Return NULL if this configuration file could not be read.
+ */
+const struct got_gotconfig *got_repo_get_gotconfig(struct got_repository *);
 
 /*
  * Obtain paths to various directories within a repository.
blob - c655d810c3ca855bdbd7d38b7191e1243efb8b91
blob + 24fddd52e81d96ff7b1800a61de1f80319326a3e
--- include/got_worktree.h
+++ include/got_worktree.h
@@ -108,6 +108,12 @@ struct got_object_id *got_worktree_get_base_commit_id(
 const struct got_error *got_worktree_set_base_commit_id(struct got_worktree *,
     struct got_repository *, struct got_object_id *);
 
+/*
+ * Obtain a parsed representation of this worktree's got.conf file.
+ * Return NULL if this configuration file could not be read.
+ */
+const struct got_gotconfig *got_worktree_get_gotconfig(struct got_worktree *);
+
 /* A callback function which is invoked when a path is checked out. */
 typedef const struct got_error *(*got_worktree_checkout_cb)(void *,
     unsigned char, const char *);
blob - /dev/null
blob + 5e02aa1efeff0dd226e617da410a4663d8376d9a (mode 644)
--- /dev/null
+++ lib/got_lib_gotconfig.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2020 Stefan Sperling <stsp@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#define GOT_GOTCONFIG_FILENAME		"got.conf"
+
+struct got_gotconfig {
+	char *author;
+	int nremotes;
+	struct got_remote_repo *remotes;
+};
+
+const struct got_error *got_gotconfig_read(struct got_gotconfig **,
+    const char *);
+void got_gotconfig_free(struct got_gotconfig *);
blob - 53390eff4e10f3f105c870ac188b9a0db1df2416
blob + 87f25580cb957d598ce95524a992a2d67f95d9ad
--- lib/got_lib_repository.h
+++ lib/got_lib_repository.h
@@ -21,7 +21,6 @@
 #define GOT_REFS_DIR		"refs"
 #define GOT_HEAD_FILE		"HEAD"
 #define GOT_GITCONFIG		"config"
-#define GOT_GOTCONFIG		"got.conf"
 
 /* Other files and directories inside the git directory. */
 #define GOT_FETCH_HEAD_FILE	"FETCH_HEAD"
@@ -67,9 +66,7 @@ struct got_repository {
 	char *gitconfig_owner;
 
 	/* Settings read from got.conf. */
-	char *gotconfig_author;
-	int ngotconfig_remotes;
-	struct got_remote_repo *gotconfig_remotes;
+	struct got_gotconfig *gotconfig;
 };
 
 const struct got_error*got_repo_cache_object(struct got_repository *,
blob - c45a95888b5015de44ca323f6dd4759e19eb36f4
blob + 067bac22c55a94684a7dcb779417671747de7972
--- lib/got_lib_worktree.h
+++ lib/got_lib_worktree.h
@@ -33,6 +33,12 @@ struct got_worktree {
 	 * shared lock must be upgraded to an exclusive lock.
 	 */
 	int lockfd;
+
+	/* Absolute path to worktree's got.conf file. */
+	char *gotconfig_path;
+
+	/* Settings read from got.conf. */
+	struct got_gotconfig *gotconfig;
 };
 
 struct got_commitable {
blob - /dev/null
blob + c52b0ab71b1a027b3dde159ef7941c99e8811c52 (mode 644)
--- /dev/null
+++ lib/gotconfig.c
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2020 Stefan Sperling <stsp@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/uio.h>
+#include <sys/socket.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <imsg.h>
+#include <sha1.h>
+#include <limits.h>
+
+#include "got_error.h"
+#include "got_object.h"
+#include "got_repository.h"
+
+#include "got_lib_delta.h"
+#include "got_lib_object.h"
+#include "got_lib_privsep.h"
+#include "got_lib_gotconfig.h"
+
+const struct got_error *
+got_gotconfig_read(struct got_gotconfig **conf, const char *gotconfig_path)
+{
+	const struct got_error *err = NULL, *child_err = NULL;
+	int fd = -1;
+	int imsg_fds[2] = { -1, -1 };
+	pid_t pid;
+	struct imsgbuf *ibuf;
+
+	*conf = calloc(1, sizeof(**conf));
+	if (*conf == NULL)
+		return got_error_from_errno("calloc");
+
+	fd = open(gotconfig_path, O_RDONLY);
+	if (fd == -1) {
+		if (errno == ENOENT)
+			return NULL;
+		return got_error_from_errno2("open", gotconfig_path);
+	}
+
+	ibuf = calloc(1, sizeof(*ibuf));
+	if (ibuf == NULL) {
+		err = got_error_from_errno("calloc");
+		goto done;
+	}
+
+	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1) {
+		err = got_error_from_errno("socketpair");
+		goto done;
+	}
+
+	pid = fork();
+	if (pid == -1) {
+		err = got_error_from_errno("fork");
+		goto done;
+	} else if (pid == 0) {
+		got_privsep_exec_child(imsg_fds, GOT_PATH_PROG_READ_GOTCONFIG,
+		    gotconfig_path);
+		/* not reached */
+	}
+
+	if (close(imsg_fds[1]) == -1) {
+		err = got_error_from_errno("close");
+		goto done;
+	}
+	imsg_fds[1] = -1;
+	imsg_init(ibuf, imsg_fds[0]);
+
+	err = got_privsep_send_gotconfig_parse_req(ibuf, fd);
+	if (err)
+		goto done;
+	fd = -1;
+
+	err = got_privsep_send_gotconfig_author_req(ibuf);
+	if (err)
+		goto done;
+
+	err = got_privsep_recv_gotconfig_str(&(*conf)->author, ibuf);
+	if (err)
+		goto done;
+
+	err = got_privsep_send_gotconfig_remotes_req(ibuf);
+	if (err)
+		goto done;
+
+	err = got_privsep_recv_gotconfig_remotes(&(*conf)->remotes,
+	    &(*conf)->nremotes, ibuf);
+	if (err)
+		goto done;
+
+	imsg_clear(ibuf);
+	err = got_privsep_send_stop(imsg_fds[0]);
+	child_err = got_privsep_wait_for_child(pid);
+	if (child_err && err == NULL)
+		err = child_err;
+done:
+	if (imsg_fds[0] != -1 && close(imsg_fds[0]) == -1 && err == NULL)
+		err = got_error_from_errno("close");
+	if (imsg_fds[1] != -1 && close(imsg_fds[1]) == -1 && err == NULL)
+		err = got_error_from_errno("close");
+	if (fd != -1 && close(fd) == -1 && err == NULL)
+		err = got_error_from_errno2("close", gotconfig_path);
+	if (err) {
+		got_gotconfig_free(*conf);
+		*conf = NULL;
+	}
+	free(ibuf);
+	return err;
+}
+
+void
+got_gotconfig_free(struct got_gotconfig *conf)
+{
+	int i;
+
+	free(conf->author);
+
+	for (i = 0; i < conf->nremotes; i++) {
+		free(conf->remotes[i].name);
+		free(conf->remotes[i].url);
+	}
+	free(conf->remotes);
+	free(conf);
+}
+
+const char *
+got_gotconfig_get_author(const struct got_gotconfig *conf)
+{
+	return conf->author;
+}
+
+void
+got_gotconfig_get_remotes(int *nremotes, const struct got_remote_repo **remotes,
+    const struct got_gotconfig *conf)
+{
+	*nremotes = conf->nremotes;
+	*remotes = conf->remotes;
+}
blob - 803b58014d1ed3dce991365b1c035c4c54f45af9
blob + 84e9477170999aa739a81012b9df1b76060ec4f2
--- lib/repository.c
+++ lib/repository.c
@@ -60,6 +60,7 @@
 #include "got_lib_sha1.h"
 #include "got_lib_object_cache.h"
 #include "got_lib_repository.h"
+#include "got_lib_gotconfig.h"
 
 #ifndef nitems
 #define nitems(_a) (sizeof(_a) / sizeof((_a)[0]))
@@ -164,29 +165,21 @@ got_repo_get_path_gitconfig(struct got_repository *rep
 char *
 got_repo_get_path_gotconfig(struct got_repository *repo)
 {
-	return get_path_git_child(repo, GOT_GOTCONFIG);
+	return get_path_git_child(repo, GOT_GOTCONFIG_FILENAME);
 }
 
-void
-got_repo_get_gitconfig_remotes(int *nremotes, struct got_remote_repo **remotes,
-    struct got_repository *repo)
+const struct got_gotconfig *
+got_repo_get_gotconfig(struct got_repository *repo)
 {
-	*nremotes = repo->ngitconfig_remotes;
-	*remotes = repo->gitconfig_remotes;
+	return repo->gotconfig;
 }
 
-const char *
-got_repo_get_gotconfig_author(struct got_repository *repo)
-{
-	return repo->gotconfig_author;
-}
-
 void
-got_repo_get_gotconfig_remotes(int *nremotes, struct got_remote_repo **remotes,
-    struct got_repository *repo)
+got_repo_get_gitconfig_remotes(int *nremotes,
+    const struct got_remote_repo **remotes, struct got_repository *repo)
 {
-	*nremotes = repo->ngotconfig_remotes;
-	*remotes = repo->gotconfig_remotes;
+	*nremotes = repo->ngitconfig_remotes;
+	*remotes = repo->gitconfig_remotes;
 }
 
 static int
@@ -539,107 +532,6 @@ done:
 }
 
 static const struct got_error *
-parse_gotconfig_file(char **author,
-    struct got_remote_repo **remotes, int *nremotes,
-    const char *gotconfig_path)
-{
-	const struct got_error *err = NULL, *child_err = NULL;
-	int fd = -1;
-	int imsg_fds[2] = { -1, -1 };
-	pid_t pid;
-	struct imsgbuf *ibuf;
-
-	if (author)
-		*author = NULL;
-	if (remotes)
-		*remotes = NULL;
-	if (nremotes)
-		*nremotes = 0;
-
-	fd = open(gotconfig_path, O_RDONLY);
-	if (fd == -1) {
-		if (errno == ENOENT)
-			return NULL;
-		return got_error_from_errno2("open", gotconfig_path);
-	}
-
-	ibuf = calloc(1, sizeof(*ibuf));
-	if (ibuf == NULL) {
-		err = got_error_from_errno("calloc");
-		goto done;
-	}
-
-	if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1) {
-		err = got_error_from_errno("socketpair");
-		goto done;
-	}
-
-	pid = fork();
-	if (pid == -1) {
-		err = got_error_from_errno("fork");
-		goto done;
-	} else if (pid == 0) {
-		got_privsep_exec_child(imsg_fds, GOT_PATH_PROG_READ_GOTCONFIG,
-		    gotconfig_path);
-		/* not reached */
-	}
-
-	if (close(imsg_fds[1]) == -1) {
-		err = got_error_from_errno("close");
-		goto done;
-	}
-	imsg_fds[1] = -1;
-	imsg_init(ibuf, imsg_fds[0]);
-
-	err = got_privsep_send_gotconfig_parse_req(ibuf, fd);
-	if (err)
-		goto done;
-	fd = -1;
-
-	if (author) {
-		err = got_privsep_send_gotconfig_author_req(ibuf);
-		if (err)
-			goto done;
-
-		err = got_privsep_recv_gotconfig_str(author, ibuf);
-		if (err)
-			goto done;
-	}
-
-	if (remotes && nremotes) {
-		err = got_privsep_send_gotconfig_remotes_req(ibuf);
-		if (err)
-			goto done;
-
-		err = got_privsep_recv_gotconfig_remotes(remotes,
-		    nremotes, ibuf);
-		if (err)
-			goto done;
-	}
-
-	imsg_clear(ibuf);
-	err = got_privsep_send_stop(imsg_fds[0]);
-	child_err = got_privsep_wait_for_child(pid);
-	if (child_err && err == NULL)
-		err = child_err;
-done:
-	if (imsg_fds[0] != -1 && close(imsg_fds[0]) == -1 && err == NULL)
-		err = got_error_from_errno("close");
-	if (imsg_fds[1] != -1 && close(imsg_fds[1]) == -1 && err == NULL)
-		err = got_error_from_errno("close");
-	if (fd != -1 && close(fd) == -1 && err == NULL)
-		err = got_error_from_errno2("close", gotconfig_path);
-	if (err) {
-		if (author) {
-			free(*author);
-			*author = NULL;
-		}
-	}
-	free(ibuf);
-	return err;
-}
-
-static const struct got_error *
 read_gotconfig(struct got_repository *repo)
 {
 	const struct got_error *err = NULL;
@@ -649,9 +541,7 @@ read_gotconfig(struct got_repository *repo)
 	if (gotconfig_path == NULL)
 		return got_error_from_errno("got_repo_get_path_gotconfig");
 
-	err = parse_gotconfig_file(&repo->gotconfig_author,
-	    &repo->gotconfig_remotes, &repo->ngotconfig_remotes,
-	    gotconfig_path);
+	err = got_gotconfig_read(&repo->gotconfig, gotconfig_path);
 	free(gotconfig_path);
 	return err;
 }
@@ -788,12 +678,8 @@ got_repo_close(struct got_repository *repo)
 			err = got_error_from_errno("close");
 	}
 
-	free(repo->gotconfig_author);
-	for (i = 0; i < repo->ngotconfig_remotes; i++) {
-		free(repo->gotconfig_remotes[i].name);
-		free(repo->gotconfig_remotes[i].url);
-	}
-	free(repo->gotconfig_remotes);
+	if (repo->gotconfig)
+		got_gotconfig_free(repo->gotconfig);
 	free(repo->gitconfig_author_name);
 	free(repo->gitconfig_author_email);
 	for (i = 0; i < repo->ngitconfig_remotes; i++) {
blob - 8afc4c2596b52c558eca6fd9e0ac8678040d308f
blob + f5fa3485648a42a4ee5b7d43aaf7f4dceb6ed763
--- lib/worktree.c
+++ lib/worktree.c
@@ -55,6 +55,7 @@
 #include "got_lib_object_create.h"
 #include "got_lib_object_idset.h"
 #include "got_lib_diff.h"
+#include "got_lib_gotconfig.h"
 
 #ifndef MIN
 #define	MIN(_a,_b) ((_a) < (_b) ? (_a) : (_b))
@@ -416,6 +417,18 @@ open_worktree(struct got_worktree **worktree, const ch
 
 	err = read_meta_file(&(*worktree)->head_ref_name, path_got,
 	    GOT_WORKTREE_HEAD_REF);
+	if (err)
+		goto done;
+
+	if (asprintf(&(*worktree)->gotconfig_path, "%s/%s/%s",
+	    (*worktree)->root_path,
+	    GOT_WORKTREE_GOT_DIR, GOT_GOTCONFIG_FILENAME) == -1) {
+		err = got_error_from_errno("asprintf");
+		goto done;
+	}
+
+	err = got_gotconfig_read(&(*worktree)->gotconfig,
+	    (*worktree)->gotconfig_path);
 done:
 	if (repo)
 		got_repo_close(repo);
@@ -468,6 +481,8 @@ got_worktree_close(struct got_worktree *worktree)
 			err = got_error_from_errno2("close",
 			    got_worktree_get_root_path(worktree));
 	free(worktree->root_path);
+	free(worktree->gotconfig_path);
+	got_gotconfig_free(worktree->gotconfig);
 	free(worktree);
 	return err;
 }
@@ -483,7 +498,6 @@ got_worktree_get_repo_path(struct got_worktree *worktr
 {
 	return worktree->repo_path;
 }
-
 const char *
 got_worktree_get_path_prefix(struct got_worktree *worktree)
 {
@@ -596,6 +610,12 @@ done:
 	free(id_str);
 	free(path_got);
 	return err;
+}
+
+const struct got_gotconfig *
+got_worktree_get_gotconfig(struct got_worktree *worktree)
+{
+	return worktree->gotconfig;
 }
 
 static const struct got_error *
blob - 13a4e58bcc3693cd4eb419089a8e29ea5e377524
blob + 7e2e93f01cb45ae2344a72007a67db4942a023f5
--- regress/cmdline/commit.sh
+++ regress/cmdline/commit.sh
@@ -736,6 +736,45 @@ function test_commit_gotconfig_author {
 	test_done "$testroot" "$ret"
 }
 
+function test_commit_gotconfig_worktree_author {
+	local testroot=`test_init commit_gotconfig_worktree_author`
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+	echo 'author "Flan Luck <flan_luck@openbsd.org>"' \
+		> $testroot/repo/.git/got.conf
+	echo 'author "Flan Squee <flan_squee@openbsd.org>"' \
+		> $testroot/wt/.got/got.conf
+
+	echo "modified alpha" > $testroot/wt/alpha
+	(cd $testroot/wt && got commit -m 'test gotconfig author' > /dev/null)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	(cd $testroot/repo && got log -l1 | grep ^from: > $testroot/stdout)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "from: Flan Squee <flan_squee@openbsd.org>" \
+		> $testroot/stdout.expected
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
+	test_done "$testroot" "$ret"
+}
+
 function test_commit_gitconfig_author {
 	local testroot=`test_init commit_gitconfig_author`
 
@@ -1335,6 +1374,7 @@ run_test test_commit_outside_refs_heads
 run_test test_commit_no_email
 run_test test_commit_tree_entry_sorting
 run_test test_commit_gotconfig_author
+run_test test_commit_gotconfig_worktree_author
 run_test test_commit_gitconfig_author
 run_test test_commit_xbit_change
 run_test test_commit_normalizes_filemodes
blob - 22fde4a90a75e2c5a0bb28cb02c1fa209ef6f3e2
blob + a131768e455946cd8609feb790b2dd1a83b1644c
--- regress/cmdline/fetch.sh
+++ regress/cmdline/fetch.sh
@@ -948,6 +948,85 @@ function test_fetch_headref_deleted_locally {
 	test_done "$testroot" "$ret"
 }
 
+function test_fetch_gotconfig_remote_repo {
+	local testroot=`test_init fetch_gotconfig_remote_repo`
+	local testurl=ssh://127.0.0.1/$testroot
+	local commit_id=`git_show_head $testroot/repo`
+
+	got branch -r $testroot/repo -c $commit_id foo
+	got ref -r $testroot/repo -c $commit_id refs/hoo/boo/zoo
+	got tag -r $testroot/repo -c $commit_id -m tag "1.0" >/dev/null
+
+	got clone -q $testurl/repo $testroot/repo-clone
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "got clone command failed unexpectedly" >&2
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+cat > $testroot/repo-clone/got.conf <<EOF
+remote "foobar" {
+	protocol ssh
+	server 127.0.0.1
+	repository "$testroot/repo"
+}
+
+remote "barbaz" {
+	protocol ssh
+	server 127.0.0.1
+	repository "$testroot/does-not-exist"
+}
+EOF
+	(cd $testroot/repo-clone && got fetch -l foobar \
+		> $testroot/stdout)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "got fetch command failed unexpectedly" >&2
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "Connecting to \"foobar\" 127.0.0.1" > $testroot/stdout.expected
+	got ref -l -r $testroot/repo >> $testroot/stdout.expected
+
+	cmp -s $testroot/stdout $testroot/stdout.expected
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+
+cat > $testroot/wt/.got/got.conf <<EOF
+remote "barbaz" {
+	protocol ssh
+	server 127.0.0.1
+	repository "$testroot/repo"
+}
+EOF
+	(cd $testroot/wt && got fetch -l barbaz > $testroot/stdout)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "got fetch command failed unexpectedly" >&2
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "Connecting to \"barbaz\" 127.0.0.1" > $testroot/stdout.expected
+	got ref -l -r $testroot/repo >> $testroot/stdout.expected
+
+	cmp -s $testroot/stdout $testroot/stdout.expected
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
+	test_done "$testroot" "$ret"
+
+}
+
 test_parseargs "$@"
 run_test test_fetch_basic
 run_test test_fetch_list
@@ -960,3 +1039,4 @@ run_test test_fetch_reference
 run_test test_fetch_replace_symref
 run_test test_fetch_update_headref
 run_test test_fetch_headref_deleted_locally
+run_test test_fetch_gotconfig_remote_repo
blob - 9834aeeb0ffac0003745cb75c3ecbe20d3a2d6ff
blob + f9e0d6b0ee0b8e3d702b742b51baa3819d382ff2
--- regress/fetch/Makefile
+++ regress/fetch/Makefile
@@ -4,7 +4,7 @@ PROG = fetch_test
 SRCS = error.c privsep.c reference.c sha1.c object.c object_parse.c path.c \
 	opentemp.c repository.c lockfile.c object_cache.c pack.c inflate.c \
 	deflate.c delta.c delta_cache.c object_idset.c object_create.c \
-	fetch.c fetch_test.c
+	fetch.c gotconfig.c fetch_test.c
 
 CPPFLAGS = -I${.CURDIR}/../../include -I${.CURDIR}/../../lib
 LDADD = -lutil -lz
blob - aafde1a4c7fe645f39e2b0acb46faa64767369c7
blob + 41af3bef58e4bdab819c0a659369ee2a3c2f2e2f
--- tog/Makefile
+++ tog/Makefile
@@ -8,7 +8,8 @@ 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 rcsutil.c diff3.c \
-		lockfile.c deflate.c object_create.c delta_cache.c
+		lockfile.c deflate.c object_create.c delta_cache.c \
+		gotconfig.c
 MAN =		${PROG}.1
 
 CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib