Repository: libgit2/libgit2 (C)

Issue:
reject attempts to delete HEAD
I just wrote some buggy code in which I accidentally deleted HEAD, rather than its symbolic target. However, HEAD is required to exist.

Given that deleting HEAD is not a legitimate git operation (it turns a git dir into a non-git dit), perhaps libgit2 could prevent this by returning an error when `git_reference_delete` is called on HEAD. I'm not sure what error code/class would be the right one, though.

Sound ok?



Discussion:
Yeah, deleting HEAD is no good.  We should prevent this.  Here's what git says when you try to `symbolic-ref --delete HEAD`:

> deleting 'HEAD' is not allowed

I think the `GIT_ERROR_REFERENCE` is appropriate here.



Patch:
diff --git a/src/refs.c b/src/refs.c
index 29dd1bdebe0..633d83abdde 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -145,6 +145,11 @@ int git_reference_delete(git_reference *ref)
 	const git_oid *old_id = NULL;
 	const char *old_target = NULL;
 
+	if (!strcmp(ref->name, "HEAD")) {
+		git_error_set(GIT_ERROR_REFERENCE, "cannot delete HEAD");
+		return GIT_ERROR;
+	}
+
 	if (ref->type == GIT_REFERENCE_DIRECT)
 		old_id = &ref->target.oid;
 	else


Test patch:
diff --git a/tests/iterator/workdir.c b/tests/iterator/workdir.c
index 926cc6af0fc..72f8bf42cca 100644
--- a/tests/iterator/workdir.c
+++ b/tests/iterator/workdir.c
@@ -620,6 +620,7 @@ void test_iterator_workdir__filesystem2(void)
 		"heads/subtrees",
 		"heads/test",
 		"heads/testrepo-worktree",
+		"symref",
 		"tags/e90810b",
 		"tags/foo/bar",
 		"tags/foo/foo/bar",
@@ -632,7 +633,7 @@ void test_iterator_workdir__filesystem2(void)
 
 	cl_git_pass(git_iterator_for_filesystem(
 		&i, "testrepo/.git/refs", NULL));
-	expect_iterator_items(i, 16, expect_base, 16, expect_base);
+	expect_iterator_items(i, 17, expect_base, 17, expect_base);
 	git_iterator_free(i);
 }
 
diff --git a/tests/refs/delete.c b/tests/refs/delete.c
index a3344964124..3e99a7959b7 100644
--- a/tests/refs/delete.c
+++ b/tests/refs/delete.c
@@ -105,3 +105,14 @@ void test_refs_delete__remove(void)
 
 	cl_git_fail(git_reference_lookup(&ref, g_repo, packed_test_head_name));
 }
+
+void test_refs_delete__head(void)
+{
+	git_reference *ref;
+
+	/* Check that it is not possible to delete HEAD */
+
+	cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD"));
+	cl_git_fail(git_reference_delete(ref));
+	git_reference_free(ref);
+}
diff --git a/tests/refs/list.c b/tests/refs/list.c
index 3e8c82c94b6..725d38161fe 100644
--- a/tests/refs/list.c
+++ b/tests/refs/list.c
@@ -36,7 +36,7 @@ void test_refs_list__all(void)
 	/* We have exactly 12 refs in total if we include the packed ones:
 	 * there is a reference that exists both in the packfile and as
 	 * loose, but we only list it once */
-	cl_assert_equal_i((int)ref_list.count, 18);
+	cl_assert_equal_i((int)ref_list.count, 19);
 
 	git_strarray_free(&ref_list);
 }
@@ -51,7 +51,7 @@ void test_refs_list__do_not_retrieve_references_which_name_end_with_a_lock_exten
 		"144344043ba4d4a405da03de3844aa829ae8be0e\n");
 
 	cl_git_pass(git_reference_list(&ref_list, g_repo));
-	cl_assert_equal_i((int)ref_list.count, 18);
+	cl_assert_equal_i((int)ref_list.count, 19);
 
 	git_strarray_free(&ref_list);
 }
diff --git a/tests/refs/races.c b/tests/refs/races.c
index fbecf4a75ee..04a1bc17b3b 100644
--- a/tests/refs/races.c
+++ b/tests/refs/races.c
@@ -74,8 +74,8 @@ void test_refs_races__delete(void)
 	git_reference_free(ref);
 
 	/* We cannot delete a symbolic value that doesn't match */
-	cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD"));
-	cl_git_pass(git_reference_symbolic_create_matching(&ref2, g_repo, "HEAD", other_refname, 1, NULL, refname));
+	cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/symref"));
+	cl_git_pass(git_reference_symbolic_create_matching(&ref2, g_repo, "refs/symref", other_refname, 1, NULL, refname));
 	cl_git_fail_with(GIT_EMODIFIED, git_reference_delete(ref));
 
 	git_reference_free(ref);
@@ -131,19 +131,19 @@ void test_refs_races__switch_symbolic_to_oid(void)
 	git_oid_fromstr(&other_id, other_commit_id);
 
 	/* Removing a symbolic ref when it's currently direct should fail */
-	cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD"));
-	cl_git_pass(git_reference_create(&ref2, g_repo, "HEAD", &id, 1, NULL));
+	cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/symref"));
+	cl_git_pass(git_reference_create(&ref2, g_repo, "refs/symref", &id, 1, NULL));
 	cl_git_fail_with(GIT_EMODIFIED, git_reference_delete(ref));
 
 	git_reference_free(ref);
 	git_reference_free(ref2);
 
-	cl_git_pass(git_reference_symbolic_create(&ref, g_repo, "HEAD", refname, 1, NULL));
+	cl_git_pass(git_reference_symbolic_create(&ref, g_repo, "refs/symref", refname, 1, NULL));
 	git_reference_free(ref);
 
 	/* Updating a symbolic ref when it's currently direct should fail */
-	cl_git_pass(git_reference_lookup(&ref, g_repo, "HEAD"));
-	cl_git_pass(git_reference_create(&ref2, g_repo, "HEAD", &id, 1, NULL));
+	cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/symref"));
+	cl_git_pass(git_reference_create(&ref2, g_repo, "refs/symref", &id, 1, NULL));
 	cl_git_fail_with(GIT_EMODIFIED, git_reference_symbolic_set_target(&ref3, ref, other_refname, NULL));
 
 	git_reference_free(ref);
diff --git a/tests/resources/testrepo/.gitted/refs/symref b/tests/resources/testrepo/.gitted/refs/symref
new file mode 100644
index 00000000000..cb089cd89a7
--- /dev/null
+++ b/tests/resources/testrepo/.gitted/refs/symref
@@ -0,0 +1,1 @@
+ref: refs/heads/master