Commit Diff


commit - 29e86f7a5a094e0e5e9ca231e615a13c0c2e6ed0
commit + d1c1ae5fa698c48d70eb16cef11bb3710221ea88
blob - a7c6931c739bce2b4746264ec0f8a7e4ef97a80d
blob + 0a9ce6f2c219a2b2a5308d9efdeb9170171b5d96
--- TODO
+++ TODO
@@ -15,7 +15,6 @@ got:
 - 'histedit -c' prompts for log message even if there are no changes to commit
 - recursive addition: got add -R
 - recursive removal: got rm -R
-- allow for creating symbolic references with 'got ref -s'
 
 tog:
 - cosmetic display issues involving \n and TABs should be fixed
blob - 00c7548af39086e290bea57234ee1243e5e1e2bd
blob + a826de9bf1ee14823162c06c47bc24c7a3767ec1
--- got/got.1
+++ got/got.1
@@ -459,6 +459,12 @@ work tree, use the repository path associated with thi
 List all existing references in the repository.
 .It Fl d Ar name
 Delete the reference with the specified name from the repository.
+.It Fl s
+Create a symbolic reference pointing at the specified
+.Ar target ,
+which must be an existing reference.
+Care should be taken not to create loops between references when
+this option is used.
 .El
 .It Cm branch Oo Fl r Ar repository-path Oc Oo Fl l Oc Oo Fl d Ar name Oc Op Ar name Op Ar base-branch
 Manage branches in a repository.
blob - 9a69229f803e9ee42a8f6cb234d669f3282effe6
blob + a322f32e174306295c8b5682c8a19d3280485378
--- got/got.c
+++ got/got.c
@@ -2561,7 +2561,7 @@ __dead static void
 usage_ref(void)
 {
 	fprintf(stderr,
-	    "usage: %s ref [-r repository] -l | -d name | name target\n",
+	    "usage: %s ref [-r repository] -l | -d name | [-s] name target\n",
 	    getprogname());
 	exit(1);
 }
@@ -2646,6 +2646,38 @@ done:
 	if (ref)
 		got_ref_close(ref);
 	free(id);
+	return err;
+}
+
+static const struct got_error *
+add_symref(struct got_repository *repo, const char *refname, const char *target)
+{
+	const struct got_error *err = NULL;
+	struct got_reference *ref = NULL;
+	struct got_reference *target_ref = NULL;
+
+	/*
+	 * Don't let the user create a reference named '-'.
+	 * While technically a valid reference name, this case is usually
+	 * an unintended typo.
+	 */
+	if (refname[0] == '-' && refname[1] == '\0')
+		return got_error(GOT_ERR_BAD_REF_NAME);
+
+	err = got_ref_open(&target_ref, repo, target, 0);
+	if (err)
+		return err;
+
+	err = got_ref_alloc_symref(&ref, refname, target_ref);
+	if (err)
+		goto done;
+
+	err = got_ref_write(ref, repo);
+done:
+	if (target_ref)
+		got_ref_close(target_ref);
+	if (ref)
+		got_ref_close(ref);
 	return err;
 }
 
@@ -2656,11 +2688,11 @@ cmd_ref(int argc, char *argv[])
 	struct got_repository *repo = NULL;
 	struct got_worktree *worktree = NULL;
 	char *cwd = NULL, *repo_path = NULL;
-	int ch, do_list = 0;
+	int ch, do_list = 0, create_symref = 0;
 	const char *delref = NULL;
 
 	/* TODO: Add -s option for adding symbolic references. */
-	while ((ch = getopt(argc, argv, "d:r:l")) != -1) {
+	while ((ch = getopt(argc, argv, "d:r:ls")) != -1) {
 		switch (ch) {
 		case 'd':
 			delref = optarg;
@@ -2673,6 +2705,9 @@ cmd_ref(int argc, char *argv[])
 			break;
 		case 'l':
 			do_list = 1;
+			break;
+		case 's':
+			create_symref = 1;
 			break;
 		default:
 			usage_ref();
@@ -2687,6 +2722,9 @@ cmd_ref(int argc, char *argv[])
 	argv += optind;
 
 	if (do_list || delref) {
+		if (create_symref)
+			errx(1, "-s option cannot be used together with the "
+			    "-l or -d options");
 		if (argc > 0)
 			usage_ref();
 	} else if (argc != 2)
@@ -2744,6 +2782,8 @@ cmd_ref(int argc, char *argv[])
 		error = list_refs(repo);
 	else if (delref)
 		error = delete_ref(repo, delref);
+	else if (create_symref)
+		error = add_symref(repo, argv[0], argv[1]);
 	else
 		error = add_ref(repo, argv[0], argv[1]);
 done:
blob - e86ea7f544fae06c498afb4bfbe43ed12e0e2818
blob + c7f57651b4255a40c5e239acede9d76dae99f6dc
--- regress/cmdline/Makefile
+++ regress/cmdline/Makefile
@@ -1,4 +1,4 @@
-REGRESS_TARGETS=checkout update status log add rm diff blame branch commit \
+REGRESS_TARGETS=checkout update status log add rm diff blame branch ref commit \
 	revert cherrypick backout rebase import histedit stage unstage
 NOOBJ=Yes
 
@@ -29,6 +29,9 @@ blame:
 branch:
 	./branch.sh
 
+ref:
+	./ref.sh
+
 commit:
 	./commit.sh
 
blob - /dev/null
blob + 3774051198b40f4ceaef773bcd650eaba78d8ead (mode 755)
--- /dev/null
+++ regress/cmdline/ref.sh
@@ -0,0 +1,153 @@
+#!/bin/sh
+#
+# Copyright (c) 2019 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.
+
+. ./common.sh
+
+function test_ref_create {
+	local testroot=`test_init ref_create`
+	local commit_id=`git_show_head $testroot/repo`
+
+	# Create a head ref based on repository's HEAD reference
+	got ref -r $testroot/repo refs/heads/newref HEAD
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "got ref command failed unexpectedly"
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# Ensure that Git recognizes the ref Got has created
+	(cd $testroot/repo && git checkout -q newref)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "git checkout command failed unexpectedly"
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# Ensure Git recognizes the new ref
+	got checkout -b newref $testroot/repo $testroot/wt >/dev/null
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "got checkout command failed unexpectedly"
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# Create a head ref based on another specific ref
+	(cd $testroot/wt && got ref refs/heads/anotherref refs/heads/master)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	(cd $testroot/repo && git checkout -q anotherref)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "git checkout command failed unexpectedly"
+		test_done "$testroot" "$ret"
+	fi
+
+	# Create a symbolic ref
+	(cd $testroot/wt && got ref -s refs/heads/symbolicref refs/heads/master)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	(cd $testroot/repo && git checkout -q symbolicref)
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "git checkout command failed unexpectedly"
+		test_done "$testroot" "$ret"
+	fi
+
+	got ref -r $testroot/repo -l > $testroot/stdout
+	echo "HEAD: refs/heads/symbolicref" > $testroot/stdout.expected
+	echo -n "refs/got/worktree/base-" >> $testroot/stdout.expected
+	cat $testroot/wt/.got/uuid | tr -d '\n' >> $testroot/stdout.expected
+	echo ": $commit_id" >> $testroot/stdout.expected
+	echo "refs/heads/anotherref: $commit_id" >> $testroot/stdout.expected
+	echo "refs/heads/master: $commit_id" >> $testroot/stdout.expected
+	echo "refs/heads/newref: $commit_id" >> $testroot/stdout.expected
+	echo "refs/heads/symbolicref: refs/heads/master" \
+		>> $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_ref_delete {
+	local testroot=`test_init ref_delete`
+	local commit_id=`git_show_head $testroot/repo`
+
+	for b in ref1 ref2 ref3; do
+		got ref -r $testroot/repo refs/heads/$b refs/heads/master
+		ret="$?"
+		if [ "$ret" != "0" ]; then
+			echo "got ref command failed unexpectedly"
+			test_done "$testroot" "$ret"
+			return 1
+		fi
+	done
+
+	got ref -d refs/heads/ref2 -r $testroot/repo > $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "got ref command failed unexpectedly"
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	got ref -l -r $testroot/repo > $testroot/stdout
+	echo "HEAD: refs/heads/master" > $testroot/stdout.expected
+	echo "refs/heads/master: $commit_id" >> $testroot/stdout.expected
+	echo "refs/heads/ref1: $commit_id" >> $testroot/stdout.expected
+	echo "refs/heads/ref3: $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
+
+	got ref -d refs/heads/bogus_ref_name -r $testroot/repo \
+		> $testroot/stdout 2> $testroot/stderr
+	ret="$?"
+	if [ "$ret" == "0" ]; then
+		echo "got ref succeeded unexpectedly"
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "got: reference refs/heads/bogus_ref_name not found" \
+		> $testroot/stderr.expected
+	cmp -s $testroot/stderr $testroot/stderr.expected
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stderr.expected $testroot/stderr
+	fi
+	test_done "$testroot" "$ret"
+}
+
+run_test test_ref_create
+run_test test_ref_delete