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

From:
Stefan Sperling <stsp@stsp.name>
Subject:
separate send and fetch config blocks
To:
gameoftrees@openbsd.org
Date:
Mon, 30 Aug 2021 13:33:51 +0200

Download raw body.

Thread
This allows for setting fetch-specific and send-specific options in got.conf.
I have added a minimal test which verifies that different repositories can
be fetched from and sent to.

I have not yet updated got.conf documentation.

There is a related problem in parse.y on the main branch:

  got-read-gotconfig/parse.y: yacc finds 16 shift/reduce conflicts

I don't know what impact this has on the behaviour of the patch below,
if any. I will look into this next.

diff 7eda82c676f6639f83139454d1ab1d487a387741 76884c3fd24036424499f2614fd0f707bba6f8b2
blob - f48598b57aa7474d4174c928c96d948834824f21
blob + 7341a1e00a6e590674fc8184217fe51ee5893014
--- got/got.c
+++ got/got.c
@@ -2370,20 +2370,20 @@ cmd_fetch(int argc, char *argv[])
 	if (TAILQ_EMPTY(&wanted_branches)) {
 		if (!fetch_all_branches)
 			fetch_all_branches = remote->fetch_all_branches;
-		for (i = 0; i < remote->nbranches; i++) {
+		for (i = 0; i < remote->nfetch_branches; i++) {
 			got_pathlist_append(&wanted_branches,
-			    remote->branches[i], NULL);
+			    remote->fetch_branches[i], NULL);
 		}
 	}
 	if (TAILQ_EMPTY(&wanted_refs)) {
-		for (i = 0; i < remote->nrefs; i++) {
+		for (i = 0; i < remote->nfetch_refs; i++) {
 			got_pathlist_append(&wanted_refs,
-			    remote->refs[i], NULL);
+			    remote->fetch_refs[i], NULL);
 		}
 	}
 
 	error = got_fetch_parse_uri(&proto, &host, &port, &server_path,
-	    &repo_name, remote->url);
+	    &repo_name, remote->fetch_url);
 	if (error)
 		goto done;
 
@@ -7701,7 +7701,7 @@ cmd_send(int argc, char *argv[])
 	}
 
 	error = got_fetch_parse_uri(&proto, &host, &port, &server_path,
-	    &repo_name, remote->url);
+	    &repo_name, remote->send_url);
 	if (error)
 		goto done;
 
blob - 9c2f8389ca9fc09f3499080dd949d464f4e85c67
blob + cad8fd52bf0d74bccf0f30ddd4d2173398530bcc
--- gotadmin/gotadmin.c
+++ gotadmin/gotadmin.c
@@ -292,7 +292,7 @@ cmd_info(int argc, char *argv[])
 		got_gotconfig_get_remotes(&nremotes, &remotes, gotconfig);
 		for (i = 0; i < nremotes; i++) {
 			printf("remote \"%s\": %s\n", remotes[i].name,
-			    remotes[i].url);
+			    remotes[i].fetch_url);
 		}
 	}
 
blob - daa3c3755d6b05b2eb1cfa3014d128ff50539685
blob + 16dcde35f5cc7689f88ae7e3486c4f1fe72aa8f2
--- include/got_repository.h
+++ include/got_repository.h
@@ -57,10 +57,11 @@ void got_repo_get_gitconfig_extensions(char ***, int *
 /* Information about one remote repository. */
 struct got_remote_repo {
 	char *name;
-	char *url;
+	char *fetch_url;
+	char *send_url;
 
 	/*
-	 * If set, references are mirrored 1:1 into the local repository.
+	 * If set, fetched references are mirrored 1:1 into our repository.
 	 * If not set, references are mapped into "refs/remotes/$name/".
 	 */
 	int mirror_references;
@@ -72,12 +73,16 @@ struct got_remote_repo {
 	int fetch_all_branches;
 
 	/* Branches to fetch by default. */
-	int nbranches;
-	char **branches;
+	int nfetch_branches;
+	char **fetch_branches;
 
+	/* Branches to send by default. */
+	int nsend_branches;
+	char **send_branches;
+
 	/* Other arbitrary references to fetch by default. */
-	int nrefs;
-	char **refs;
+	int nfetch_refs;
+	char **fetch_refs;
 };
 
 /*
blob - daaf7f3639f1a1257da430a23bbc5529db74d685
blob + e60e8eb1808bf28909abf7034ebbce19c0dc5100
--- lib/got_lib_privsep.h
+++ lib/got_lib_privsep.h
@@ -443,19 +443,24 @@ struct got_imsg_traversed_commits {
 } __attribute__((__packed__));
 
 /*
- * Structure for GOT_IMSG_GITCONFIG_REMOTE data.
+ * Structure for GOT_IMSG_GOTCONFIG_REMOTE and
+ * GOT_IMSG_GOTCONFIG_REMOTE data.
  */
 struct got_imsg_remote {
 	size_t name_len;
-	size_t url_len;
+	size_t fetch_url_len;
+	size_t send_url_len;
 	int mirror_references;
 	int fetch_all_branches;
-	int nbranches;
-	int nrefs;
+	int nfetch_branches;
+	int nsend_branches;
+	int nfetch_refs;
 
-	/* Followed by name_len + url_len data bytes. */
-	/* Followed by nbranches GOT_IMSG_GITCONFIG_STR_VAL messages. */
-	/* Followed by nrefs GOT_IMSG_GITCONFIG_STR_VAL messages. */
+	/* Followed by name_len data bytes. */
+	/* Followed by fetch_url_len + send_url_len data bytes. */
+	/* Followed by nfetch_branches GOT_IMSG_GITCONFIG_STR_VAL messages. */
+	/* Followed by nsend_branches GOT_IMSG_GITCONFIG_STR_VAL messages. */
+	/* Followed by nfetch_refs GOT_IMSG_GITCONFIG_STR_VAL messages. */
 } __attribute__((__packed__));
 
 /*
blob - ecad96e05628c923f414e7613a59a1e54b82bba0
blob + 4846f5880ced5034d986881b1a612ba1062686af
--- lib/privsep.c
+++ lib/privsep.c
@@ -2206,13 +2206,17 @@ free_remote_data(struct got_remote_repo *remote)
 	int i;
 
 	free(remote->name);
-	free(remote->url);
-	for (i = 0; i < remote->nbranches; i++)
-		free(remote->branches[i]);
-	free(remote->branches);
-	for (i = 0; i < remote->nrefs; i++)
-		free(remote->refs[i]);
-	free(remote->refs);
+	free(remote->fetch_url);
+	free(remote->send_url);
+	for (i = 0; i < remote->nfetch_branches; i++)
+		free(remote->fetch_branches[i]);
+	free(remote->fetch_branches);
+	for (i = 0; i < remote->nsend_branches; i++)
+		free(remote->send_branches[i]);
+	free(remote->send_branches);
+	for (i = 0; i < remote->nfetch_refs; i++)
+		free(remote->fetch_refs[i]);
+	free(remote->fetch_refs);
 }
 
 const struct got_error *
@@ -2274,9 +2278,11 @@ got_privsep_recv_gitconfig_remotes(struct got_remote_r
 				break;
 			}
 			memcpy(&iremote, imsg.data, sizeof(iremote));
-			if (iremote.name_len == 0 || iremote.url_len == 0 ||
+			if (iremote.name_len == 0 ||
+			    iremote.fetch_url_len == 0 ||
+			    iremote.send_url_len == 0 ||
 			    (sizeof(iremote) + iremote.name_len +
-			    iremote.url_len) > datalen) {
+			    iremote.fetch_url_len + iremote.send_url_len) > datalen) {
 				err = got_error(GOT_ERR_PRIVSEP_LEN);
 				break;
 			}
@@ -2286,19 +2292,29 @@ got_privsep_recv_gitconfig_remotes(struct got_remote_r
 				err = got_error_from_errno("strndup");
 				break;
 			}
-			remote->url = strndup(imsg.data + sizeof(iremote) +
-			    iremote.name_len, iremote.url_len);
-			if (remote->url == NULL) {
+			remote->fetch_url = strndup(imsg.data + sizeof(iremote) +
+			    iremote.name_len, iremote.fetch_url_len);
+			if (remote->fetch_url == NULL) {
 				err = got_error_from_errno("strndup");
 				free_remote_data(remote);
 				break;
 			}
+			remote->send_url = strndup(imsg.data + sizeof(iremote) +
+			    iremote.name_len + iremote.fetch_url_len,
+			    iremote.send_url_len);
+			if (remote->send_url == NULL) {
+				err = got_error_from_errno("strndup");
+				free_remote_data(remote);
+				break;
+			}
 			remote->mirror_references = iremote.mirror_references;
 			remote->fetch_all_branches = iremote.fetch_all_branches;
-			remote->nbranches = 0;
-			remote->branches = NULL;
-			remote->nrefs = 0;
-			remote->refs = NULL;
+			remote->nfetch_branches = 0;
+			remote->fetch_branches = NULL;
+			remote->nsend_branches = 0;
+			remote->send_branches = NULL;
+			remote->nfetch_refs = 0;
+			remote->fetch_refs = NULL;
 			(*nremotes)++;
 			break;
 		default:
@@ -2404,7 +2420,6 @@ got_privsep_recv_gotconfig_str(char **str, struct imsg
 	return err;
 }
 
-
 const struct got_error *
 got_privsep_recv_gotconfig_remotes(struct got_remote_repo **remotes,
     int *nremotes, struct imsgbuf *ibuf)
@@ -2483,9 +2498,12 @@ got_privsep_recv_gotconfig_remotes(struct got_remote_r
 				break;
 			}
 			memcpy(&iremote, imsg.data, sizeof(iremote));
-			if (iremote.name_len == 0 || iremote.url_len == 0 ||
+			if (iremote.name_len == 0 ||
+			    (iremote.fetch_url_len == 0 &&
+			    iremote.send_url_len == 0) ||
 			    (sizeof(iremote) + iremote.name_len +
-			    iremote.url_len) > datalen) {
+			    iremote.fetch_url_len + iremote.send_url_len) >
+			    datalen) {
 				err = got_error(GOT_ERR_PRIVSEP_LEN);
 				break;
 			}
@@ -2495,26 +2513,35 @@ got_privsep_recv_gotconfig_remotes(struct got_remote_r
 				err = got_error_from_errno("strndup");
 				break;
 			}
-			remote->url = strndup(imsg.data + sizeof(iremote) +
-			    iremote.name_len, iremote.url_len);
-			if (remote->url == NULL) {
+			remote->fetch_url = strndup(imsg.data +
+			    sizeof(iremote) + iremote.name_len,
+			    iremote.fetch_url_len);
+			if (remote->fetch_url == NULL) {
 				err = got_error_from_errno("strndup");
 				free_remote_data(remote);
 				break;
 			}
+			remote->send_url = strndup(imsg.data +
+			    sizeof(iremote) + iremote.name_len +
+			    iremote.fetch_url_len, iremote.send_url_len);
+			if (remote->send_url == NULL) {
+				err = got_error_from_errno("strndup");
+				free_remote_data(remote);
+				break;
+			}
 			remote->mirror_references = iremote.mirror_references;
 			remote->fetch_all_branches = iremote.fetch_all_branches;
-			if (iremote.nbranches > 0) {
-				remote->branches = recallocarray(NULL, 0,
-				    iremote.nbranches, sizeof(char *));
-				if (remote->branches == NULL) {
+			if (iremote.nfetch_branches > 0) {
+				remote->fetch_branches = recallocarray(NULL, 0,
+				    iremote.nfetch_branches, sizeof(char *));
+				if (remote->fetch_branches == NULL) {
 					err = got_error_from_errno("calloc");
 					free_remote_data(remote);
 					break;
 				}
 			}
-			remote->nbranches = 0;
-			for (i = 0; i < iremote.nbranches; i++) {
+			remote->nfetch_branches = 0;
+			for (i = 0; i < iremote.nfetch_branches; i++) {
 				char *branch;
 				err = got_privsep_recv_gotconfig_str(&branch,
 				    ibuf);
@@ -2522,20 +2549,41 @@ got_privsep_recv_gotconfig_remotes(struct got_remote_r
 					free_remote_data(remote);
 					goto done;
 				}
-				remote->branches[i] = branch;
-				remote->nbranches++;
+				remote->fetch_branches[i] = branch;
+				remote->nfetch_branches++;
 			}
-			if (iremote.nrefs > 0) {
-				remote->refs = recallocarray(NULL, 0,
-				    iremote.nrefs, sizeof(char *));
-				if (remote->refs == NULL) {
+			if (iremote.nsend_branches > 0) {
+				remote->send_branches = recallocarray(NULL, 0,
+				    iremote.nsend_branches, sizeof(char *));
+				if (remote->send_branches == NULL) {
 					err = got_error_from_errno("calloc");
 					free_remote_data(remote);
 					break;
 				}
 			}
-			remote->nrefs = 0;
-			for (i = 0; i < iremote.nrefs; i++) {
+			remote->nsend_branches = 0;
+			for (i = 0; i < iremote.nsend_branches; i++) {
+				char *branch;
+				err = got_privsep_recv_gotconfig_str(&branch,
+				    ibuf);
+				if (err) {
+					free_remote_data(remote);
+					goto done;
+				}
+				remote->send_branches[i] = branch;
+				remote->nsend_branches++;
+			}
+			if (iremote.nfetch_refs > 0) {
+				remote->fetch_refs = recallocarray(NULL, 0,
+				    iremote.nfetch_refs, sizeof(char *));
+				if (remote->fetch_refs == NULL) {
+					err = got_error_from_errno("calloc");
+					free_remote_data(remote);
+					break;
+				}
+			}
+			remote->nfetch_refs = 0;
+			for (i = 0; i < iremote.nfetch_refs; i++) {
 				char *ref;
 				err = got_privsep_recv_gotconfig_str(&ref,
 				    ibuf);
@@ -2543,8 +2591,8 @@ got_privsep_recv_gotconfig_remotes(struct got_remote_r
 					free_remote_data(remote);
 					goto done;
 				}
-				remote->refs[i] = ref;
-				remote->nrefs++;
+				remote->fetch_refs[i] = ref;
+				remote->nfetch_refs++;
 			}
 			(*nremotes)++;
 			break;
blob - 3d857e3a7784f358729bd0b93b349fa77eaa1c10
blob + deb44a1116a2fcd589ff0e0db2fcc4a1c0e2c63d
--- lib/repository.c
+++ lib/repository.c
@@ -786,13 +786,20 @@ got_repo_free_remote_repo_data(struct got_remote_repo 
 
 	free(repo->name);
 	repo->name = NULL;
-	free(repo->url);
-	repo->url = NULL;
-	for (i = 0; i < repo->nbranches; i++)
-		free(repo->branches[i]);
-	free(repo->branches);
-	repo->branches = NULL;
-	repo->nbranches = 0;
+	free(repo->fetch_url);
+	repo->fetch_url = NULL;
+	free(repo->send_url);
+	repo->send_url = NULL;
+	for (i = 0; i < repo->nfetch_branches; i++)
+		free(repo->fetch_branches[i]);
+	free(repo->fetch_branches);
+	repo->fetch_branches = NULL;
+	repo->nfetch_branches = 0;
+	for (i = 0; i < repo->nsend_branches; i++)
+		free(repo->send_branches[i]);
+	free(repo->send_branches);
+	repo->send_branches = NULL;
+	repo->nsend_branches = 0;
 }
 
 const struct got_error *
blob - eadef712009b2da9a414361c8395ccbd43f2b1aa
blob + 3ce93f09be615d41528eb6af7e5f5a1bd77ce92f
--- libexec/got-read-gitconfig/got-read-gitconfig.c
+++ libexec/got-read-gitconfig/got-read-gitconfig.c
@@ -121,8 +121,10 @@ send_gitconfig_remotes(struct imsgbuf *ibuf, struct go
 		iremote.mirror_references = remotes[i].mirror_references;
 		iremote.name_len = strlen(remotes[i].name);
 		len += iremote.name_len;
-		iremote.url_len = strlen(remotes[i].url);
-		len += iremote.url_len;
+		iremote.fetch_url_len = strlen(remotes[i].fetch_url);
+		len += iremote.fetch_url_len;
+		iremote.send_url_len = strlen(remotes[i].send_url);
+		len += iremote.send_url_len;
 
 		wbuf = imsg_create(ibuf, GOT_IMSG_GITCONFIG_REMOTE, 0, 0, len);
 		if (wbuf == NULL)
@@ -142,12 +144,18 @@ send_gitconfig_remotes(struct imsgbuf *ibuf, struct go
 			ibuf_free(wbuf);
 			return err;
 		}
-		if (imsg_add(wbuf, remotes[i].url, iremote.url_len) == -1) {
+		if (imsg_add(wbuf, remotes[i].fetch_url, iremote.fetch_url_len) == -1) {
 			err = got_error_from_errno(
 			    "imsg_add GITCONFIG_REMOTE");
 			ibuf_free(wbuf);
 			return err;
 		}
+		if (imsg_add(wbuf, remotes[i].send_url, iremote.send_url_len) == -1) {
+			err = got_error_from_errno(
+			    "imsg_add GITCONFIG_REMOTE");
+			ibuf_free(wbuf);
+			return err;
+		}
 
 		wbuf->fd = -1;
 		imsg_close(ibuf, wbuf);
@@ -218,13 +226,23 @@ gitconfig_remotes_request(struct imsgbuf *ibuf, struct
 			*end = '\0';
 		remotes[i].name = name;
 
-		remotes[i].url = got_gitconfig_get_str(gitconfig,
+		remotes[i].fetch_url = got_gitconfig_get_str(gitconfig,
 		    node->field, "url");
-		if (remotes[i].url == NULL) {
+		if (remotes[i].fetch_url == NULL) {
 			err = got_error(GOT_ERR_GITCONFIG_SYNTAX);
 			goto done;
 		}
 
+		remotes[i].send_url = got_gitconfig_get_str(gitconfig,
+		    node->field, "pushurl");
+		if (remotes[i].send_url == NULL)
+			remotes[i].send_url = got_gitconfig_get_str(gitconfig,
+			    node->field, "url");
+		if (remotes[i].send_url == NULL) {
+			err = got_error(GOT_ERR_GITCONFIG_SYNTAX);
+			goto done;
+		}
+
 		remotes[i].mirror_references = 0;
 		mirror = got_gitconfig_get_str(gitconfig, node->field,
 		    "mirror");
blob - d2b3b15299aad1d02917abf96a7496072e5c4af3
blob + 87b6e325e90f1a5df5293fea97f21d0e0ec8ecdd
--- libexec/got-read-gotconfig/got-read-gotconfig.c
+++ libexec/got-read-gotconfig/got-read-gotconfig.c
@@ -51,28 +51,48 @@ catch_sigint(int signo)
 }
 
 static const struct got_error *
-make_repo_url(char **url, struct gotconfig_remote_repo *repo)
+make_fetch_url(char **url, struct gotconfig_remote_repo *repo)
 {
 	const struct got_error *err = NULL;
 	char *s = NULL, *p = NULL;
+	const char *protocol, *server, *repo_path;
+	int port;
 
 	*url = NULL;
 
-	if (asprintf(&s, "%s://", repo->protocol) == -1)
+	if (repo->fetch_config && repo->fetch_config->protocol)
+		protocol = repo->fetch_config->protocol;
+	else
+		protocol = repo->protocol;
+	if (protocol == NULL)
+		return got_error_fmt(GOT_ERR_PARSE_CONFIG,
+		    "fetch protocol required for remote repository \"%s\"",
+		    repo->name);
+	if (asprintf(&s, "%s://", protocol) == -1)
 		return got_error_from_errno("asprintf");
 
-	if (repo->server) {
-		p = s;
-		s = NULL;
-		if (asprintf(&s, "%s%s", p, repo->server) == -1) {
-			err = got_error_from_errno("asprintf");
-			goto done;
-		}
-		free(p);
-		p = NULL;
+	if (repo->fetch_config && repo->fetch_config->server)
+		server = repo->fetch_config->server;
+	else
+		server = repo->server;
+	if (server == NULL)
+		return got_error_fmt(GOT_ERR_PARSE_CONFIG,
+		    "fetch server required for remote repository \"%s\"",
+		    repo->name);
+	p = s;
+	s = NULL;
+	if (asprintf(&s, "%s%s", p, server) == -1) {
+		err = got_error_from_errno("asprintf");
+		goto done;
 	}
+	free(p);
+	p = NULL;
 
-	if (repo->port) {
+	if (repo->fetch_config && repo->fetch_config->server)
+		port = repo->fetch_config->port;
+	else
+		port = repo->port;
+	if (port) {
 		p = s;
 		s = NULL;
 		if (asprintf(&s, "%s:%d", p, repo->port) == -1) {
@@ -83,13 +103,82 @@ make_repo_url(char **url, struct gotconfig_remote_repo
 		p = NULL;
 	}
 
-	if (repo->repository) {
-		char *repo_path = repo->repository;
-		while (repo_path[0] == '/')
-			repo_path++;
+	if (repo->fetch_config && repo->fetch_config->repository)
+		repo_path = repo->fetch_config->repository;
+	else
+		repo_path = repo->repository;
+	if (repo_path == NULL)
+		return got_error_fmt(GOT_ERR_PARSE_CONFIG,
+		    "fetch repository path required for remote "
+		    "repository \"%s\"", repo->name);
+
+	while (repo_path[0] == '/')
+		repo_path++;
+	p = s;
+	s = NULL;
+	if (asprintf(&s, "%s/%s", p, repo_path) == -1) {
+		err = got_error_from_errno("asprintf");
+		goto done;
+	}
+	free(p);
+	p = NULL;
+
+	got_path_strip_trailing_slashes(s);
+done:
+	if (err) {
+		free(s);
+		free(p);
+	} else
+		*url = s;
+	return err;
+}
+
+static const struct got_error *
+make_send_url(char **url, struct gotconfig_remote_repo *repo)
+{
+	const struct got_error *err = NULL;
+	char *s = NULL, *p = NULL;
+	const char *protocol, *server, *repo_path;
+	int port;
+
+	*url = NULL;
+
+	if (repo->send_config && repo->send_config->protocol)
+		protocol = repo->send_config->protocol;
+	else
+		protocol = repo->protocol;
+	if (protocol == NULL)
+		return got_error_fmt(GOT_ERR_PARSE_CONFIG,
+		    "send protocol required for remote repository \"%s\"",
+		    repo->name);
+	if (asprintf(&s, "%s://", protocol) == -1)
+		return got_error_from_errno("asprintf");
+
+	if (repo->send_config && repo->send_config->server)
+		server = repo->send_config->server;
+	else
+		server = repo->server;
+	if (server == NULL)
+		return got_error_fmt(GOT_ERR_PARSE_CONFIG,
+		    "send server required for remote repository \"%s\"",
+		    repo->name);
+	p = s;
+	s = NULL;
+	if (asprintf(&s, "%s%s", p, server) == -1) {
+		err = got_error_from_errno("asprintf");
+		goto done;
+	}
+	free(p);
+	p = NULL;
+
+	if (repo->send_config && repo->send_config->server)
+		port = repo->send_config->port;
+	else
+		port = repo->port;
+	if (port) {
 		p = s;
 		s = NULL;
-		if (asprintf(&s, "%s/%s", p, repo_path) == -1) {
+		if (asprintf(&s, "%s:%d", p, repo->port) == -1) {
 			err = got_error_from_errno("asprintf");
 			goto done;
 		}
@@ -97,6 +186,26 @@ make_repo_url(char **url, struct gotconfig_remote_repo
 		p = NULL;
 	}
 
+	if (repo->send_config && repo->send_config->repository)
+		repo_path = repo->send_config->repository;
+	else
+		repo_path = repo->repository;
+	if (repo_path == NULL)
+		return got_error_fmt(GOT_ERR_PARSE_CONFIG,
+		    "send repository path required for remote "
+		    "repository \"%s\"", repo->name);
+
+	while (repo_path[0] == '/')
+		repo_path++;
+	p = s;
+	s = NULL;
+	if (asprintf(&s, "%s/%s", p, repo_path) == -1) {
+		err = got_error_from_errno("asprintf");
+		goto done;
+	}
+	free(p);
+	p = NULL;
+
 	got_path_strip_trailing_slashes(s);
 done:
 	if (err) {
@@ -126,7 +235,7 @@ send_gotconfig_remotes(struct imsgbuf *ibuf,
 	const struct got_error *err = NULL;
 	struct got_imsg_remotes iremotes;
 	struct gotconfig_remote_repo *repo;
-	char *url = NULL;
+	char *fetch_url = NULL, *send_url = NULL;
 
 	iremotes.nremotes = nremotes;
 	if (imsg_compose(ibuf, GOT_IMSG_GOTCONFIG_REMOTES, 0, 0, -1,
@@ -144,34 +253,63 @@ send_gotconfig_remotes(struct imsgbuf *ibuf,
 		struct ibuf *wbuf;
 		struct node_branch *branch;
 		struct node_ref *ref;
-		int nbranches = 0, nrefs = 0;
+		int nfetch_branches = 0, nsend_branches = 0, nfetch_refs = 0;
 
-		branch = repo->branch;
-		while (branch) {
-			branch = branch->next;
-			nbranches++;
+		if (repo->fetch_config && repo->fetch_config->branch) {
+			branch = repo->fetch_config->branch;
+			while (branch) {
+				branch = branch->next;
+				nfetch_branches++;
+			}
+		} else {
+			branch = repo->branch;
+			while (branch) {
+				branch = branch->next;
+				nfetch_branches++;
+			}
 		}
 
-		ref = repo->ref;
+		if (repo->send_config && repo->send_config->branch) {
+			branch = repo->send_config->branch;
+			while (branch) {
+				branch = branch->next;
+				nsend_branches++;
+			}
+		} else {
+			branch = repo->branch;
+			while (branch) {
+				branch = branch->next;
+				nsend_branches++;
+			}
+		}
+
+		ref = repo->fetch_ref;
 		while (ref) {
 			ref = ref->next;
-			nrefs++;
+			nfetch_refs++;
 		}
 
-		iremote.nbranches = nbranches;
-		iremote.nrefs = nrefs;
+		iremote.nfetch_branches = nfetch_branches;
+		iremote.nsend_branches = nsend_branches;
+		iremote.nfetch_refs = nfetch_refs;
 		iremote.mirror_references = repo->mirror_references;
 		iremote.fetch_all_branches = repo->fetch_all_branches;
 
 		iremote.name_len = strlen(repo->name);
 		len += iremote.name_len;
 
-		err = make_repo_url(&url, repo);
+		err = make_fetch_url(&fetch_url, repo);
 		if (err)
 			break;
-		iremote.url_len = strlen(url);
-		len += iremote.url_len;
+		iremote.fetch_url_len = strlen(fetch_url);
+		len += iremote.fetch_url_len;
 
+		err = make_send_url(&send_url, repo);
+		if (err)
+			break;
+		iremote.send_url_len = strlen(send_url);
+		len += iremote.send_url_len;
+
 		wbuf = imsg_create(ibuf, GOT_IMSG_GOTCONFIG_REMOTE, 0, 0, len);
 		if (wbuf == NULL) {
 			err = got_error_from_errno(
@@ -192,12 +330,18 @@ send_gotconfig_remotes(struct imsgbuf *ibuf,
 			ibuf_free(wbuf);
 			break;
 		}
-		if (imsg_add(wbuf, url, iremote.url_len) == -1) {
+		if (imsg_add(wbuf, fetch_url, iremote.fetch_url_len) == -1) {
 			err = got_error_from_errno(
 			    "imsg_add GOTCONFIG_REMOTE");
 			ibuf_free(wbuf);
 			break;
 		}
+		if (imsg_add(wbuf, send_url, iremote.send_url_len) == -1) {
+			err = got_error_from_errno(
+			    "imsg_add GOTCONFIG_REMOTE");
+			ibuf_free(wbuf);
+			break;
+		}
 
 		wbuf->fd = -1;
 		imsg_close(ibuf, wbuf);
@@ -205,18 +349,52 @@ send_gotconfig_remotes(struct imsgbuf *ibuf,
 		if (err)
 			break;
 
-		free(url);
-		url = NULL;
+		free(fetch_url);
+		fetch_url = NULL;
+		free(send_url);
+		send_url = NULL;
 
-		branch = repo->branch;
-		while (branch) {
-			err = send_gotconfig_str(ibuf, branch->branch_name);
-			if (err)
-				break;
-			branch = branch->next;
+		if (repo->fetch_config && repo->fetch_config->branch) {
+			branch = repo->fetch_config->branch;
+			while (branch) {
+				err = send_gotconfig_str(ibuf,
+				    branch->branch_name);
+				if (err)
+					break;
+				branch = branch->next;
+			}
+		} else {
+			branch = repo->branch;
+			while (branch) {
+				err = send_gotconfig_str(ibuf,
+				    branch->branch_name);
+				if (err)
+					break;
+				branch = branch->next;
+			}
 		}
 
-		ref = repo->ref;
+		if (repo->send_config && repo->send_config->branch) {
+			branch = repo->send_config->branch;
+			while (branch) {
+				err = send_gotconfig_str(ibuf,
+				    branch->branch_name);
+				if (err)
+					break;
+				branch = branch->next;
+			}
+		} else {
+			branch = repo->branch;
+			while (branch) {
+				err = send_gotconfig_str(ibuf,
+				    branch->branch_name);
+				if (err)
+					break;
+				branch = branch->next;
+			}
+		}
+
+		ref = repo->fetch_ref;
 		while (ref) {
 			err = send_gotconfig_str(ibuf, ref->ref_name);
 			if (err)
@@ -225,7 +403,8 @@ send_gotconfig_remotes(struct imsgbuf *ibuf,
 		}
 	}
 
-	free(url);
+	free(fetch_url);
+	free(send_url);
 	return err;
 }
 
blob - 1ce83bbcfa6c33daba6da2f078860f3a41659d38
blob + 1ce499222101a45de399bd433825c767df869d91
--- libexec/got-read-gotconfig/gotconfig.h
+++ libexec/got-read-gotconfig/gotconfig.h
@@ -56,7 +56,7 @@ struct gotconfig_remote_repo {
 	int	mirror_references;
 	int	fetch_all_branches;
 	struct	node_branch *branch;
-	struct	node_ref *ref;
+	struct	node_ref *fetch_ref;
 	struct	fetch_config *fetch_config;
 	struct	send_config *send_config;
 };
blob - dba94813bce80f3ec22679fe997ba51bbdfc8c58
blob + 4ade508cd285b640d7fa3aaf8064bacfe1641a0c
--- libexec/got-read-gotconfig/parse.y
+++ libexec/got-read-gotconfig/parse.y
@@ -231,7 +231,7 @@ remoteopts1	: REPOSITORY STRING {
 			remote->branch = $2;
 		}
 		| REFERENCE ref {
-			remote->ref = $2;
+			remote->fetch_ref = $2;
 		}
 		| FETCH {
 			static const struct got_error* error;
blob - b7603c982431e0c17ae7aeb27a58df3481304608
blob + 2c2112b0a45b7a3317cd276452aa270633f87995
--- regress/cmdline/send.sh
+++ regress/cmdline/send.sh
@@ -1041,7 +1041,123 @@ EOF
 	test_done "$testroot" "$ret"
 }
 
+test_send_and_fetch_config() {
+	local testroot=`test_init send_fetch_conf`
+	local testurl=ssh://127.0.0.1/$testroot
+	local commit_id=`git_show_head $testroot/repo`
 
+	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
+
+	got tag -r $testroot/repo -m '1.0' 1.0 >/dev/null
+	tag_id=`got ref -r $testroot/repo -l | grep "^refs/tags/1.0" \
+		| tr -d ' ' | cut -d: -f2`
+
+	cp -R $testroot/repo-clone $testroot/repo-clone2
+	got tag -r $testroot/repo-clone2 -m '2.0' 2.0 >/dev/null
+	tag_id2=`got ref -r $testroot/repo-clone2 -l | grep "^refs/tags/2.0" \
+		| tr -d ' ' | cut -d: -f2`
+
+	cat > $testroot/repo/.git/got.conf <<EOF
+remote "origin" {
+	protocol ssh
+	server 127.0.0.1
+	send {
+		repository "$testroot/repo-clone"
+	}
+	fetch {
+		repository "$testroot/repo-clone2"
+	}
+}
+EOF
+	got ref -l -r $testroot/repo > $testroot/stdout
+	if [ "$ret" != "0" ]; then
+		echo "got ref command failed unexpectedly" >&2
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "HEAD: refs/heads/master" > $testroot/stdout.expected
+	echo "refs/heads/master: $commit_id" >> $testroot/stdout.expected
+	echo "refs/tags/1.0: $tag_id" >> $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
+
+	# fetch tag 2.0 from repo-clone2
+	got fetch -q -r $testroot/repo > $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "got fetch command failed unexpectedly" >&2
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	got ref -l -r $testroot/repo > $testroot/stdout
+	if [ "$ret" != "0" ]; then
+		echo "got ref command failed unexpectedly" >&2
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "HEAD: refs/heads/master" > $testroot/stdout.expected
+	echo "refs/heads/master: $commit_id" >> $testroot/stdout.expected
+	echo "refs/remotes/origin/HEAD: refs/remotes/origin/master" \
+		>> $testroot/stdout.expected
+	echo "refs/remotes/origin/master: $commit_id" \
+		>> $testroot/stdout.expected
+	echo "refs/tags/1.0: $tag_id" >> $testroot/stdout.expected
+	echo "refs/tags/2.0: $tag_id2" >> $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
+
+	# send tag 1.0 to repo-clone
+	got send -q -r $testroot/repo -t 1.0 > $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "got send command failed unexpectedly" >&2
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+	
+	got ref -l -r $testroot/repo-clone > $testroot/stdout
+	if [ "$ret" != "0" ]; then
+		echo "got ref command failed unexpectedly" >&2
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "HEAD: refs/heads/master" > $testroot/stdout.expected
+	echo "refs/heads/master: $commit_id" >> $testroot/stdout.expected
+	echo "refs/remotes/origin/HEAD: refs/remotes/origin/master" \
+		>> $testroot/stdout.expected
+	echo "refs/remotes/origin/master: $commit_id" \
+		>> $testroot/stdout.expected
+	echo "refs/tags/1.0: $tag_id" >> $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_send_basic
 run_test test_send_rebase_required
@@ -1052,3 +1168,4 @@ run_test test_send_tags
 run_test test_send_new_branch
 run_test test_send_all_branches
 run_test test_send_to_empty_repo
+run_test test_send_and_fetch_config