refs/files: support symbolic and root refs in initial transaction

The "files" backend has implemented special logic when committing the first transactions in an otherwise empty ref store: instead of writing all refs as separate loose files, it instead knows to write them all into a "packed-refs" file directly. This is significantly more efficient than having to write each of the refs as separate "loose" ref. The only user of this optimization is git-clone(1), which only uses this mechanism to write regular refs. Consequently, the implementation does not know how to handle both symbolic and root refs. While fine in the context of git-clone(1), this keeps us from using the mechanism in more cases. Adapt the logic to also support symbolic and root refs by using a second transaction that we use for all of the refs that need to be written as loose refs. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Nov 20, 2024 at 08:51 UTC c0b9cf3b55e9e1d6ea2d848bae1f8efe67b3b61b
1 file changed +34 -10
refs/files-backend.c
+34 -10
@@ -2995,6 +2995,7 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
2995 int ret = 0;
2996 struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
2997 struct ref_transaction *packed_transaction = NULL;
2998 + struct ref_transaction *loose_transaction = NULL;
2999
3000 assert(err);
3001
@@ -3040,6 +3041,7 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
3041 if ((update->flags & REF_HAVE_OLD) &&
3042 !is_null_oid(&update->old_oid))
3043 BUG("initial ref transaction with old_sha1 set");
3044 +
3045 if (refs_verify_refname_available(&refs->base, update->refname,
3046 &affected_refnames, NULL,
3047 err)) {
@@ -3048,26 +3050,48 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
3050 }
3051
3052 /*
3051 - * Add a reference creation for this reference to the
3052 - * packed-refs transaction:
3053 + * packed-refs don't support symbolic refs and root refs, so we
3054 + * have to queue these references via the loose transaction.
3055 */
3054 - ref_transaction_add_update(packed_transaction, update->refname,
3055 - update->flags & ~REF_HAVE_OLD,
3056 - &update->new_oid, &update->old_oid,
3057 - NULL, NULL, NULL);
3056 + if (update->new_target || is_root_ref(update->refname)) {
3057 + if (!loose_transaction) {
3058 + loose_transaction = ref_store_transaction_begin(&refs->base, 0, err);
3059 + if (!loose_transaction) {
3060 + ret = TRANSACTION_GENERIC_ERROR;
3061 + goto cleanup;
3062 + }
3063 + }
3064 +
3065 + ref_transaction_add_update(loose_transaction, update->refname,
3066 + update->flags & ~REF_HAVE_OLD,
3067 + update->new_target ? NULL : &update->new_oid, NULL,
3068 + update->new_target, NULL, NULL);
3069 + } else {
3070 + ref_transaction_add_update(packed_transaction, update->refname,
3071 + update->flags & ~REF_HAVE_OLD,
3072 + &update->new_oid, &update->old_oid,
3073 + NULL, NULL, NULL);
3074 + }
3075 }
3076
3060 - if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
3077 + if (packed_refs_lock(refs->packed_ref_store, 0, err) ||
3078 + ref_transaction_commit(packed_transaction, err)) {
3079 ret = TRANSACTION_GENERIC_ERROR;
3080 goto cleanup;
3081 }
3082 + packed_refs_unlock(refs->packed_ref_store);
3083
3065 - if (ref_transaction_commit(packed_transaction, err)) {
3066 - ret = TRANSACTION_GENERIC_ERROR;
3084 + if (loose_transaction) {
3085 + if (ref_transaction_prepare(loose_transaction, err) ||
3086 + ref_transaction_commit(loose_transaction, err)) {
3087 + ret = TRANSACTION_GENERIC_ERROR;
3088 + goto cleanup;
3089 + }
3090 }
3091
3069 - packed_refs_unlock(refs->packed_ref_store);
3092 cleanup:
3093 + if (loose_transaction)
3094 + ref_transaction_free(loose_transaction);
3095 if (packed_transaction)
3096 ref_transaction_free(packed_transaction);
3097 transaction->state = REF_TRANSACTION_CLOSED;