Skip to content

Commit e911104

Browse files
mhaggergitster
authored andcommitted
refs: check for D/F conflicts among refs created in a transaction
If two references that D/F conflict (e.g., "refs/foo" and "refs/foo/bar") are created in a single transaction, the old code discovered the problem only after the "commit" phase of ref_transaction_commit() had already begun. This could leave some references updated and others not, which violates the promise of atomicity. Instead, check for such conflicts during the "locking" phase: * Teach is_refname_available() to take an "extras" parameter that can contain extra reference names with which the specified refname must not conflict. * Change lock_ref_sha1_basic() to take an "extras" parameter, which it passes through to is_refname_available(). * Change ref_transaction_commit() to pass "affected_refnames" to lock_ref_sha1_basic() as its "extras" argument. This change fixes a test case in t1404. This code is a bit stricter than it needs to be. We could conceivably allow reference "refs/foo/bar" to be created in the same transaction as "refs/foo" is deleted (or vice versa). But that would be complicated to implement, because it is not possible to lock "refs/foo/bar" while "refs/foo" exists as a loose reference, but on the other hand we don't want to delete some references before adding others (because that could leave a gap during which required objects are unreachable). There is also a complication that reflog files' paths can conflict. Any less-strict implementation would probably require tricks like the packing of all references before the start of the real transaction, or the use of temporary intermediate reference names. So for now let's accept too-strict checks. Some reference update transactions will be rejected unnecessarily, but they will be rejected in their entirety rather than leaving the repository in an intermediate state, as would happen now. Please note that there is still one kind of D/F conflict that is *not* handled correctly. If two processes are running at the same time, and one tries to create "refs/foo" at the same time that the other tries to create "refs/foo/bar", then they can race with each other. Both processes can obtain their respective locks ("refs/foo.lock" and "refs/foo/bar.lock"), proceed to the "commit" phase of ref_transaction_commit(), and then the slower process will discover that it cannot rename its lockfile into place (after possibly having committed changes to other references). There appears to be no way to fix this race without changing the locking policy, which in turn would require a change to *all* Git clients. Signed-off-by: Michael Haggerty <[email protected]>
1 parent 07f9c88 commit e911104

File tree

2 files changed

+95
-63
lines changed

2 files changed

+95
-63
lines changed

refs.c

Lines changed: 94 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -859,19 +859,22 @@ static int nonmatching_ref_fn(struct ref_entry *entry, void *vdata)
859859

860860
/*
861861
* Return true iff a reference named refname could be created without
862-
* conflicting with the name of an existing reference in dir. If
863-
* skip is non-NULL, ignore potential conflicts with refs in skip
864-
* (e.g., because they are scheduled for deletion in the same
865-
* operation).
862+
* conflicting with the name of an existing reference in dir. If
863+
* extras is non-NULL, it is a list of additional refnames with which
864+
* refname is not allowed to conflict. If skip is non-NULL, ignore
865+
* potential conflicts with refs in skip (e.g., because they are
866+
* scheduled for deletion in the same operation). Behavior is
867+
* undefined if the same name is listed in both extras and skip.
866868
*
867869
* Two reference names conflict if one of them exactly matches the
868870
* leading components of the other; e.g., "refs/foo/bar" conflicts
869871
* with both "refs/foo" and with "refs/foo/bar/baz" but not with
870872
* "refs/foo/bar" or "refs/foo/barbados".
871873
*
872-
* skip must be sorted.
874+
* extras and skip must be sorted.
873875
*/
874876
static int is_refname_available(const char *refname,
877+
const struct string_list *extras,
875878
const struct string_list *skip,
876879
struct ref_dir *dir)
877880
{
@@ -895,51 +898,53 @@ static int is_refname_available(const char *refname,
895898
* "refs/foo"; if there is a reference with that name,
896899
* it is a conflict, *unless* it is in skip.
897900
*/
898-
pos = search_ref_dir(dir, dirname.buf, dirname.len);
899-
if (pos >= 0) {
900-
/*
901-
* We found a reference whose name is a proper
902-
* prefix of refname; e.g., "refs/foo".
903-
*/
904-
if (skip && string_list_has_string(skip, dirname.buf)) {
901+
if (dir) {
902+
pos = search_ref_dir(dir, dirname.buf, dirname.len);
903+
if (pos >= 0 &&
904+
(!skip || !string_list_has_string(skip, dirname.buf))) {
905905
/*
906-
* The reference we just found, e.g.,
907-
* "refs/foo", is also in skip, so it
908-
* is not considered a conflict.
909-
* Moreover, the fact that "refs/foo"
910-
* exists means that there cannot be
911-
* any references anywhere under the
912-
* "refs/foo/" namespace (because they
913-
* would have conflicted with
914-
* "refs/foo"). So we can stop looking
915-
* now and return true.
906+
* We found a reference whose name is
907+
* a proper prefix of refname; e.g.,
908+
* "refs/foo", and is not in skip.
916909
*/
917-
ret = 1;
910+
error("'%s' exists; cannot create '%s'",
911+
dirname.buf, refname);
918912
goto cleanup;
919913
}
920-
error("'%s' exists; cannot create '%s'", dirname.buf, refname);
921-
goto cleanup;
922914
}
923915

916+
if (extras && string_list_has_string(extras, dirname.buf) &&
917+
(!skip || !string_list_has_string(skip, dirname.buf))) {
918+
error("cannot process '%s' and '%s' at the same time",
919+
refname, dirname.buf);
920+
goto cleanup;
921+
}
924922

925923
/*
926924
* Otherwise, we can try to continue our search with
927925
* the next component. So try to look up the
928-
* directory, e.g., "refs/foo/".
926+
* directory, e.g., "refs/foo/". If we come up empty,
927+
* we know there is nothing under this whole prefix,
928+
* but even in that case we still have to continue the
929+
* search for conflicts with extras.
929930
*/
930931
strbuf_addch(&dirname, '/');
931-
pos = search_ref_dir(dir, dirname.buf, dirname.len);
932-
if (pos < 0) {
933-
/*
934-
* There was no directory "refs/foo/", so
935-
* there is nothing under this whole prefix,
936-
* and we are OK.
937-
*/
938-
ret = 1;
939-
goto cleanup;
932+
if (dir) {
933+
pos = search_ref_dir(dir, dirname.buf, dirname.len);
934+
if (pos < 0) {
935+
/*
936+
* There was no directory "refs/foo/",
937+
* so there is nothing under this
938+
* whole prefix. So there is no need
939+
* to continue looking for conflicting
940+
* references. But we need to continue
941+
* looking for conflicting extras.
942+
*/
943+
dir = NULL;
944+
} else {
945+
dir = get_ref_dir(dir->entries[pos]);
946+
}
940947
}
941-
942-
dir = get_ref_dir(dir->entries[pos]);
943948
}
944949

945950
/*
@@ -952,30 +957,56 @@ static int is_refname_available(const char *refname,
952957
*/
953958
strbuf_addstr(&dirname, refname + dirname.len);
954959
strbuf_addch(&dirname, '/');
955-
pos = search_ref_dir(dir, dirname.buf, dirname.len);
956960

957-
if (pos >= 0) {
961+
if (dir) {
962+
pos = search_ref_dir(dir, dirname.buf, dirname.len);
963+
964+
if (pos >= 0) {
965+
/*
966+
* We found a directory named "$refname/"
967+
* (e.g., "refs/foo/bar/"). It is a problem
968+
* iff it contains any ref that is not in
969+
* "skip".
970+
*/
971+
struct nonmatching_ref_data data;
972+
973+
data.skip = skip;
974+
data.conflicting_refname = NULL;
975+
dir = get_ref_dir(dir->entries[pos]);
976+
sort_ref_dir(dir);
977+
if (do_for_each_entry_in_dir(dir, 0, nonmatching_ref_fn, &data)) {
978+
error("'%s' exists; cannot create '%s'",
979+
data.conflicting_refname, refname);
980+
goto cleanup;
981+
}
982+
}
983+
}
984+
985+
if (extras) {
958986
/*
959-
* We found a directory named "$refname/" (e.g.,
960-
* "refs/foo/bar/"). It is a problem iff it contains
961-
* any ref that is not in "skip".
987+
* Check for entries in extras that start with
988+
* "$refname/". We do that by looking for the place
989+
* where "$refname/" would be inserted in extras. If
990+
* there is an entry at that position that starts with
991+
* "$refname/" and is not in skip, then we have a
992+
* conflict.
962993
*/
963-
struct nonmatching_ref_data data;
964-
struct ref_entry *entry = dir->entries[pos];
965-
966-
dir = get_ref_dir(entry);
967-
data.skip = skip;
968-
sort_ref_dir(dir);
969-
if (!do_for_each_entry_in_dir(dir, 0, nonmatching_ref_fn, &data)) {
970-
ret = 1;
971-
goto cleanup;
972-
}
994+
for (pos = string_list_find_insert_index(extras, dirname.buf, 0);
995+
pos < extras->nr; pos++) {
996+
const char *extra_refname = extras->items[pos].string;
973997

974-
error("'%s' exists; cannot create '%s'",
975-
data.conflicting_refname, refname);
976-
goto cleanup;
998+
if (!starts_with(extra_refname, dirname.buf))
999+
break;
1000+
1001+
if (!skip || !string_list_has_string(skip, extra_refname)) {
1002+
error("cannot process '%s' and '%s' at the same time",
1003+
refname, extra_refname);
1004+
goto cleanup;
1005+
}
1006+
}
9771007
}
9781008

1009+
/* No conflicts were found */
9791010
ret = 1;
9801011

9811012
cleanup:
@@ -2296,6 +2327,7 @@ int dwim_log(const char *str, int len, unsigned char *sha1, char **log)
22962327
*/
22972328
static struct ref_lock *lock_ref_sha1_basic(const char *refname,
22982329
const unsigned char *old_sha1,
2330+
const struct string_list *extras,
22992331
const struct string_list *skip,
23002332
unsigned int flags, int *type_p)
23012333
{
@@ -2351,7 +2383,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
23512383
* our refname.
23522384
*/
23532385
if (is_null_sha1(lock->old_sha1) &&
2354-
!is_refname_available(refname, skip, get_packed_refs(&ref_cache))) {
2386+
!is_refname_available(refname, extras, skip, get_packed_refs(&ref_cache))) {
23552387
last_errno = ENOTDIR;
23562388
goto error_return;
23572389
}
@@ -2792,8 +2824,8 @@ static int rename_ref_available(const char *oldname, const char *newname)
27922824
int ret;
27932825

27942826
string_list_insert(&skip, oldname);
2795-
ret = is_refname_available(newname, &skip, get_packed_refs(&ref_cache))
2796-
&& is_refname_available(newname, &skip, get_loose_refs(&ref_cache));
2827+
ret = is_refname_available(newname, NULL, &skip, get_packed_refs(&ref_cache))
2828+
&& is_refname_available(newname, NULL, &skip, get_loose_refs(&ref_cache));
27972829
string_list_clear(&skip, 0);
27982830
return ret;
27992831
}
@@ -2851,7 +2883,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
28512883

28522884
logmoved = log;
28532885

2854-
lock = lock_ref_sha1_basic(newrefname, NULL, NULL, 0, NULL);
2886+
lock = lock_ref_sha1_basic(newrefname, NULL, NULL, NULL, 0, NULL);
28552887
if (!lock) {
28562888
error("unable to lock %s for update", newrefname);
28572889
goto rollback;
@@ -2865,7 +2897,7 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
28652897
return 0;
28662898

28672899
rollback:
2868-
lock = lock_ref_sha1_basic(oldrefname, NULL, NULL, 0, NULL);
2900+
lock = lock_ref_sha1_basic(oldrefname, NULL, NULL, NULL, 0, NULL);
28692901
if (!lock) {
28702902
error("unable to lock %s for rollback", oldrefname);
28712903
goto rollbacklog;
@@ -3777,7 +3809,7 @@ int ref_transaction_commit(struct ref_transaction *transaction,
37773809
update->refname,
37783810
((update->flags & REF_HAVE_OLD) ?
37793811
update->old_sha1 : NULL),
3780-
NULL,
3812+
&affected_refnames, NULL,
37813813
flags,
37823814
&update->type);
37833815
if (!update->lock) {
@@ -4054,7 +4086,7 @@ int reflog_expire(const char *refname, const unsigned char *sha1,
40544086
* reference itself, plus we might need to update the
40554087
* reference if --updateref was specified:
40564088
*/
4057-
lock = lock_ref_sha1_basic(refname, sha1, NULL, 0, &type);
4089+
lock = lock_ref_sha1_basic(refname, sha1, NULL, NULL, 0, &type);
40584090
if (!lock)
40594091
return error("cannot lock ref '%s'", refname);
40604092
if (!reflog_exists(refname)) {

t/t1404-update-ref-df-conflicts.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ test_expect_success 'new ref is a deeper prefix of existing packed' '
9696
9797
'
9898

99-
test_expect_failure 'one new ref is a simple prefix of another' '
99+
test_expect_success 'one new ref is a simple prefix of another' '
100100
101101
prefix=refs/5 &&
102102
test_update_rejected $prefix "a e" false "b c c/x d" \

0 commit comments

Comments
 (0)