Merge branch 'kn/non-transactional-batch-updates'

Updating multiple references have only been possible in all-or-none
fashion with transactions, but it can be more efficient to batch
multiple updates even when some of them are allowed to fail in a
best-effort manner.  A new "best effort batches of updates" mode
has been introduced.

* kn/non-transactional-batch-updates:
  update-ref: add --batch-updates flag for stdin mode
  refs: support rejection in batch updates during F/D checks
  refs: implement batch reference update support
  refs: introduce enum-based transaction error types
  refs/reftable: extract code from the transaction preparation
  refs/files: remove duplicate duplicates check
  refs: move duplicate refname update check to generic layer
  refs/files: remove redundant check in split_symref_update()
This commit is contained in:
Junio C Hamano
2025-04-16 13:54:19 -07:00
10 changed files with 969 additions and 523 deletions

171
refs.c
View File

@@ -1176,6 +1176,11 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,
CALLOC_ARRAY(tr, 1);
tr->ref_store = refs;
tr->flags = flags;
string_list_init_dup(&tr->refnames);
if (flags & REF_TRANSACTION_ALLOW_FAILURE)
CALLOC_ARRAY(tr->rejections, 1);
return tr;
}
@@ -1206,10 +1211,45 @@ void ref_transaction_free(struct ref_transaction *transaction)
free((char *)transaction->updates[i]->old_target);
free(transaction->updates[i]);
}
if (transaction->rejections)
free(transaction->rejections->update_indices);
free(transaction->rejections);
string_list_clear(&transaction->refnames, 0);
free(transaction->updates);
free(transaction);
}
int ref_transaction_maybe_set_rejected(struct ref_transaction *transaction,
size_t update_idx,
enum ref_transaction_error err)
{
if (update_idx >= transaction->nr)
BUG("trying to set rejection on invalid update index");
if (!(transaction->flags & REF_TRANSACTION_ALLOW_FAILURE))
return 0;
if (!transaction->rejections)
BUG("transaction not inititalized with failure support");
/*
* Don't accept generic errors, since these errors are not user
* input related.
*/
if (err == REF_TRANSACTION_ERROR_GENERIC)
return 0;
transaction->updates[update_idx]->rejection_err = err;
ALLOC_GROW(transaction->rejections->update_indices,
transaction->rejections->nr + 1,
transaction->rejections->alloc);
transaction->rejections->update_indices[transaction->rejections->nr++] = update_idx;
return 1;
}
struct ref_update *ref_transaction_add_update(
struct ref_transaction *transaction,
const char *refname, unsigned int flags,
@@ -1219,6 +1259,7 @@ struct ref_update *ref_transaction_add_update(
const char *committer_info,
const char *msg)
{
struct string_list_item *item;
struct ref_update *update;
if (transaction->state != REF_TRANSACTION_OPEN)
@@ -1234,6 +1275,7 @@ struct ref_update *ref_transaction_add_update(
transaction->updates[transaction->nr++] = update;
update->flags = flags;
update->rejection_err = 0;
update->new_target = xstrdup_or_null(new_target);
update->old_target = xstrdup_or_null(old_target);
@@ -1246,6 +1288,16 @@ struct ref_update *ref_transaction_add_update(
update->msg = normalize_reflog_message(msg);
}
/*
* This list is generally used by the backends to avoid duplicates.
* But we do support multiple log updates for a given refname within
* a single transaction.
*/
if (!(update->flags & REF_LOG_ONLY)) {
item = string_list_append(&transaction->refnames, refname);
item->util = update;
}
return update;
}
@@ -2279,7 +2331,7 @@ int refs_update_symref_extended(struct ref_store *refs, const char *ref,
REF_NO_DEREF, logmsg, &err))
goto error_return;
prepret = ref_transaction_prepare(transaction, &err);
if (prepret && prepret != TRANSACTION_CREATE_EXISTS)
if (prepret && prepret != REF_TRANSACTION_ERROR_CREATE_EXISTS)
goto error_return;
} else {
if (ref_transaction_update(transaction, ref, NULL, NULL,
@@ -2297,7 +2349,7 @@ int refs_update_symref_extended(struct ref_store *refs, const char *ref,
}
}
if (prepret == TRANSACTION_CREATE_EXISTS)
if (prepret == REF_TRANSACTION_ERROR_CREATE_EXISTS)
goto cleanup;
if (ref_transaction_commit(transaction, &err))
@@ -2311,8 +2363,13 @@ cleanup:
return ret;
}
int ref_update_reject_duplicates(struct string_list *refnames,
struct strbuf *err)
/*
* Write an error to `err` and return a nonzero value iff the same
* refname appears multiple times in `refnames`. `refnames` must be
* sorted on entry to this function.
*/
static int ref_update_reject_duplicates(struct string_list *refnames,
struct strbuf *err)
{
size_t i, n = refnames->nr;
@@ -2426,6 +2483,10 @@ int ref_transaction_prepare(struct ref_transaction *transaction,
return -1;
}
string_list_sort(&transaction->refnames);
if (ref_update_reject_duplicates(&transaction->refnames, err))
return REF_TRANSACTION_ERROR_GENERIC;
ret = refs->be->transaction_prepare(refs, transaction, err);
if (ret)
return ret;
@@ -2496,19 +2557,21 @@ int ref_transaction_commit(struct ref_transaction *transaction,
return ret;
}
int refs_verify_refnames_available(struct ref_store *refs,
const struct string_list *refnames,
const struct string_list *extras,
const struct string_list *skip,
unsigned int initial_transaction,
struct strbuf *err)
enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs,
const struct string_list *refnames,
const struct string_list *extras,
const struct string_list *skip,
struct ref_transaction *transaction,
unsigned int initial_transaction,
struct strbuf *err)
{
struct strbuf dirname = STRBUF_INIT;
struct strbuf referent = STRBUF_INIT;
struct string_list_item *item;
struct ref_iterator *iter = NULL;
struct strset conflicting_dirnames;
struct strset dirnames;
int ret = -1;
int ret = REF_TRANSACTION_ERROR_NAME_CONFLICT;
/*
* For the sake of comments in this function, suppose that
@@ -2517,9 +2580,11 @@ int refs_verify_refnames_available(struct ref_store *refs,
assert(err);
strset_init(&conflicting_dirnames);
strset_init(&dirnames);
for_each_string_list_item(item, refnames) {
const size_t *update_idx = (size_t *)item->util;
const char *refname = item->string;
const char *extra_refname;
struct object_id oid;
@@ -2557,14 +2622,30 @@ int refs_verify_refnames_available(struct ref_store *refs,
continue;
if (!initial_transaction &&
!refs_read_raw_ref(refs, dirname.buf, &oid, &referent,
&type, &ignore_errno)) {
(strset_contains(&conflicting_dirnames, dirname.buf) ||
!refs_read_raw_ref(refs, dirname.buf, &oid, &referent,
&type, &ignore_errno))) {
if (transaction && ref_transaction_maybe_set_rejected(
transaction, *update_idx,
REF_TRANSACTION_ERROR_NAME_CONFLICT)) {
strset_remove(&dirnames, dirname.buf);
strset_add(&conflicting_dirnames, dirname.buf);
continue;
}
strbuf_addf(err, _("'%s' exists; cannot create '%s'"),
dirname.buf, refname);
goto cleanup;
}
if (extras && string_list_has_string(extras, dirname.buf)) {
if (transaction && ref_transaction_maybe_set_rejected(
transaction, *update_idx,
REF_TRANSACTION_ERROR_NAME_CONFLICT)) {
strset_remove(&dirnames, dirname.buf);
continue;
}
strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"),
refname, dirname.buf);
goto cleanup;
@@ -2597,6 +2678,11 @@ int refs_verify_refnames_available(struct ref_store *refs,
string_list_has_string(skip, iter->refname))
continue;
if (transaction && ref_transaction_maybe_set_rejected(
transaction, *update_idx,
REF_TRANSACTION_ERROR_NAME_CONFLICT))
continue;
strbuf_addf(err, _("'%s' exists; cannot create '%s'"),
iter->refname, refname);
goto cleanup;
@@ -2608,6 +2694,11 @@ int refs_verify_refnames_available(struct ref_store *refs,
extra_refname = find_descendant_ref(dirname.buf, extras, skip);
if (extra_refname) {
if (transaction && ref_transaction_maybe_set_rejected(
transaction, *update_idx,
REF_TRANSACTION_ERROR_NAME_CONFLICT))
continue;
strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"),
refname, extra_refname);
goto cleanup;
@@ -2619,17 +2710,19 @@ int refs_verify_refnames_available(struct ref_store *refs,
cleanup:
strbuf_release(&referent);
strbuf_release(&dirname);
strset_clear(&conflicting_dirnames);
strset_clear(&dirnames);
ref_iterator_free(iter);
return ret;
}
int refs_verify_refname_available(struct ref_store *refs,
const char *refname,
const struct string_list *extras,
const struct string_list *skip,
unsigned int initial_transaction,
struct strbuf *err)
enum ref_transaction_error refs_verify_refname_available(
struct ref_store *refs,
const char *refname,
const struct string_list *extras,
const struct string_list *skip,
unsigned int initial_transaction,
struct strbuf *err)
{
struct string_list_item item = { .string = (char *) refname };
struct string_list refnames = {
@@ -2638,7 +2731,7 @@ int refs_verify_refname_available(struct ref_store *refs,
};
return refs_verify_refnames_available(refs, &refnames, extras, skip,
initial_transaction, err);
NULL, initial_transaction, err);
}
struct do_for_each_reflog_help {
@@ -2726,6 +2819,28 @@ void ref_transaction_for_each_queued_update(struct ref_transaction *transaction,
}
}
void ref_transaction_for_each_rejected_update(struct ref_transaction *transaction,
ref_transaction_for_each_rejected_update_fn cb,
void *cb_data)
{
if (!transaction->rejections)
return;
for (size_t i = 0; i < transaction->rejections->nr; i++) {
size_t update_index = transaction->rejections->update_indices[i];
struct ref_update *update = transaction->updates[update_index];
if (!update->rejection_err)
continue;
cb(update->refname,
(update->flags & REF_HAVE_OLD) ? &update->old_oid : NULL,
(update->flags & REF_HAVE_NEW) ? &update->new_oid : NULL,
update->old_target, update->new_target,
update->rejection_err, cb_data);
}
}
int refs_delete_refs(struct ref_store *refs, const char *logmsg,
struct string_list *refnames, unsigned int flags)
{
@@ -2817,8 +2932,9 @@ int ref_update_has_null_new_value(struct ref_update *update)
return !update->new_target && is_null_oid(&update->new_oid);
}
int ref_update_check_old_target(const char *referent, struct ref_update *update,
struct strbuf *err)
enum ref_transaction_error ref_update_check_old_target(const char *referent,
struct ref_update *update,
struct strbuf *err)
{
if (!update->old_target)
BUG("called without old_target set");
@@ -2826,17 +2942,18 @@ int ref_update_check_old_target(const char *referent, struct ref_update *update,
if (!strcmp(referent, update->old_target))
return 0;
if (!strcmp(referent, ""))
if (!strcmp(referent, "")) {
strbuf_addf(err, "verifying symref target: '%s': "
"reference is missing but expected %s",
ref_update_original_update_refname(update),
update->old_target);
else
strbuf_addf(err, "verifying symref target: '%s': "
"is at %s but expected %s",
return REF_TRANSACTION_ERROR_NONEXISTENT_REF;
}
strbuf_addf(err, "verifying symref target: '%s': is at %s but expected %s",
ref_update_original_update_refname(update),
referent, update->old_target);
return -1;
return REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE;
}
struct migration_data {