refs/files: batch refname availability checks for initial transactions

The "files" backend explicitly carves out special logic for its initial transaction so that it can avoid writing out every single reference as a loose reference. While the assumption is that there shouldn't be any preexisting references, we still have to verify that none of the newly written references will conflict with any other new reference in the same transaction. Refactor the initial transaction to use batched refname availability checks. This does not yet have an effect on performance as we still call `refs_verify_refname_available()` in a loop. But this will change in subsequent commits and then impact performance when cloning a repository with many references or when migrating references to the "files" format. This will improve performance when cloning a repository with many references or when migrating references from any format to the "files" format once the availability checks have learned to optimize checks for many references in a subsequent commit. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Mar 12, 2025 at 16:56 UTC 268ea8515cd11bd0f3f8c4d64373121058c3fac2
1 file changed +16 -7
refs/files-backend.c
+16 -7
@@ -3056,6 +3056,7 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
3056 size_t i;
3057 int ret = 0;
3058 struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
3059 + struct string_list refnames_to_check = STRING_LIST_INIT_NODUP;
3060 struct ref_transaction *packed_transaction = NULL;
3061 struct ref_transaction *loose_transaction = NULL;
3062
@@ -3105,11 +3106,7 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
3106 !is_null_oid(&update->old_oid))
3107 BUG("initial ref transaction with old_sha1 set");
3108
3108 - if (refs_verify_refname_available(&refs->base, update->refname,
3109 - &affected_refnames, NULL, 1, err)) {
3110 - ret = TRANSACTION_NAME_CONFLICT;
3111 - goto cleanup;
3112 - }
3109 + string_list_append(&refnames_to_check, update->refname);
3110
3111 /*
3112 * packed-refs don't support symbolic refs, root refs and reflogs,
@@ -3145,8 +3142,19 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
3142 }
3143 }
3144
3148 - if (packed_refs_lock(refs->packed_ref_store, 0, err) ||
3149 - ref_transaction_commit(packed_transaction, err)) {
3145 + if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
3146 + ret = TRANSACTION_GENERIC_ERROR;
3147 + goto cleanup;
3148 + }
3149 +
3150 + if (refs_verify_refnames_available(&refs->base, &refnames_to_check,
3151 + &affected_refnames, NULL, 1, err)) {
3152 + packed_refs_unlock(refs->packed_ref_store);
3153 + ret = TRANSACTION_NAME_CONFLICT;
3154 + goto cleanup;
3155 + }
3156 +
3157 + if (ref_transaction_commit(packed_transaction, err)) {
3158 ret = TRANSACTION_GENERIC_ERROR;
3159 goto cleanup;
3160 }
@@ -3167,6 +3175,7 @@ cleanup:
3175 ref_transaction_free(packed_transaction);
3176 transaction->state = REF_TRANSACTION_CLOSED;
3177 string_list_clear(&affected_refnames, 0);
3178 + string_list_clear(&refnames_to_check, 0);
3179 return ret;
3180 }
3181