refs: check the ref's old value when deleting

Recognize when the reference has changed since we loaded it.
This commit is contained in:
Carlos Martín Nieto 2014-02-05 11:21:14 +01:00
parent 7ee8c7e677
commit f44fd59ed7
3 changed files with 46 additions and 2 deletions

View File

@ -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);

View File

@ -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,

View File

@ -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);
}