diff options
-rw-r--r-- | include/git2/refs.h | 5 | ||||
-rw-r--r-- | src/refs.c | 10 | ||||
-rw-r--r-- | tests/refs/races.c | 33 |
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); +} |