Skip to content

Commit 95b7a63

Browse files
committed
git_reference_create_matching: Treat all-zero OID as "must be absent"
This is pretty useful in avoiding races: I want to create a ref only if it doesn't already exist. I can't check first because of TOCTOU -- by the time I finish the check, someone else might have already created the ref. And I can't take a lock because then I can't do the create, since the create expects to take the lock. The semantics are inspired by git update-ref, which allows an all-zero old value to mean that the ref must not exist.
1 parent ac77d30 commit 95b7a63

File tree

3 files changed

+20
-1
lines changed

3 files changed

+20
-1
lines changed

include/git2/refs.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ GIT_EXTERN(int) git_reference_dwim(git_reference **out, git_repository *repo, co
9797
* of updating does not match the one passed through `current_value`
9898
* (i.e. if the ref has changed since the user read it).
9999
*
100+
* If `current_value` is all zeros, this function will return GIT_EMODIFIED
101+
* if the ref already exists.
102+
*
100103
* @param out Pointer to the newly created reference
101104
* @param repo Repository where that reference will live
102105
* @param name The name of the reference

src/refdb_fs.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1145,8 +1145,11 @@ static int cmp_old_ref(int *cmp, git_refdb_backend *backend, const char *name,
11451145
if (!old_id && !old_target)
11461146
return 0;
11471147

1148-
if ((error = refdb_fs_backend__lookup(&old_ref, backend, name)) < 0)
1148+
if ((error = refdb_fs_backend__lookup(&old_ref, backend, name)) < 0) {
1149+
if (error == GIT_ENOTFOUND && old_id && git_oid_is_zero(old_id))
1150+
return 0;
11491151
goto out;
1152+
}
11501153

11511154
/* If the types don't match, there's no way the values do */
11521155
if (old_id && old_ref->type != GIT_REFERENCE_DIRECT) {

tests/refs/races.c

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,19 @@ void test_refs_races__cleanup(void)
2222
cl_git_sandbox_cleanup();
2323
}
2424

25+
void test_refs_races__create_matching_zero_old(void)
26+
{
27+
git_reference *ref;
28+
git_oid id, zero_id;
29+
30+
git_oid_fromstr(&id, commit_id);
31+
git_oid_fromstr(&zero_id, "0000000000000000000000000000000000000000");
32+
33+
cl_git_pass(git_reference_create_matching(&ref, g_repo, other_refname, &id, 1, &zero_id, NULL));
34+
35+
git_reference_free(ref);
36+
}
37+
2538
void test_refs_races__create_matching(void)
2639
{
2740
git_reference *ref, *ref2, *ref3;

0 commit comments

Comments
 (0)