Commit f44fd59ed7b3533bf9cbaa07969a8a57475a77aa

Carlos Martín Nieto 2014-02-05T11:21:14

refs: check the ref's old value when deleting Recognize when the reference has changed since we loaded it.

diff --git a/include/git2/refs.h b/include/git2/refs.h
index 284671d..0e5f779 100644
--- a/include/git2/refs.h
+++ b/include/git2/refs.h
@@ -398,8 +398,11 @@ GIT_EXTERN(int) git_reference_rename(
  * will be immediately removed on disk but the memory will not be freed.
  * Callers must call `git_reference_free`.
  *
+ * This function will return an error if the reference has changed
+ * from the time it was looked up.
+ *
  * @param ref The reference to remove
- * @return 0 or an error code
+ * @return 0, GIT_EMODIFIED or an error code
  */
 GIT_EXTERN(int) git_reference_delete(git_reference *ref);
 
diff --git a/src/refs.c b/src/refs.c
index f45c642..90340d0 100644
--- a/src/refs.c
+++ b/src/refs.c
@@ -116,7 +116,15 @@ void git_reference_free(git_reference *reference)
 
 int git_reference_delete(git_reference *ref)
 {
-	return git_refdb_delete(ref->db, ref->name, NULL, NULL);
+	const git_oid *old_id = NULL;
+	const char *old_target = NULL;
+
+	if (ref->type == GIT_REF_OID)
+		old_id = &ref->target.oid;
+	else
+		old_target = ref->target.symbolic;
+
+	return git_refdb_delete(ref->db, ref->name, old_id, old_target);
 }
 
 int git_reference_lookup(git_reference **ref_out,
diff --git a/tests/refs/races.c b/tests/refs/races.c
index 6613fc2..396f13d 100644
--- a/tests/refs/races.c
+++ b/tests/refs/races.c
@@ -59,3 +59,36 @@ void test_refs_races__symbolic_create_matching(void)
 	git_reference_free(ref2);
 	git_reference_free(ref3);
 }
+
+void test_refs_races__delete(void)
+{
+	git_reference *ref, *ref2;
+	git_oid id, other_id;
+
+	git_oid_fromstr(&id, commit_id);
+	git_oid_fromstr(&other_id, other_commit_id);
+
+	/* We can delete a value that matches */
+	cl_git_pass(git_reference_lookup(&ref, g_repo, refname));
+	cl_git_pass(git_reference_delete(ref));
+	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, NULL, refname));
+	cl_git_fail_with(GIT_EMODIFIED, git_reference_delete(ref));
+
+	git_reference_free(ref);
+	git_reference_free(ref2);
+
+	cl_git_pass(git_reference_create(&ref, g_repo, refname, &id, 1, NULL, NULL));
+	git_reference_free(ref);
+
+	/* We cannot delete an oid value that doesn't match */
+	cl_git_pass(git_reference_lookup(&ref, g_repo, refname));
+	cl_git_pass(git_reference_create_matching(&ref2, g_repo, refname, &other_id, 1, NULL, NULL, &id));
+	cl_git_fail_with(GIT_EMODIFIED, git_reference_delete(ref));
+
+	git_reference_free(ref);
+	git_reference_free(ref2);
+}