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

From:
Stefan Sperling <stsp@stsp.name>
Subject:
Re: improve got fetch error reporting
To:
Mark Jamsek <mark@jamsek.com>
Cc:
gameoftrees@openbsd.org
Date:
Mon, 6 Feb 2023 21:54:19 +0100

Download raw body.

Thread
On Mon, Feb 06, 2023 at 03:09:08PM +1100, Mark Jamsek wrote:
> Yes, I like this much better! I was a little concerned by erroring out
> if fetching from a worktree with a checked-out branch that doesn't exist
> on the server. I prefer this behaviour. Thank you for unwrapping this :)

Yet more unwrapping. This is more complicated than expected.
I've rewritten some of the logic in got-fetch-pack to make it easier
to follow.

I still need to add test coverage for the edge-case -R fix mentioned
in the last paragraph, but I am running out of steam for now.

 fix interaction of 'got fetch -b', got.conf, and work tree
 
 Without branches in got.conf for a remote, and without -b/-R options,
 the fallback to HEAD would only work when not invoked in a work tree.
 
 With this fix 'got fetch' should behave as described in the man page.
 The -b option now overrides both got.conf and the fallback to the work
 tree's branch.
 And fallback to HEAD works as expected when invoked in a repository.
 
 Also, do not strictly require remote repositories to provide a branch
 from the refs/heads/ namespace. In such cases users should be able to
 use -R to select something to fetch.
 
diff c9003c52fc7f70dd988402560adcd22709e74800 3d7ad4c9f65020b55ebd28d8bc0e6d499ec13fc0
commit - c9003c52fc7f70dd988402560adcd22709e74800
commit + 3d7ad4c9f65020b55ebd28d8bc0e6d499ec13fc0
blob - a32137a402ce476d4300983fe5e9e17d680c8ac0
blob + 83d6a791c83d96ed0e79da912f70428804dfcb3a
--- got/got.c
+++ got/got.c
@@ -1725,7 +1725,7 @@ cmd_clone(int argc, char *argv[])
 	error = got_fetch_pack(&pack_hash, &refs, &symrefs,
 	    GOT_FETCH_DEFAULT_REMOTE_NAME, mirror_references,
 	    fetch_all_branches, &wanted_branches, &wanted_refs,
-	    list_refs_only, verbosity, fetchfd, repo,
+	    list_refs_only, verbosity, fetchfd, repo, NULL,
 	    fetch_progress, &fpa);
 	if (error)
 		goto done;
@@ -2296,7 +2296,7 @@ cmd_fetch(int argc, char *argv[])
 	struct got_fetch_progress_arg fpa;
 	int verbosity = 0, fetch_all_branches = 0, list_refs_only = 0;
 	int delete_refs = 0, replace_tags = 0, delete_remote = 0;
-	int *pack_fds = NULL;
+	int *pack_fds = NULL, have_bflag = 0;
 
 	TAILQ_INIT(&refs);
 	TAILQ_INIT(&symrefs);
@@ -2313,6 +2313,7 @@ cmd_fetch(int argc, char *argv[])
 			    optarg, NULL);
 			if (error)
 				return error;
+			have_bflag = 1;
 			break;
 		case 'd':
 			delete_refs = 1;
@@ -2480,12 +2481,6 @@ cmd_fetch(int argc, char *argv[])
 			if (error)
 				goto done;
 		}
-		if (worktree) {
-			error = got_pathlist_append(&wanted_branches,
-			    got_worktree_get_head_ref_name(worktree), NULL);
-			if (error)
-				goto done;
-		}
 	}
 	if (TAILQ_EMPTY(&wanted_refs)) {
 		for (i = 0; i < remote->nfetch_refs; i++) {
@@ -2554,6 +2549,8 @@ cmd_fetch(int argc, char *argv[])
 	error = got_fetch_pack(&pack_hash, &refs, &symrefs, remote->name,
 	    remote->mirror_references, fetch_all_branches, &wanted_branches,
 	    &wanted_refs, list_refs_only, verbosity, fetchfd, repo,
+	    (worktree != NULL && !have_bflag) ?
+	    got_worktree_get_head_ref_name(worktree) : NULL,
 	    fetch_progress, &fpa);
 	if (error)
 		goto done;
blob - 49f9fd5da6c527fcc00a29827b61df267135112e
blob + 5703d1fbe97e2c2bfcb5c6c2095c9653f5ee5e6e
--- include/got_fetch.h
+++ include/got_fetch.h
@@ -46,4 +46,5 @@ const struct got_error *got_fetch_pack(struct got_obje
 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 *, struct got_pathlist_head *,
-	int, int, int, struct got_repository *, got_fetch_progress_cb, void *);
+	int, int, int, struct got_repository *, const char *,
+	got_fetch_progress_cb, void *);
blob - a006844341e6b3ee524360e3929fe3eee0536090
blob + bfa126b8fabcca280af94961030dcae5aae3e6e6
--- lib/fetch.c
+++ lib/fetch.c
@@ -104,7 +104,7 @@ got_fetch_pack(struct got_object_id **pack_hash, struc
     int mirror_references, int fetch_all_branches,
     struct got_pathlist_head *wanted_branches,
     struct got_pathlist_head *wanted_refs, int list_refs_only, int verbosity,
-    int fetchfd, struct got_repository *repo,
+    int fetchfd, struct got_repository *repo, const char *worktree_refname,
     got_fetch_progress_cb progress_cb, void *progress_arg)
 {
 	size_t i;
@@ -261,7 +261,7 @@ got_fetch_pack(struct got_object_id **pack_hash, struc
 	}
 	err = got_privsep_send_fetch_req(&fetchibuf, nfetchfd, &have_refs,
 	    fetch_all_branches, wanted_branches, wanted_refs,
-	    list_refs_only, verbosity);
+	    list_refs_only, worktree_refname, verbosity);
 	if (err != NULL)
 		goto done;
 	nfetchfd = -1;
blob - 076cab5adad635b8e7ea46af68a14510dced4da4
blob + 09e7cb7ce6e9cf826f667d69280ad2dab4b4d161
--- lib/got_lib_privsep.h
+++ lib/got_lib_privsep.h
@@ -407,9 +407,11 @@ struct got_imsg_fetch_request {
 	int fetch_all_branches;
 	int list_refs_only;
 	int verbosity;
+	size_t worktree_branch_len;
 	size_t n_have_refs;
 	size_t n_wanted_branches;
 	size_t n_wanted_refs;
+	/* Followed by worktree_branch_len bytes of reference name. */
 	/* 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. */
@@ -698,7 +700,7 @@ const struct got_error *got_privsep_send_fetch_req(str
     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 *,
-    struct got_pathlist_head *, int, int);
+    struct got_pathlist_head *, int, const char *, int);
 const struct got_error *got_privsep_send_fetch_outfd(struct imsgbuf *, int);
 const struct got_error *got_privsep_recv_fetch_progress(int *,
     struct got_object_id **, char **, struct got_pathlist_head *, char **,
blob - ddb1e595d753eddc725ce0bbb056d1dc8236fc07
blob + d99805a6e345d6d142e6a571b2f787068b19ef76
--- lib/privsep.c
+++ lib/privsep.c
@@ -531,7 +531,8 @@ got_privsep_send_fetch_req(struct imsgbuf *ibuf, int f
 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,
-    struct got_pathlist_head *wanted_refs, int list_refs_only, int verbosity)
+    struct got_pathlist_head *wanted_refs, int list_refs_only,
+    const char *worktree_branch, int verbosity)
 {
 	const struct got_error *err = NULL;
 	struct ibuf *wbuf;
@@ -539,27 +540,42 @@ got_privsep_send_fetch_req(struct imsgbuf *ibuf, int f
 	struct got_pathlist_entry *pe;
 	struct got_imsg_fetch_request fetchreq;
 
+	if (worktree_branch)
+		len = sizeof(fetchreq) + strlen(worktree_branch);
+	else
+		len = sizeof(fetchreq);
+
+	if (len >= MAX_IMSGSIZE - IMSG_HEADER_SIZE) {
+		close(fd);
+		return got_error(GOT_ERR_NO_SPACE);
+	}
+
+	wbuf = imsg_create(ibuf, GOT_IMSG_FETCH_REQUEST, 0, 0, len);
+	if (wbuf == NULL)
+		return got_error_from_errno("imsg_create FETCH_HAVE_REF");
+
 	memset(&fetchreq, 0, sizeof(fetchreq));
 	fetchreq.fetch_all_branches = fetch_all_branches;
 	fetchreq.list_refs_only = list_refs_only;
 	fetchreq.verbosity = verbosity;
+	if (worktree_branch != NULL)
+		fetchreq.worktree_branch_len = strlen(worktree_branch);
 	TAILQ_FOREACH(pe, have_refs, entry)
 		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);
-		return got_error(GOT_ERR_NO_SPACE);
+	if (imsg_add(wbuf, &fetchreq, sizeof(fetchreq)) == -1)
+		return got_error_from_errno("imsg_add FETCH_REQUEST");
+	if (worktree_branch) {
+		if (imsg_add(wbuf, worktree_branch,
+		    strlen(worktree_branch))== -1)
+			return got_error_from_errno("imsg_add FETCH_REQUEST");
 	}
+	wbuf->fd = fd;
+	imsg_close(ibuf, wbuf);
 
-	if (imsg_compose(ibuf, GOT_IMSG_FETCH_REQUEST, 0, 0, fd,
-	    &fetchreq, sizeof(fetchreq)) == -1)
-		return got_error_from_errno(
-		    "imsg_compose FETCH_SERVER_PROGRESS");
-
 	err = flush_imsg(ibuf);
 	if (err) {
 		close(fd);
blob - 77ba914fb971f5e412b57bd1633bda6ee0e1f26d
blob + 32778499296b895c8f8a0bd7f201ca9a637c6f87
--- libexec/got-fetch-pack/got-fetch-pack.c
+++ libexec/got-fetch-pack/got-fetch-pack.c
@@ -71,7 +71,7 @@ match_remote_ref(struct got_pathlist_head *have_refs,
 
 static void
 match_remote_ref(struct got_pathlist_head *have_refs,
-    struct got_object_id *my_id, char *refname)
+    struct got_object_id *my_id, const char *refname)
 {
 	struct got_pathlist_entry *pe;
 
@@ -292,11 +292,48 @@ fetch_pack(int fd, int packfd, uint8_t *pack_sha1,
 }
 
 static const struct got_error *
+fetch_ref(struct imsgbuf *ibuf, struct got_pathlist_head *have_refs,
+    struct got_object_id *have, struct got_object_id *want,
+    const char *refname, const char *id_str)
+{
+	const struct got_error *err;
+	char *theirs = NULL, *mine = NULL;
+
+	if (!got_parse_sha1_digest(want->sha1, id_str)) {
+		err = got_error(GOT_ERR_BAD_OBJ_ID_STR);
+		goto done;
+	}
+
+	match_remote_ref(have_refs, have, refname);
+	err = send_fetch_ref(ibuf, want, refname);
+	if (err)
+		goto done;
+
+	if (chattygot)
+		fprintf(stderr, "%s: %s will be fetched\n",
+		    getprogname(), refname);
+	if (chattygot > 1) {
+		err = got_object_id_str(&theirs, want);
+		if (err)
+			goto done;
+		err = got_object_id_str(&mine, have);
+		if (err)
+			goto done;
+		fprintf(stderr, "%s: remote: %s\n%s: local:  %s\n",
+		    getprogname(), theirs, getprogname(), mine);
+	}
+done:
+	free(theirs);
+	free(mine);
+	return err;
+}
+
+static const struct got_error *
 fetch_pack(int fd, int packfd, uint8_t *pack_sha1,
     struct got_pathlist_head *have_refs, int fetch_all_branches,
     struct got_pathlist_head *wanted_branches,
     struct got_pathlist_head *wanted_refs, int list_refs_only,
-    struct imsgbuf *ibuf)
+    const char *worktree_branch, struct imsgbuf *ibuf)
 {
 	const struct got_error *err = NULL;
 	char buf[GOT_PKT_MAX];
@@ -305,7 +342,7 @@ fetch_pack(int fd, int packfd, uint8_t *pack_sha1,
 	int is_firstpkt = 1, nref = 0, refsz = 16;
 	int i, n, nwant = 0, nhave = 0, acked = 0;
 	off_t packsz = 0, last_reported_packsz = 0;
-	char *id_str = NULL, *refname = NULL;
+	char *id_str = NULL, *default_id_str = NULL, *refname = NULL;
 	char *server_capabilities = NULL, *my_capabilities = NULL;
 	const char *default_branch = NULL;
 	struct got_pathlist_head symrefs;
@@ -346,6 +383,21 @@ fetch_pack(int fd, int packfd, uint8_t *pack_sha1,
 		    &server_capabilities, buf, n);
 		if (err)
 			goto done;
+
+		if (refsz == nref + 1) {
+			refsz *= 2;
+			have = reallocarray(have, refsz, sizeof(have[0]));
+			if (have == NULL) {
+				err = got_error_from_errno("reallocarray");
+				goto done;
+			}
+			want = reallocarray(want, refsz, sizeof(want[0]));
+			if (want == NULL) {
+				err = got_error_from_errno("reallocarray");
+				goto done;
+			}
+		}
+
 		if (is_firstpkt) {
 			if (chattygot && server_capabilities[0] != '\0')
 				fprintf(stderr, "%s: server capabilities: %s\n",
@@ -383,107 +435,80 @@ fetch_pack(int fd, int packfd, uint8_t *pack_sha1,
 			}
 			continue;
 		}
-
-		if (strncmp(refname, "refs/heads/", 11) == 0) {
-			if (fetch_all_branches || list_refs_only) {
-				found_branch = 1;
-			} else if (!TAILQ_EMPTY(wanted_branches)) {
-				TAILQ_FOREACH(pe, wanted_branches, entry) {
-					if (match_branch(refname, pe->path))
-						break;
-				}
-				if (pe == NULL) {
-					if (chattygot) {
-						fprintf(stderr,
-						    "%s: ignoring %s\n",
-						    getprogname(), refname);
-					}
-					continue;
-				}
-				found_branch = 1;
-			} else if (default_branch != NULL) {
-				if (!match_branch(refname, default_branch)) {
-					if (chattygot) {
-						fprintf(stderr,
-						    "%s: ignoring %s\n",
-						    getprogname(), refname);
-					}
-					continue;
-				}
-				found_branch = 1;
-			}
-		} else if (strncmp(refname, "refs/tags/", 10) != 0) {
-			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);
-				}
-				continue;
-			}
-		}
-
-		if (refsz == nref + 1) {
-			refsz *= 2;
-			have = reallocarray(have, refsz, sizeof(have[0]));
-			if (have == NULL) {
-				err = got_error_from_errno("reallocarray");
+		if (default_branch && default_id_str == NULL &&
+		    strcmp(refname, default_branch) == 0) {
+			default_id_str = strdup(id_str);
+			if (default_id_str == NULL) {
+				err = got_error_from_errno("strdup");
 				goto done;
 			}
-			want = reallocarray(want, refsz, sizeof(want[0]));
-			if (want == NULL) {
-				err = got_error_from_errno("reallocarray");
-				goto done;
-			}
 		}
-		if (!got_parse_sha1_digest(want[nref].sha1, id_str)) {
-			err = got_error(GOT_ERR_BAD_OBJ_ID_STR);
-			goto done;
-		}
-		match_remote_ref(have_refs, &have[nref], refname);
-		err = send_fetch_ref(ibuf, &want[nref], refname);
-		if (err)
-			goto done;
 
-		if (chattygot)
-			fprintf(stderr, "%s: %s will be fetched\n",
-			    getprogname(), refname);
-		if (chattygot > 1) {
-			char *theirs, *mine;
-			err = got_object_id_str(&theirs, &want[nref]);
+		if (list_refs_only || strncmp(refname, "refs/tags/", 10) == 0) {
+			err = fetch_ref(ibuf, have_refs, &have[nref],
+			    &want[nref], refname, id_str);
 			if (err)
 				goto done;
-			err = got_object_id_str(&mine, &have[nref]);
-			if (err) {
-				free(theirs);
-				goto done;
+			nref++;
+		} else if (strncmp(refname, "refs/heads/", 11) == 0) {
+			if (fetch_all_branches) {
+				err = fetch_ref(ibuf, have_refs, &have[nref],
+				    &want[nref], refname, id_str);
+				if (err)
+					goto done;
+				nref++;
+				found_branch = 1;
+				continue;
 			}
-			fprintf(stderr, "%s: remote: %s\n%s: local:  %s\n",
-			    getprogname(), theirs, getprogname(), mine);
-			free(theirs);
-			free(mine);
+			TAILQ_FOREACH(pe, wanted_branches, entry) {
+				if (match_branch(refname, pe->path))
+					break;
+			}
+			if (pe != NULL || (worktree_branch != NULL &&
+			    match_branch(refname, worktree_branch))) {
+				err = fetch_ref(ibuf, have_refs, &have[nref],
+				    &want[nref], refname, id_str);
+				if (err)
+					goto done;
+				nref++;
+				found_branch = 1;
+			} else if (chattygot) {
+				fprintf(stderr, "%s: ignoring %s\n",
+				    getprogname(), refname);
+			}
+		} else {
+			TAILQ_FOREACH(pe, wanted_refs, entry) {
+				if (match_wanted_ref(refname, pe->path))
+					break;
+			}
+			if (pe != NULL) {
+				err = fetch_ref(ibuf, have_refs, &have[nref],
+				    &want[nref], refname, id_str);
+				if (err)
+					goto done;
+				nref++;
+			} else if (chattygot) {
+				fprintf(stderr, "%s: ignoring %s\n",
+				    getprogname(), refname);
+			}
 		}
-		nref++;
 	}
 
 	if (list_refs_only)
 		goto done;
 
-	/* Abort if we haven't found any branch to fetch. */
-	if (!found_branch) {
+	if (!found_branch && default_branch && default_id_str &&
+	    strncmp(default_branch, "refs/heads/", 11) == 0) {
+		err = fetch_ref(ibuf, have_refs, &have[nref],
+		    &want[nref], default_branch, default_id_str);
+		if (err)
+			goto done;
+		nref++;
+		found_branch = 1;
+	}
+
+	/* Abort if we haven't found anything to fetch. */
+	if (nref == 0) {
 		err = got_error(GOT_ERR_FETCH_NO_BRANCH);
 		goto done;
 	}
@@ -763,6 +788,7 @@ done:
 	free(have);
 	free(want);
 	free(id_str);
+	free(default_id_str);
 	free(refname);
 	free(server_capabilities);
 	return err;
@@ -785,6 +811,7 @@ main(int argc, char **argv)
 	struct got_imsg_fetch_wanted_branch wbranch;
 	struct got_imsg_fetch_wanted_ref wref;
 	size_t datalen, i;
+	char *worktree_branch = NULL;
 #if 0
 	static int attached;
 	while (!attached)
@@ -823,6 +850,22 @@ main(int argc, char **argv)
 	}
 	memcpy(&fetch_req, imsg.data, sizeof(fetch_req));
 	fetchfd = imsg.fd;
+
+	if (datalen != sizeof(fetch_req) +
+	    fetch_req.worktree_branch_len) {
+		err = got_error(GOT_ERR_PRIVSEP_LEN);
+		goto done;
+	}
+
+	if (fetch_req.worktree_branch_len != 0) {
+		worktree_branch = strndup(imsg.data +
+		    sizeof(fetch_req), fetch_req.worktree_branch_len);
+		if (worktree_branch == NULL) {
+			err = got_error_from_errno("strndup");
+			goto done;
+		}
+	}
+
 	imsg_free(&imsg);
 
 	if (fetch_req.verbosity > 0)
@@ -978,8 +1021,10 @@ main(int argc, char **argv)
 
 	err = fetch_pack(fetchfd, packfd, pack_sha1, &have_refs,
 	    fetch_req.fetch_all_branches, &wanted_branches,
-	    &wanted_refs, fetch_req.list_refs_only, &ibuf);
+	    &wanted_refs, fetch_req.list_refs_only,
+	    worktree_branch, &ibuf);
 done:
+	free(worktree_branch);
 	got_pathlist_free(&have_refs, GOT_PATHLIST_FREE_ALL);
 	got_pathlist_free(&wanted_branches, GOT_PATHLIST_FREE_PATH);
 	if (fetchfd != -1 && close(fetchfd) == -1 && err == NULL)
blob - 0f381607476a2ed62e00ff9cb319557a26e8d633
blob + 26346effc18d13d5e986f7072e63b5d9c885450f
--- regress/cmdline/fetch.sh
+++ regress/cmdline/fetch.sh
@@ -190,6 +190,44 @@ test_fetch_branch() {
 
 	# foo is now the default HEAD branch in $testroot/repo
 	# but got.conf still says to fetch "master"
+	got fetch -q -r $testroot/repo-clone > $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		echo "got fetch command failed unexpectedly" >&2
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo -n > $testroot/stdout.expected
+
+	cmp -s $testroot/stdout $testroot/stdout.expected
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		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/HEAD: refs/remotes/origin/master" \
+		>> $testroot/stdout.expected
+	echo "refs/remotes/origin/master: $commit_id2" \
+		>> $testroot/stdout.expected
+	# refs/hoo/boo/zoo is missing because it is outside of refs/heads
+	echo "refs/tags/1.0: $tag_id" >> $testroot/stdout.expected
+
+	cmp -s $testroot/stdout $testroot/stdout.expected
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# fetch branch foo via command-line switch
 	got fetch -q -r $testroot/repo-clone -b foo > $testroot/stdout
 	ret=$?
 	if [ $ret -ne 0 ]; then
@@ -216,8 +254,7 @@ test_fetch_branch() {
 	echo "refs/remotes/origin/HEAD: refs/remotes/origin/foo" \
 		>> $testroot/stdout.expected
 	echo "refs/remotes/origin/foo: $commit_id3" >> $testroot/stdout.expected
-	# refs/remotes/origin/master is umodified because it wasn't fetched
-	echo "refs/remotes/origin/master: $commit_id" \
+	echo "refs/remotes/origin/master: $commit_id2" \
 		>> $testroot/stdout.expected
 	# refs/hoo/boo/zoo is missing because it is outside of refs/heads
 	echo "refs/tags/1.0: $tag_id" >> $testroot/stdout.expected
@@ -315,7 +352,57 @@ test_fetch_branch() {
 	ret=$?
 	if [ $ret -ne 0 ]; then
 		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
 	fi
+
+	# remove default branch information from got.conf
+	sed -i -e "/branch {/d" $testroot/repo-clone/got.conf
+
+	# make another change on 'foo' and fetch it without got.conf
+	(cd $testroot/repo && git checkout -q foo)
+	echo "modified beta on foo agan" > $testroot/repo/beta
+	git_commit $testroot/repo -m "modified beta"
+	local commit_id5=`git_show_head $testroot/repo`
+	(cd $testroot/repo && git checkout -q master)
+
+	# fetch new commits on branch 'foo', implicitly obtaining the
+	# branch name from a work tree
+	(cd $testroot/wt && got fetch -q > $testroot/stdout)
+
+	echo -n > $testroot/stdout.expected
+
+	cmp -s $testroot/stdout $testroot/stdout.expected
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	wt_uuid=`(cd $testroot/wt && got info | grep 'UUID:' | \
+		cut -d ':' -f 2 | tr -d ' ')`
+
+	got ref -l -r $testroot/repo-clone > $testroot/stdout
+
+	echo "HEAD: refs/heads/master" > $testroot/stdout.expected
+	echo "refs/got/worktree/base-$wt_uuid: $commit_id3" \
+		>> $testroot/stdout.expected
+	echo "refs/heads/foo: $commit_id3" >> $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/foo: $commit_id5" >> $testroot/stdout.expected
+	echo "refs/remotes/origin/master: $commit_id2" \
+		>> $testroot/stdout.expected
+	# refs/hoo/boo/zoo is missing because it is outside of refs/heads
+	echo "refs/tags/1.0: $tag_id" >> $testroot/stdout.expected
+
+	cmp -s $testroot/stdout $testroot/stdout.expected
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
 	test_done "$testroot" "$ret"
 }