Commit e8a967e0cafdb6604275a9cbfcce988d4f363ef5

Stefan Sperling 2020-03-21T22:44:07

make replacing symrefs actually work in 'got fetch'

diff --git a/got/got.c b/got/got.c
index cd12b5e..0420f2e 100644
--- a/got/got.c
+++ b/got/got.c
@@ -1422,19 +1422,15 @@ update_ref(struct got_reference *ref, struct got_object_id *new_id,
 	}
 
 	if (got_ref_is_symbolic(ref)) {
-		struct got_reference *new_ref;
-		err = got_ref_alloc(&new_ref, got_ref_get_name(ref), new_id);
-		if (err)
-			goto done;
-		err = got_ref_delete(ref, repo);
-		if (err)
-			goto done;
 		if (verbosity >= 0) {
-			printf("Deleted reference %s: %s\n",
+			printf("Replacing reference %s: %s\n",
 			    got_ref_get_name(ref),
 			    got_ref_get_symref_target(ref));
 		}
-		err = got_ref_write(new_ref, repo);
+		err = got_ref_change_symref_to_ref(ref, new_id);
+		if (err)
+			goto done;
+		err = got_ref_write(ref, repo);
 		if (err)
 			goto done;
 	} else {
diff --git a/include/got_reference.h b/include/got_reference.h
index da0da7b..df306d5 100644
--- a/include/got_reference.h
+++ b/include/got_reference.h
@@ -123,6 +123,13 @@ got_ref_change_ref(struct got_reference *, struct got_object_id *);
 const struct got_error *got_ref_change_symref(struct got_reference *,
     char *);
 
+/*
+ * Change a symbolic reference into a regular reference which points to
+ * the provided object ID.
+ */
+const struct got_error *got_ref_change_symref_to_ref(struct got_reference *,
+    struct got_object_id *);
+
 /* Write a reference to its on-disk path in the repository. */
 const struct got_error *got_ref_write(struct got_reference *,
     struct got_repository *);
diff --git a/lib/reference.c b/lib/reference.c
index ff1f037..86409d0 100644
--- a/lib/reference.c
+++ b/lib/reference.c
@@ -1007,6 +1007,19 @@ got_ref_change_symref(struct got_reference *ref, char *refname)
 }
 
 const struct got_error *
+got_ref_change_symref_to_ref(struct got_reference *symref,
+    struct got_object_id *id)
+{
+	if ((symref->flags & GOT_REF_IS_SYMBOLIC) == 0)
+		return got_error(GOT_ERR_BAD_REF_TYPE);
+
+	symref->ref.ref.name = symref->ref.symref.name;
+	memcpy(symref->ref.ref.sha1, id->sha1, SHA1_DIGEST_LENGTH);
+	symref->flags &= ~GOT_REF_IS_SYMBOLIC;
+	return NULL;
+}
+
+const struct got_error *
 got_ref_write(struct got_reference *ref, struct got_repository *repo)
 {
 	const struct got_error *err = NULL, *unlock_err = NULL;
diff --git a/regress/cmdline/fetch.sh b/regress/cmdline/fetch.sh
index d62e1ba..b59b39e 100755
--- a/regress/cmdline/fetch.sh
+++ b/regress/cmdline/fetch.sh
@@ -728,6 +728,71 @@ function test_fetch_reference {
 
 }
 
+function test_fetch_replace_symref {
+	local testroot=`test_init fetch_replace_symref`
+	local testurl=ssh://127.0.0.1/$testroot
+	local commit_id=`git_show_head $testroot/repo`
+
+	got clone -m -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 ref -r $testroot/repo refs/hoo/boo/zoo $commit_id
+	got ref -r $testroot/repo-clone -s refs/hoo/boo/zoo refs/heads/master
+
+	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: 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
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	got fetch -r $testroot/repo-clone -R refs/hoo \
+		2> $testroot/stderr | grep ^Replacing > $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		echo "got fetch command failed unexpectedly" >&2
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	echo "Replacing reference refs/hoo/boo/zoo: 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
+		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
+
+	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
@@ -736,3 +801,4 @@ run_test test_fetch_empty_packfile
 run_test test_fetch_delete_branch
 run_test test_fetch_update_tag
 run_test test_fetch_reference
+run_test test_fetch_replace_symref