summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/git2/refs.h5
-rw-r--r--src/refs.c10
-rw-r--r--tests/refs/races.c33
3 files changed, 46 insertions, 2 deletions
diff --git a/include/git2/refs.h b/include/git2/refs.h
index 284671d2c..0e5f779cf 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 f45c64246..90340d09c 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 6613fc20f..396f13d2d 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);
+}