Commit Diff


commit - 239821eb715be88de935d7d1b96845b3cb8d324a
commit + 0e4002cadf11b9274c4355850d55bd59bbc20d31
blob - 25d74be5281e94a84e756a0c422e72de0aa32d7e
blob + c42f0af663c423cad213d1b0e5045d338932372c
--- got/got.1
+++ got/got.1
@@ -136,7 +136,7 @@ follows the globbing rules documented in
 .It Cm im
 Short alias for
 .Cm import .
-.It Cm clone Oo Fl a Oc Oo Fl b Ar branch Oc Oo Fl l Oc Oo Fl m Oc Oo Fl q Oc Oo Fl v Oc Ar repository-URL Op Ar directory
+.It Cm clone Oo Fl a Oc Oo Fl b Ar branch Oc Oo Fl l Oc Oo Fl m Oc Oo Fl q Oc Oo Fl v Oc Oo Fl R Ar reference Oc Ar repository-URL Op Ar directory
 Clone a Git repository at the specified
 .Ar repository-URL
 into the specified
@@ -273,6 +273,29 @@ This option will be passed to
 if applicable.
 Multiple -v options increase the verbosity.
 The maximum is 3.
+.It Fl R Ar reference
+In addition to the branches and tags that will be fetched, fetch an arbitrary
+.Ar reference
+from the remote repository's
+.Dq refs/
+namespace.
+This option may be specified multiple times to build a list of additional
+references to fetch.
+.Pp
+The references will be mapped into the cloned repository's
+.Dq refs/remotes/
+namespace, unless the
+.Fl m
+option is used to mirror references directly into the cloned repository's
+.Dq refs/
+namespace.
+.Pp
+.Cm got clone
+will refuse to fetch references from the remote repository's
+.Dq refs/remotes/
+or
+.Dq refs/got/
+namespace.
 .El
 .It Cm cl
 Short alias for
@@ -385,6 +408,30 @@ The same option will be passed to
 if applicable.
 Multiple -v options increase the verbosity.
 The maximum is 3.
+.It Fl R Ar reference
+In addition to the branches and tags that will be fetched, fetch an arbitrary
+.Ar reference
+from the remote repository's
+.Dq refs/
+namespace.
+This option may be specified multiple times to build a list of additional
+references to fetch.
+.Pp
+Each reference will be mapped into the local repository's
+.Dq refs/remotes/
+namespace, unless the local repository was created as a mirror with
+.Cm got clone -m .
+Once a reference has been fetched, a local branch based on it can be
+created with
+.Cm got branch
+if needed.
+.Pp
+.Cm got fetch
+will refuse to fetch references from the remote repository's
+.Dq refs/remotes/
+or
+.Dq refs/got/
+namespace.
 .El
 .It Cm fe
 Short alias for
blob - 989af2eb06a657a2eef3af8bf6402edafbf95c9c
blob + 4f87cf680b2db1a9e42d32ba9fff5ce4869f50f8
--- got/got.c
+++ got/got.c
@@ -812,7 +812,7 @@ __dead static void
 usage_clone(void)
 {
 	fprintf(stderr, "usage: %s clone [-a] [-b branch] [-l] [-m] [-q] [-v] "
-	    "repository-url [directory]\n", getprogname());
+	    "[-R reference] repository-url [directory]\n", getprogname());
 	exit(1);
 }
 
@@ -957,9 +957,68 @@ create_ref(const char *refname, struct got_object_id *
 done:
 	free(id_str);
 	return err;
+}
+
+static int
+match_wanted_ref(const char *refname, const char *wanted_ref)
+{
+	if (strncmp(refname, "refs/", 5) != 0)
+		return 0;
+	refname += 5;
+
+	/*
+	 * Prevent fetching of references that won't make any
+	 * sense outside of the remote repository's context.
+	 */
+	if (strncmp(refname, "got/", 4) == 0)
+		return 0;
+	if (strncmp(refname, "remotes/", 8) == 0)
+		return 0;
+
+	if (strncmp(wanted_ref, "refs/", 5) == 0)
+		wanted_ref += 5;
+
+	/* Allow prefix match. */
+	if (got_path_is_child(refname, wanted_ref, strlen(wanted_ref)))
+		return 1;
+
+	/* Allow exact match. */
+	return (strcmp(refname, wanted_ref) == 0);
 }
 
+static int
+is_wanted_ref(struct got_pathlist_head *wanted_refs, const char *refname)
+{
+	struct got_pathlist_entry *pe;
+
+	TAILQ_FOREACH(pe, wanted_refs, entry) {
+		if (match_wanted_ref(refname, pe->path))
+			return 1;
+	}
+
+	return 0;
+}
+
 static const struct got_error *
+create_wanted_ref(const char *refname, struct got_object_id *id,
+    const char *remote_repo_name, int verbosity, struct got_repository *repo)
+{
+	const struct got_error *err;
+	char *remote_refname;
+
+	if (strncmp("refs/", refname, 5) == 0)
+		refname += 5;
+
+	if (asprintf(&remote_refname, "refs/remotes/%s/%s",
+	    remote_repo_name, refname) == -1)
+		return got_error_from_errno("asprintf");
+
+	err = create_ref(remote_refname, id, verbosity, repo);
+	free(remote_refname);
+	return err;
+}
+
+static const struct got_error *
 cmd_clone(int argc, char *argv[])
 {
 	const struct got_error *error = NULL;
@@ -968,7 +1027,7 @@ cmd_clone(int argc, char *argv[])
 	char *default_destdir = NULL, *id_str = NULL;
 	const char *repo_path;
 	struct got_repository *repo = NULL;
-	struct got_pathlist_head refs, symrefs, wanted_branches;
+	struct got_pathlist_head refs, symrefs, wanted_branches, wanted_refs;
 	struct got_pathlist_entry *pe;
 	struct got_object_id *pack_hash = NULL;
 	int ch, fetchfd = -1, fetchstatus;
@@ -986,8 +1045,9 @@ cmd_clone(int argc, char *argv[])
 	TAILQ_INIT(&refs);
 	TAILQ_INIT(&symrefs);
 	TAILQ_INIT(&wanted_branches);
+	TAILQ_INIT(&wanted_refs);
 
-	while ((ch = getopt(argc, argv, "ab:lmvq")) != -1) {
+	while ((ch = getopt(argc, argv, "ab:lmvqR:")) != -1) {
 		switch (ch) {
 		case 'a':
 			fetch_all_branches = 1;
@@ -1013,6 +1073,12 @@ cmd_clone(int argc, char *argv[])
 		case 'q':
 			verbosity = -1;
 			break;
+		case 'R':
+			error = got_pathlist_append(&wanted_refs,
+			    optarg, NULL);
+			if (error)
+				return error;
+			break;
 		default:
 			usage_clone();
 			break;
@@ -1032,6 +1098,8 @@ cmd_clone(int argc, char *argv[])
 			errx(1, "-l and -m options are mutually exclusive");
 		if (verbosity == -1)
 			errx(1, "-l and -q options are mutually exclusive");
+		if (!TAILQ_EMPTY(&wanted_refs))
+			errx(1, "-l and -R options are mutually exclusive");
 	}
 
 	uri = argv[0];
@@ -1124,8 +1192,9 @@ cmd_clone(int argc, char *argv[])
 	fpa.verbosity = verbosity;
 	error = got_fetch_pack(&pack_hash, &refs, &symrefs,
 	    GOT_FETCH_DEFAULT_REMOTE_NAME, mirror_references,
-	    fetch_all_branches, &wanted_branches, list_refs_only,
-	    verbosity, fetchfd, repo, fetch_progress, &fpa);
+	    fetch_all_branches, &wanted_branches, &wanted_refs,
+	    list_refs_only, verbosity, fetchfd, repo,
+	    fetch_progress, &fpa);
 	if (error)
 		goto done;
 
@@ -1146,6 +1215,16 @@ cmd_clone(int argc, char *argv[])
 		const char *refname = pe->path;
 		struct got_object_id *id = pe->data;
 		char *remote_refname;
+
+		if (is_wanted_ref(&wanted_refs, refname) &&
+		    !mirror_references) {
+			error = create_wanted_ref(refname, id,
+			    GOT_FETCH_DEFAULT_REMOTE_NAME,
+			    verbosity - 1, repo);
+			if (error)
+				goto done;
+			continue;
+		}
 
 		error = create_ref(refname, id, verbosity - 1, repo);
 		if (error)
@@ -1308,6 +1387,7 @@ done:
 	}
 	got_pathlist_free(&symrefs);
 	got_pathlist_free(&wanted_branches);
+	got_pathlist_free(&wanted_refs);
 	free(pack_hash);
 	free(proto);
 	free(host);
@@ -1385,7 +1465,8 @@ __dead static void
 usage_fetch(void)
 {
 	fprintf(stderr, "usage: %s fetch [-a] [-b branch] [-d] [-l] "
-	    "[-r repository-path] [-t] [-q] [-v] [remote-repository-name]\n",
+	    "[-r repository-path] [-t] [-q] [-v] [-R reference] "
+	    "[remote-repository-name]\n",
 	    getprogname());
 	exit(1);
 }
@@ -1438,7 +1519,36 @@ delete_missing_refs(struct got_pathlist_head *their_re
 			    got_ref_get_name(re->ref), id_str);
 		}
 	}
+
+	return err;
+}
+
+static const struct got_error *
+update_wanted_ref(const char *refname, struct got_object_id *id,
+    const char *remote_repo_name, int verbosity, struct got_repository *repo)
+{
+	const struct got_error *err;
+	char *remote_refname;
+	struct got_reference *ref;
+
+	if (strncmp("refs/", refname, 5) == 0)
+		refname += 5;
+
+	if (asprintf(&remote_refname, "refs/remotes/%s/%s",
+	    remote_repo_name, refname) == -1)
+		return got_error_from_errno("asprintf");
 
+	err = got_ref_open(&ref, repo, remote_refname, 0);
+	if (err) {
+		if (err->code != GOT_ERR_NOT_REF)
+			goto done;
+		err = create_ref(remote_refname, id, verbosity, repo);
+	} else {
+		err = update_ref(ref, id, 0, verbosity, repo);
+		got_ref_close(ref);
+	}
+done:
+	free(remote_refname);
 	return err;
 }
 
@@ -1455,7 +1565,7 @@ cmd_fetch(int argc, char *argv[])
 	char *id_str = NULL;
 	struct got_repository *repo = NULL;
 	struct got_worktree *worktree = NULL;
-	struct got_pathlist_head refs, symrefs, wanted_branches;
+	struct got_pathlist_head refs, symrefs, wanted_branches, wanted_refs;
 	struct got_pathlist_entry *pe;
 	struct got_object_id *pack_hash = NULL;
 	int i, ch, fetchfd = -1, fetchstatus;
@@ -1467,8 +1577,9 @@ cmd_fetch(int argc, char *argv[])
 	TAILQ_INIT(&refs);
 	TAILQ_INIT(&symrefs);
 	TAILQ_INIT(&wanted_branches);
+	TAILQ_INIT(&wanted_refs);
 
-	while ((ch = getopt(argc, argv, "ab:dlr:tvq")) != -1) {
+	while ((ch = getopt(argc, argv, "ab:dlr:tvqR:")) != -1) {
 		switch (ch) {
 		case 'a':
 			fetch_all_branches = 1;
@@ -1504,6 +1615,12 @@ cmd_fetch(int argc, char *argv[])
 		case 'q':
 			verbosity = -1;
 			break;
+		case 'R':
+			error = got_pathlist_append(&wanted_refs,
+			    optarg, NULL);
+			if (error)
+				return error;
+			break;
 		default:
 			usage_fetch();
 			break;
@@ -1628,7 +1745,8 @@ cmd_fetch(int argc, char *argv[])
 	fpa.verbosity = verbosity;
 	error = got_fetch_pack(&pack_hash, &refs, &symrefs, remote->name,
 	    remote->mirror_references, fetch_all_branches, &wanted_branches,
-	    list_refs_only, verbosity, fetchfd, repo, fetch_progress, &fpa);
+	    &wanted_refs, list_refs_only, verbosity, fetchfd, repo,
+	    fetch_progress, &fpa);
 	if (error)
 		goto done;
 
@@ -1661,6 +1779,15 @@ cmd_fetch(int argc, char *argv[])
 		struct got_reference *ref;
 		char *remote_refname;
 
+		if (is_wanted_ref(&wanted_refs, refname) &&
+		    !remote->mirror_references) {
+			error = update_wanted_ref(refname, id,
+			    remote->name, verbosity, repo);
+			if (error)
+				goto done;
+			continue;
+		}
+
 		if (remote->mirror_references ||
 		    strncmp("refs/tags/", refname, 10) == 0) {
 			error = got_ref_open(&ref, repo, refname, 0);
@@ -1740,6 +1867,7 @@ done:
 	}
 	got_pathlist_free(&symrefs);
 	got_pathlist_free(&wanted_branches);
+	got_pathlist_free(&wanted_refs);
 	free(id_str);
 	free(cwd);
 	free(repo_path);
blob - 7d8cd87f9386f24c86d96e95cac445f281b1b452
blob + dd38ef5eac3a25d0396293542c1569e376556118
--- include/got_error.h
+++ include/got_error.h
@@ -139,6 +139,7 @@
 #define GOT_ERR_BAD_PACKET	122
 #define GOT_ERR_NO_REMOTE	123
 #define GOT_ERR_FETCH_NO_BRANCH	124
+#define GOT_ERR_FETCH_BAD_REF	125
 
 static const struct got_error {
 	int code;
@@ -284,6 +285,7 @@ static const struct got_error {
 	{ GOT_ERR_BAD_PACKET, "bad packet received" },
 	{ GOT_ERR_NO_REMOTE, "remote repository not found" },
 	{ GOT_ERR_FETCH_NO_BRANCH, "could not find any branches to fetch" },
+	{ GOT_ERR_FETCH_BAD_REF, "reference cannot be fetched" },
 };
 
 /*
blob - 42a3f6350fdf629aaf696b69bd8d27386e5ef3fa
blob + ec4d2dc496a45f2a0804737eeb32ad4991f4c8ba
--- include/got_fetch.h
+++ include/got_fetch.h
@@ -66,5 +66,5 @@ typedef const struct got_error *(*got_fetch_progress_c
  */
 const struct got_error *got_fetch_pack(struct got_object_id **,
 	struct got_pathlist_head *, struct got_pathlist_head *, const char *,
-	int, int, struct got_pathlist_head *, int, int, int, 
-	struct got_repository *, got_fetch_progress_cb, void *);
+	int, int, struct got_pathlist_head *, struct got_pathlist_head *,
+	int, int, int, struct got_repository *, got_fetch_progress_cb, void *);
blob - 4442a20cef8658f67a2edc55456e82264944e572
blob + 9cdf6781c6716b721ebbe42fd37c027823ee6ba8
--- lib/fetch.c
+++ lib/fetch.c
@@ -391,8 +391,9 @@ const struct got_error*
 got_fetch_pack(struct got_object_id **pack_hash, struct got_pathlist_head *refs,
     struct got_pathlist_head *symrefs, const char *remote_name,
     int mirror_references, int fetch_all_branches,
-    struct got_pathlist_head *wanted_branches, int list_refs_only,
-    int verbosity, int fetchfd, struct got_repository *repo,
+    struct got_pathlist_head *wanted_branches,
+    struct got_pathlist_head *wanted_refs, int list_refs_only, int verbosity,
+    int fetchfd, struct got_repository *repo,
     got_fetch_progress_cb progress_cb, void *progress_arg)
 {
 	int imsg_fetchfds[2], imsg_idxfds[2];
@@ -417,6 +418,19 @@ got_fetch_pack(struct got_object_id **pack_hash, struc
 	char *path;
 	char *progress = NULL;
 
+	/*
+	 * Prevent fetching of references that won't make any
+	 * sense outside of the remote repository's context.
+	 */
+	TAILQ_FOREACH(pe, wanted_refs, entry) {
+		const char *refname = pe->path;
+		if (strncmp(refname, "refs/got/", 9) == 0 ||
+		    strncmp(refname, "got/", 4) == 0 ||
+		    strncmp(refname, "refs/remotes/", 13) == 0 ||
+		    strncmp(refname, "remotes/", 8) == 0)
+			return got_error_path(refname, GOT_ERR_FETCH_BAD_REF);
+	}
+
 	if (!list_refs_only)
 		repo_path = got_repo_get_path_git_dir(repo);
 
@@ -577,7 +591,8 @@ got_fetch_pack(struct got_object_id **pack_hash, struc
 		goto done;
 	}
 	err = got_privsep_send_fetch_req(&fetchibuf, nfetchfd, &have_refs,
-	    fetch_all_branches, wanted_branches, list_refs_only, verbosity);
+	    fetch_all_branches, wanted_branches, wanted_refs,
+	    list_refs_only, verbosity);
 	if (err != NULL)
 		goto done;
 	nfetchfd = -1;
blob - 8c31f36d79e4b53047ca02d2b0f280b70f84eaa9
blob + dcfb30c7f3aef64701b8fae8369a9a04ded29115
--- lib/got_lib_privsep.h
+++ lib/got_lib_privsep.h
@@ -112,6 +112,7 @@ enum got_imsg_type {
 	GOT_IMSG_FETCH_REQUEST,
 	GOT_IMSG_FETCH_HAVE_REF,
 	GOT_IMSG_FETCH_WANTED_BRANCH,
+	GOT_IMSG_FETCH_WANTED_REF,
 	GOT_IMSG_FETCH_OUTFD,
 	GOT_IMSG_FETCH_SYMREFS,
 	GOT_IMSG_FETCH_REF,
@@ -250,6 +251,12 @@ struct got_imsg_fetch_have_ref {
 
 /* Structure for GOT_IMSG_FETCH_WANTED_BRANCH data. */
 struct got_imsg_fetch_wanted_branch {
+	size_t name_len;
+	/* Followed by name_len data bytes. */
+} __attribute__((__packed__));
+
+/* Structure for GOT_IMSG_FETCH_WANTED_REF data. */
+struct got_imsg_fetch_wanted_ref {
 	size_t name_len;
 	/* Followed by name_len data bytes. */
 } __attribute__((__packed__));
@@ -261,8 +268,10 @@ struct got_imsg_fetch_request {
 	int verbosity;
 	size_t n_have_refs;
 	size_t n_wanted_branches;
+	size_t n_wanted_refs;
 	/* Followed by n_have_refs GOT_IMSG_FETCH_HAVE_REF messages. */
 	/* Followed by n_wanted_branches times GOT_IMSG_FETCH_WANTED_BRANCH. */
+	/* Followed by n_wanted_refs times GOT_IMSG_FETCH_WANTED_REF. */
 } __attribute__((__packed__));
 
 /* Structures for GOT_IMSG_FETCH_SYMREFS data. */
@@ -403,7 +412,8 @@ const struct got_error *got_privsep_send_index_pack_do
 const struct got_error *got_privsep_recv_index_progress(int *, int *, int *,
     int *, int *, struct imsgbuf *ibuf);
 const struct got_error *got_privsep_send_fetch_req(struct imsgbuf *, int,
-    struct got_pathlist_head *, int, struct got_pathlist_head *, int, int);
+    struct got_pathlist_head *, int, struct got_pathlist_head *,
+    struct got_pathlist_head *, int, int);
 const struct got_error *got_privsep_send_fetch_outfd(struct imsgbuf *, int);
 const struct got_error *got_privsep_send_fetch_symrefs(struct imsgbuf *,
     struct got_pathlist_head *);
blob - 8c47b66e6079d8494ada12b05443c7d960089ae5
blob + 3e7631e45cb6f10d8c034f2706fc258bd7aeea54
--- lib/privsep.c
+++ lib/privsep.c
@@ -413,8 +413,8 @@ got_privsep_send_obj(struct imsgbuf *ibuf, struct got_
 const struct got_error *
 got_privsep_send_fetch_req(struct imsgbuf *ibuf, int fd,
    struct got_pathlist_head *have_refs, int fetch_all_branches,
-   struct got_pathlist_head *wanted_branches, int list_refs_only,
-   int verbosity)
+   struct got_pathlist_head *wanted_branches,
+   struct got_pathlist_head *wanted_refs, int list_refs_only, int verbosity)
 {
 	const struct got_error *err = NULL;
 	struct ibuf *wbuf;
@@ -430,6 +430,8 @@ got_privsep_send_fetch_req(struct imsgbuf *ibuf, int f
 		fetchreq.n_have_refs++;
 	TAILQ_FOREACH(pe, wanted_branches, entry)
 		fetchreq.n_wanted_branches++;
+	TAILQ_FOREACH(pe, wanted_refs, entry)
+		fetchreq.n_wanted_refs++;
 	len = sizeof(struct got_imsg_fetch_request);
 	if (len >= MAX_IMSGSIZE - IMSG_HEADER_SIZE) {
 		close(fd);
@@ -514,6 +516,39 @@ got_privsep_send_fetch_req(struct imsgbuf *ibuf, int f
 			return err;
 	}
 
+	TAILQ_FOREACH(pe, wanted_refs, entry) {
+		const char *name = pe->path;
+		size_t name_len = pe->path_len;
+
+		len = sizeof(struct got_imsg_fetch_wanted_ref) + name_len;
+		wbuf = imsg_create(ibuf, GOT_IMSG_FETCH_WANTED_REF, 0, 0,
+		    len);
+		if (wbuf == NULL)
+			return got_error_from_errno(
+			     "imsg_create FETCH_WANTED_REF");
+
+		/* Keep in sync with struct got_imsg_fetch_wanted_ref! */
+		if (imsg_add(wbuf, &name_len, sizeof(name_len)) == -1) {
+			err = got_error_from_errno(
+			    "imsg_add FETCH_WANTED_REF");
+			ibuf_free(wbuf);
+			return err;
+		}
+		if (imsg_add(wbuf, name, name_len) == -1) {
+			err = got_error_from_errno(
+			     "imsg_add FETCH_WANTED_REF");
+			ibuf_free(wbuf);
+			return err;
+		}
+
+		wbuf->fd = -1;
+		imsg_close(ibuf, wbuf);
+		err = flush_imsg(ibuf);
+		if (err)
+			return err;
+	}
+
+
 	return NULL;
 
 }
blob - 70f8e799b2fd3c6d65ef1ffdb6df9a97c48d9579
blob + 8bedceae325de82abd4bbef6715118687872c598
--- libexec/got-fetch-pack/got-fetch-pack.c
+++ libexec/got-fetch-pack/got-fetch-pack.c
@@ -249,6 +249,33 @@ match_branch(const char *branch, const char *wanted_br
 		wanted_branch += 11;
 
 	return (strcmp(branch + 11, wanted_branch) == 0);
+}
+
+static int
+match_wanted_ref(const char *refname, const char *wanted_ref)
+{
+	if (strncmp(refname, "refs/", 5) != 0)
+		return 0;
+	refname += 5;
+
+	/*
+	 * Prevent fetching of references that won't make any
+	 * sense outside of the remote repository's context.
+	 */
+	if (strncmp(refname, "got/", 4) == 0)
+		return 0;
+	if (strncmp(refname, "remotes/", 8) == 0)
+		return 0;
+
+	if (strncmp(wanted_ref, "refs/", 5) == 0)
+		wanted_ref += 5;
+
+	/* Allow prefix match. */
+	if (got_path_is_child(refname, wanted_ref, strlen(wanted_ref)))
+		return 1;
+
+	/* Allow exact match. */
+	return (strcmp(refname, wanted_ref) == 0);
 }
 
 static const struct got_error *
@@ -488,7 +515,8 @@ fetch_error(const char *buf, size_t len)
 static const struct got_error *
 fetch_pack(int fd, int packfd, struct got_object_id *packid,
     struct got_pathlist_head *have_refs, int fetch_all_branches,
-    struct got_pathlist_head *wanted_branches, int list_refs_only,
+    struct got_pathlist_head *wanted_branches,
+    struct got_pathlist_head *wanted_refs, int list_refs_only,
     struct imsgbuf *ibuf)
 {
 	const struct got_error *err = NULL;
@@ -594,7 +622,21 @@ fetch_pack(int fd, int packfd, struct got_object_id *p
 				found_branch = 1;
 			}
 		} else if (strncmp(refname, "refs/tags/", 10) != 0) {
-			if (!list_refs_only) {
+			if (!TAILQ_EMPTY(wanted_refs)) {
+				TAILQ_FOREACH(pe, wanted_refs, entry) {
+					if (match_wanted_ref(refname, pe->path))
+						break;
+				}
+				if (pe == NULL) {
+					if (chattygot) {
+						fprintf(stderr,
+						    "%s: ignoring %s\n",
+						    getprogname(), refname);
+					}
+					continue;
+				}
+				found_branch = 1;
+			} else if (!list_refs_only) {
 				if (chattygot) {
 					fprintf(stderr, "%s: ignoring %s\n",
 					    getprogname(), refname);
@@ -871,10 +913,12 @@ main(int argc, char **argv)
 	struct imsg imsg;
 	struct got_pathlist_head have_refs;
 	struct got_pathlist_head wanted_branches;
+	struct got_pathlist_head wanted_refs;
 	struct got_pathlist_entry *pe;
 	struct got_imsg_fetch_request fetch_req;
 	struct got_imsg_fetch_have_ref href;
 	struct got_imsg_fetch_wanted_branch wbranch;
+	struct got_imsg_fetch_wanted_ref wref;
 	size_t datalen;
 #if 0
 	static int attached;
@@ -884,6 +928,7 @@ main(int argc, char **argv)
 
 	TAILQ_INIT(&have_refs);
 	TAILQ_INIT(&wanted_branches);
+	TAILQ_INIT(&wanted_refs);
 
 	imsg_init(&ibuf, GOT_IMSG_FD_CHILD);
 #ifndef PROFILE
@@ -1008,6 +1053,47 @@ main(int argc, char **argv)
 		imsg_free(&imsg);
 	}
 
+	for (i = 0; i < fetch_req.n_wanted_refs; i++) {
+		char *refname;
+
+		if ((err = got_privsep_recv_imsg(&imsg, &ibuf, 0)) != 0) {
+			if (err->code == GOT_ERR_PRIVSEP_PIPE)
+				err = NULL;
+			goto done;
+		}
+		if (imsg.hdr.type == GOT_IMSG_STOP)
+			goto done;
+		if (imsg.hdr.type != GOT_IMSG_FETCH_WANTED_REF) {
+			err = got_error(GOT_ERR_PRIVSEP_MSG);
+			goto done;
+		}
+		datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
+		if (datalen < sizeof(wref)) {
+			err = got_error(GOT_ERR_PRIVSEP_LEN);
+			goto done;
+		}
+		memcpy(&wref, imsg.data, sizeof(wref));
+		if (datalen - sizeof(wref) < wref.name_len) {
+			err = got_error(GOT_ERR_PRIVSEP_LEN);
+			goto done;
+		}
+		refname = malloc(wref.name_len + 1);
+		if (refname == NULL) {
+			err = got_error_from_errno("malloc");
+			goto done;
+		}
+		memcpy(refname, imsg.data + sizeof(wref), wref.name_len);
+		refname[wref.name_len] = '\0';
+
+		err = got_pathlist_append(&wanted_refs, refname, NULL);
+		if (err) {
+			free(refname);
+			goto done;
+		}
+
+		imsg_free(&imsg);
+	}
+
 	if ((err = got_privsep_recv_imsg(&imsg, &ibuf, 0)) != 0) {
 		if (err->code == GOT_ERR_PRIVSEP_PIPE)
 			err = NULL;
@@ -1027,7 +1113,7 @@ main(int argc, char **argv)
 
 	err = fetch_pack(fetchfd, packfd, &packid, &have_refs,
 	    fetch_req.fetch_all_branches, &wanted_branches,
-	    fetch_req.list_refs_only, &ibuf);
+	    &wanted_refs, fetch_req.list_refs_only, &ibuf);
 done:
 	TAILQ_FOREACH(pe, &have_refs, entry) {
 		free((char *)pe->path);
blob - b408166d9ed58cf1a15d4b908ad1a3897a3f44e9
blob + f3a8329ac8336d70b30d8bb5d62f5fa383572eba
--- regress/cmdline/clone.sh
+++ regress/cmdline/clone.sh
@@ -258,10 +258,121 @@ function test_clone_mirror_all {
 	fi
 	test_done "$testroot" "$ret"
 }
+
+function test_clone_reference {
+	local testroot=`test_init clone_reference`
+	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 refs/hoo/boo/zoo $commit_id
+	got tag -r $testroot/repo -c $commit_id -m tag "1.0" >/dev/null
+	local tag_id=`got ref -r $testroot/repo -l \
+		| grep "^refs/tags/$tag" | tr -d ' ' | cut -d: -f2`
+
+	got clone -q -R hoo $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 ref -l -r $testroot/repo-clone > $testroot/stdout
+
+	echo "HEAD: refs/heads/master" > $testroot/stdout.expected
+	echo "refs/heads/master: $commit_id" >> $testroot/stdout.expected
+	echo "refs/remotes/origin/hoo/boo/zoo: $commit_id" \
+		>> $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"
+}
+
+function test_clone_branch_and_reference {
+	local testroot=`test_init clone_reference`
+	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 refs/hoo/boo/zoo $commit_id
+	got tag -r $testroot/repo -c $commit_id -m tag "1.0" >/dev/null
+	local tag_id=`got ref -r $testroot/repo -l \
+		| grep "^refs/tags/$tag" | tr -d ' ' | cut -d: -f2`
+
+	got clone -q -R hoo/boo/zoo -b foo $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 ref -l -r $testroot/repo-clone > $testroot/stdout
+
+	echo "HEAD: refs/heads/foo" > $testroot/stdout.expected
+	echo "refs/heads/foo: $commit_id" >> $testroot/stdout.expected
+	echo "refs/remotes/origin/foo: $commit_id" \
+		>> $testroot/stdout.expected
+	echo "refs/remotes/origin/hoo/boo/zoo: $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"
+}
+
+function test_clone_reference_mirror {
+	local testroot=`test_init clone_reference_mirror`
+	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 refs/hoo/boo/zoo $commit_id
+	got tag -r $testroot/repo -c $commit_id -m tag "1.0" >/dev/null
+	local tag_id=`got ref -r $testroot/repo -l \
+		| grep "^refs/tags/$tag" | tr -d ' ' | cut -d: -f2`
+
+	got clone -q -R hoo -m $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 ref -l -r $testroot/repo-clone > $testroot/stdout
+
+	echo "HEAD: refs/heads/master" > $testroot/stdout.expected
+	echo "refs/heads/master: $commit_id" >> $testroot/stdout.expected
+	echo "refs/hoo/boo/zoo: $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"
+}
+
 run_test test_clone_basic
 run_test test_clone_list
 run_test test_clone_branch
 run_test test_clone_all
 run_test test_clone_mirror
 run_test test_clone_mirror_all
+run_test test_clone_reference
+run_test test_clone_branch_and_reference
+run_test test_clone_reference_mirror
blob - 374586a754ffabebdadd00bdc20a010dba7fbdd6
blob + ce08ae3c3d285ab16881c6e4ebfe33f5f04117f3
--- regress/cmdline/fetch.sh
+++ regress/cmdline/fetch.sh
@@ -641,7 +641,93 @@ function test_fetch_update_tag {
 	fi
 	test_done "$testroot" "$ret"
 }
+
+function test_fetch_reference {
+	local testroot=`test_init fetch_reference`
+	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 branch -r $testroot/repo -c $commit_id foo
+	got ref -r $testroot/repo refs/hoo/boo/zoo $commit_id
+	got tag -r $testroot/repo -c $commit_id -m tag "1.0" >/dev/null
+	local tag_id=`got ref -r $testroot/repo -l \
+		| grep "^refs/tags/$tag" | tr -d ' ' | cut -d: -f2`
+
+	echo "modified alpha on master" > $testroot/repo/alpha
+	git_commit $testroot/repo -m "modified alpha"
+	local commit_id2=`git_show_head $testroot/repo`
+
+	(cd $testroot/repo && git checkout -q foo)
+	echo "modified alpha on foo" > $testroot/repo/alpha
+	git_commit $testroot/repo -m "modified alpha"
+	local commit_id3=`git_show_head $testroot/repo`
+	(cd $testroot/repo && git checkout -q master)
+
+	got fetch -q -r $testroot/repo-clone -R refs/remotes/origin/main \
+		> $testroot/stdout 2> $testroot/stderr
+	ret="$?"
+	if [ "$ret" == "0" ]; then
+		echo "got fetch command succeeded unexpectedly" >&2
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo -n > $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
+
+	echo "got: refs/remotes/origin/main: reference cannot be fetched" \
+		> $testroot/stderr.expected
+
+	cmp -s $testroot/stderr $testroot/stderr.expected
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stderr.expected $testroot/stderr
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	got fetch -q -r $testroot/repo-clone -R refs/hoo
+	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-clone > $testroot/stdout
+
+	echo "HEAD: refs/heads/master" > $testroot/stdout.expected
+	echo "refs/heads/master: $commit_id" >> $testroot/stdout.expected
+	echo "refs/remotes/origin/hoo/boo/zoo: $commit_id" \
+		>> $testroot/stdout.expected
+	echo "refs/remotes/origin/master: $commit_id2" \
+		>> $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"
+
+}
+
 run_test test_fetch_basic
 run_test test_fetch_list
 run_test test_fetch_branch
@@ -649,3 +735,4 @@ run_test test_fetch_all
 run_test test_fetch_empty_packfile
 run_test test_fetch_delete_branch
 run_test test_fetch_update_tag
+run_test test_fetch_reference