refs: move duplicate refname update check to generic layer

Move the tracking of refnames in `affected_refnames` from individual
backends into the generic layer in 'refs.c'. This centralizes the
duplicate refname detection that was previously handled separately by
each backend.

Make some changes to accommodate this move:

  - Add a `string_list` field `refnames` to `ref_transaction` to contain
    all the references in a transaction. This field is updated whenever
    a new update is added via `ref_transaction_add_update`, so manual
    additions in reference backends are dropped.

  - Modify the backends to use this field internally as needed. The
    backends need to check if an update for refname already exists when
    splitting symrefs or adding an update for 'HEAD'.

  - In the reftable backend, within `reftable_be_transaction_prepare()`,
    move the `string_list_has_string()` check above
    `ref_transaction_add_update()`. Since `ref_transaction_add_update()`
    automatically adds the refname to `transaction->refnames`,
    performing the check after will always return true, so we perform
    the check before adding the update.

This helps reduce duplication of functionality between the backends and
makes it easier to make changes in a more centralized manner.

Signed-off-by: Karthik Nayak <karthik.188@gmail.com>
Acked-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Karthik Nayak
2025-04-08 10:51:06 +02:00
committed by Junio C Hamano
parent 05a1834e42
commit c3baddf04f
5 changed files with 51 additions and 114 deletions

View File

@@ -1076,7 +1076,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
struct reftable_ref_store *refs =
reftable_be_downcast(ref_store, REF_STORE_WRITE|REF_STORE_MAIN, "ref_transaction_prepare");
struct strbuf referent = STRBUF_INIT, head_referent = STRBUF_INIT;
struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
struct string_list refnames_to_check = STRING_LIST_INIT_NODUP;
struct reftable_transaction_data *tx_data = NULL;
struct reftable_backend *be;
@@ -1101,10 +1100,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
transaction->updates[i], err);
if (ret)
goto done;
if (!(transaction->updates[i]->flags & REF_LOG_ONLY))
string_list_append(&affected_refnames,
transaction->updates[i]->refname);
}
/*
@@ -1116,17 +1111,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
tx_data->args[i].updates_alloc = tx_data->args[i].updates_expected;
}
/*
* Fail if a refname appears more than once in the transaction.
* This code is taken from the files backend and is a good candidate to
* be moved into the generic layer.
*/
string_list_sort(&affected_refnames);
if (ref_update_reject_duplicates(&affected_refnames, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto done;
}
/*
* TODO: it's dubious whether we should reload the stack that "HEAD"
* belongs to or not. In theory, it may happen that we only modify
@@ -1194,14 +1178,12 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
!(u->flags & REF_LOG_ONLY) &&
!(u->flags & REF_UPDATE_VIA_HEAD) &&
!strcmp(rewritten_ref, head_referent.buf)) {
struct ref_update *new_update;
/*
* First make sure that HEAD is not already in the
* transaction. This check is O(lg N) in the transaction
* size, but it happens at most once per transaction.
*/
if (string_list_has_string(&affected_refnames, "HEAD")) {
if (string_list_has_string(&transaction->refnames, "HEAD")) {
/* An entry already existed */
strbuf_addf(err,
_("multiple updates for 'HEAD' (including one "
@@ -1211,12 +1193,11 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
goto done;
}
new_update = ref_transaction_add_update(
transaction, "HEAD",
u->flags | REF_LOG_ONLY | REF_NO_DEREF,
&u->new_oid, &u->old_oid, NULL, NULL, NULL,
u->msg);
string_list_insert(&affected_refnames, new_update->refname);
ref_transaction_add_update(
transaction, "HEAD",
u->flags | REF_LOG_ONLY | REF_NO_DEREF,
&u->new_oid, &u->old_oid, NULL, NULL, NULL,
u->msg);
}
ret = reftable_backend_read_ref(be, rewritten_ref,
@@ -1281,6 +1262,15 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
if (!strcmp(rewritten_ref, "HEAD"))
new_flags |= REF_UPDATE_VIA_HEAD;
if (string_list_has_string(&transaction->refnames, referent.buf)) {
strbuf_addf(err,
_("multiple updates for '%s' (including one "
"via symref '%s') are not allowed"),
referent.buf, u->refname);
ret = TRANSACTION_NAME_CONFLICT;
goto done;
}
/*
* If we are updating a symref (eg. HEAD), we should also
* update the branch that the symref points to.
@@ -1305,16 +1295,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
*/
u->flags |= REF_LOG_ONLY | REF_NO_DEREF;
u->flags &= ~REF_HAVE_OLD;
if (string_list_has_string(&affected_refnames, new_update->refname)) {
strbuf_addf(err,
_("multiple updates for '%s' (including one "
"via symref '%s') are not allowed"),
referent.buf, u->refname);
ret = TRANSACTION_NAME_CONFLICT;
goto done;
}
string_list_insert(&affected_refnames, new_update->refname);
}
}
@@ -1383,7 +1363,8 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
}
}
ret = refs_verify_refnames_available(ref_store, &refnames_to_check, &affected_refnames, NULL,
ret = refs_verify_refnames_available(ref_store, &refnames_to_check,
&transaction->refnames, NULL,
transaction->flags & REF_TRANSACTION_FLAG_INITIAL,
err);
if (ret < 0)
@@ -1401,7 +1382,6 @@ done:
strbuf_addf(err, _("reftable: transaction prepare: %s"),
reftable_error_str(ret));
}
string_list_clear(&affected_refnames, 0);
strbuf_release(&referent);
strbuf_release(&head_referent);
string_list_clear(&refnames_to_check, 0);