Commit Diff


commit - e31abbf21f99a7312bdfd392f33ace285feadfe5
commit + b2070a3f25a75399baa9a402542a60326197a053
blob - 59cc0bc74ca2f04ba3df6a2dc55756f8e29edde5
blob + 99bf3e8ae0503062b3ca312d1f3880d3861a5bf7
--- got/got.1
+++ got/got.1
@@ -843,7 +843,15 @@ If this directory is a
 .Nm
 work tree, use the repository path associated with this work tree.
 .It Fl l
-List all existing references in the repository.
+List references in the repository.
+If no
+.Ar name
+is specified, list all existing references in the repository.
+If
+.Ar name
+is a reference namespace, list all references in this namespace.
+Otherwise, show only the reference with the given
+.Ar name .
 Cannot be used together with any other options except
 .Fl r .
 .It Fl c Ar object
blob - b27ba046f77b825e22827ab64329341daa4b767b
blob + a10c0e212fc2d7195e321950605e7a96f3f8c95a
--- got/got.c
+++ got/got.c
@@ -4182,14 +4182,14 @@ usage_ref(void)
 }
 
 static const struct got_error *
-list_refs(struct got_repository *repo)
+list_refs(struct got_repository *repo, const char *refname)
 {
 	static const struct got_error *err = NULL;
 	struct got_reflist_head refs;
 	struct got_reflist_entry *re;
 
 	SIMPLEQ_INIT(&refs);
-	err = got_ref_list(&refs, repo, NULL, got_ref_cmp_by_name, NULL);
+	err = got_ref_list(&refs, repo, refname, got_ref_cmp_by_name, NULL);
 	if (err)
 		return err;
 
@@ -4304,7 +4304,8 @@ cmd_ref(int argc, char *argv[])
 	struct got_worktree *worktree = NULL;
 	char *cwd = NULL, *repo_path = NULL;
 	int ch, do_list = 0, do_delete = 0;
-	const char *refname = NULL, *obj_arg = NULL, *symref_target= NULL;
+	const char *obj_arg = NULL, *symref_target= NULL;
+	char *refname = NULL;
 
 	while ((ch = getopt(argc, argv, "c:dr:ls:")) != -1) {
 		switch (ch) {
@@ -4350,13 +4351,27 @@ cmd_ref(int argc, char *argv[])
 	argv += optind;
 
 	if (do_list) {
-		if (argc > 0)
+		if (argc != 0 && argc != 1)
 			usage_ref();
+		if (argc == 1) {
+			refname = strdup(argv[0]);
+			if (refname == NULL) {
+				error = got_error_from_errno("strdup");
+				goto done;
+			}
+		}
 	} else {
 		if (argc != 1)
 			usage_ref();
-		refname = argv[0];
+		refname = strdup(argv[0]);
+		if (refname == NULL) {
+			error = got_error_from_errno("strdup");
+			goto done;
+		}
 	}
+
+	if (refname)
+		got_path_strip_trailing_slashes(refname);
 
 #ifndef PROFILE
 	if (do_list) {
@@ -4407,7 +4422,7 @@ cmd_ref(int argc, char *argv[])
 		goto done;
 
 	if (do_list)
-		error = list_refs(repo);
+		error = list_refs(repo, refname);
 	else if (do_delete)
 		error = delete_ref(repo, refname);
 	else if (symref_target)
@@ -4418,6 +4433,7 @@ cmd_ref(int argc, char *argv[])
 		error = add_ref(repo, refname, obj_arg);
 	}
 done:
+	free(refname);
 	if (repo)
 		got_repo_close(repo);
 	if (worktree)
blob - 86409d0ebedc1d35c6333f13f4f8267d21581671
blob + f74cc9ecacc8b745cdb5c73db7f2c60cd9e918ef
--- lib/reference.c
+++ lib/reference.c
@@ -162,7 +162,7 @@ parse_ref_line(struct got_reference **ref, const char 
 
 static const struct got_error *
 parse_ref_file(struct got_reference **ref, const char *name,
-    const char *abspath, int lock)
+    const char *absname, const char *abspath, int lock)
 {
 	const struct got_error *err = NULL;
 	FILE *f;
@@ -175,24 +175,34 @@ parse_ref_file(struct got_reference **ref, const char 
 		err = got_lockfile_lock(&lf, abspath);
 		if (err) {
 			if (err->code == GOT_ERR_ERRNO && errno == ENOENT)
-				err = got_error(GOT_ERR_NOT_REF);
+				err = got_error_not_ref(name);
 			return err;
 		}
 	}
 
 	f = fopen(abspath, "rb");
 	if (f == NULL) {
+		if (errno != ENOTDIR && errno != ENOENT)
+			err = got_error_from_errno2("fopen", abspath);
+		else
+			err = got_error_not_ref(name);
 		if (lock)
 			got_lockfile_unlock(lf);
-		return NULL;
+		return err;
 	}
 
 	linelen = getline(&line, &linesize, f);
 	if (linelen == -1) {
 		if (feof(f))
 			err = NULL; /* ignore empty files (could be locks) */
-		else
-			err = got_error_from_errno2("getline", abspath);
+		else {
+			if (errno == EISDIR)
+				err = got_error(GOT_ERR_NOT_REF);
+			else if (ferror(f))
+				err = got_ferror(f, GOT_ERR_IO);
+			else
+				err = got_error_from_errno2("getline", abspath);
+		}
 		if (lock)
 			got_lockfile_unlock(lf);
 		goto done;
@@ -202,7 +212,7 @@ parse_ref_file(struct got_reference **ref, const char 
 		linelen--;
 	}
 
-	err = parse_ref_line(ref, name, line);
+	err = parse_ref_line(ref, absname, line);
 	if (lock) {
 		if (err)
 			got_lockfile_unlock(lf);
@@ -411,7 +421,7 @@ open_ref(struct got_reference **ref, const char *path_
 		}
 	}
 
-	err = parse_ref_file(ref, absname, path, lock);
+	err = parse_ref_file(ref, name, absname, path, lock);
 done:
 	if (!ref_is_absolute && !ref_is_well_known)
 		free(absname);
@@ -449,7 +459,7 @@ got_ref_open(struct got_reference **ref, struct got_re
 		for (i = 0; i < nitems(subdirs); i++) {
 			err = open_ref(ref, path_refs, subdirs[i], refname,
 			    lock);
-			if (err || *ref)
+			if ((err && err->code != GOT_ERR_NOT_REF) || *ref)
 				goto done;
 		}
 
@@ -801,6 +811,9 @@ gather_on_disk_refs(struct got_reflist_head *refs, con
 	const struct got_error *err = NULL;
 	DIR *d = NULL;
 	char *path_subdir;
+
+	while (subdir[0] == '/')
+		subdir++;
 
 	if (asprintf(&path_subdir, "%s/%s", path_refs, subdir) == -1)
 		return got_error_from_errno("asprintf");
@@ -865,7 +878,8 @@ got_ref_list(struct got_reflist_head *refs, struct got
 {
 	const struct got_error *err;
 	char *packed_refs_path, *path_refs = NULL;
-	const char *ondisk_ref_namespace = NULL;
+	char *abs_namespace = NULL;
+	char *buf = NULL, *ondisk_ref_namespace = NULL;
 	FILE *f = NULL;
 	struct got_reference *ref;
 	struct got_reflist_entry *new;
@@ -885,11 +899,52 @@ got_ref_list(struct got_reflist_head *refs, struct got
 			got_ref_close(ref);
 		if (err && err->code != GOT_ERR_NOT_REF)
 			goto done;
+	} else {
+		/* Try listing a single reference. */
+		const char *refname = ref_namespace;
+		path_refs = get_refs_dir_path(repo, refname);
+		if (path_refs == NULL) {
+			err = got_error_from_errno("get_refs_dir_path");
+			goto done;
+		}
+		err = open_ref(&ref, path_refs, "", refname, 0);
+		if (err) {
+			if (err->code != GOT_ERR_NOT_REF)
+				goto done;
+			/* Try to look up references in a given namespace. */
+		} else {
+			err = insert_ref(&new, refs, ref, repo,
+			    cmp_cb, cmp_arg);
+			if (err || new == NULL /* duplicate */)
+				got_ref_close(ref);
+			return err;
+		}
 	}
 
-	ondisk_ref_namespace = ref_namespace;
-	if (ref_namespace && strncmp(ref_namespace, "refs/", 5) == 0)
-		ondisk_ref_namespace += 5;
+	if (ref_namespace) {
+		size_t len;
+		/* Canonicalize the path to eliminate double-slashes if any. */
+		if (asprintf(&abs_namespace, "/%s", ref_namespace) == -1) {
+			err = got_error_from_errno("asprintf");
+			goto done;
+		}
+		len = strlen(abs_namespace) + 1;
+		buf = malloc(len);
+		if (buf == NULL) {
+			err = got_error_from_errno("malloc");
+			goto done;
+		}
+		err = got_canonpath(abs_namespace, buf, len);
+		if (err)
+			goto done;
+		ondisk_ref_namespace = buf;
+		while (ondisk_ref_namespace[0] == '/')
+			ondisk_ref_namespace++;
+		if (strncmp(ondisk_ref_namespace, "refs/", 5) == 0)
+			ondisk_ref_namespace += 5;
+		else if (strcmp(ondisk_ref_namespace, "refs") == 0)
+			ondisk_ref_namespace = "";
+	}
 
 	/* Gather on-disk refs before parsing packed-refs. */
 	free(path_refs);
@@ -936,8 +991,9 @@ got_ref_list(struct got_reflist_head *refs, struct got
 				if (ref_namespace) {
 					const char *name;
 					name = got_ref_get_name(ref);
-					if (strncmp(name, ref_namespace,
-					    strlen(ref_namespace)) != 0) {
+					if (!got_path_is_child(name,
+					    ref_namespace,
+					    strlen(ref_namespace))) {
 						got_ref_close(ref);
 						continue;
 					}
@@ -952,6 +1008,8 @@ got_ref_list(struct got_reflist_head *refs, struct got
 		}
 	}
 done:
+	free(abs_namespace);
+	free(buf);
 	free(path_refs);
 	if (f && fclose(f) != 0 && err == NULL)
 		err = got_error_from_errno("fclose");
blob - 94736443ecd38ab37cc59e7464129a48092a6a72
blob + 8457642808883458905c4ddef16686f54b7b7fd2
--- regress/cmdline/ref.sh
+++ regress/cmdline/ref.sh
@@ -238,9 +238,139 @@ function test_ref_delete {
 	ret="$?"
 	if [ "$ret" != "0" ]; then
 		diff -u $testroot/stderr.expected $testroot/stderr
+	fi
+	test_done "$testroot" "$ret"
+}
+
+function test_ref_list {
+	local testroot=`test_init ref_list`
+	local commit_id=`git_show_head $testroot/repo`
+
+	# Create a tag pointing at a commit ID
+	got tag -r $testroot/repo -c $commit_id -m "1.0" "1.0" >/dev/null
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "got tag command failed unexpectedly"
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+	local tag_id=`got ref -r $testroot/repo -l \
+		| grep "^refs/tags/$tag" | tr -d ' ' | cut -d: -f2`
+
+	# Create a ref based on repository's HEAD reference
+	got ref -r $testroot/repo -c HEAD refs/foo/zoo
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "got ref command failed unexpectedly"
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# Create a head ref based on another specific ref
+	(cd $testroot/repo && got ref -c refs/heads/master refs/foo/bar/baz)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	got ref -r $testroot/repo -l > $testroot/stdout
+
+	echo "HEAD: refs/heads/master" > $testroot/stdout.expected
+	echo "refs/foo/bar/baz: $commit_id" >> $testroot/stdout.expected
+	echo "refs/foo/zoo: $commit_id" >> $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
+
+	got ref -r $testroot/repo -l refs > $testroot/stdout
+
+	echo "refs/foo/bar/baz: $commit_id" > $testroot/stdout.expected
+	echo "refs/foo/zoo: $commit_id" >> $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
+
+	got ref -r $testroot/repo -l refs/tags > $testroot/stdout
+
+	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
+
+	for r in refs/foo/bar/baz refs/foo/bar/baz foo/bar/baz foo/bar; do
+		got ref -r $testroot/repo -l $r > $testroot/stdout
+
+		echo "refs/foo/bar/baz: $commit_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
+	done
+
+	for r in refs/foo foo; do
+		got ref -r $testroot/repo -l $r > $testroot/stdout
+
+		echo "refs/foo/bar/baz: $commit_id" > $testroot/stdout.expected
+		echo "refs/foo/zoo: $commit_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
+	done
+
+	for r in refs//foo/bar refs//foo//bar refs////////foo//bar; do
+		got ref -r $testroot/repo -l $r > $testroot/stdout
+
+		echo "refs/foo/bar/baz: $commit_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
+	done
+
+	# attempt to list non-existing references
+	for r in refs/fo bar baz moo riffs /refs/abc refs/foo/bar/baz/moo; do
+		got ref -r $testroot/repo -l $r > $testroot/stdout
+
+		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
+	done	
+
 	test_done "$testroot" "$ret"
 }
 
 run_test test_ref_create
 run_test test_ref_delete
+run_test test_ref_list