aboutsummaryrefslogtreecommitdiffstats
path: root/refs.c
diff options
context:
space:
mode:
authorJunio C Hamano <gitster@pobox.com>2025-04-16 13:54:19 -0700
committerJunio C Hamano <gitster@pobox.com>2025-04-16 13:54:19 -0700
commit47478802daddf3f9916111307f153c6298ffc0bc (patch)
tree4caf590d3e027eda9a8b9c01c91da9927a672b90 /refs.c
parentMerge branch 'zy/send-email-error-handling' (diff)
parentupdate-ref: add --batch-updates flag for stdin mode (diff)
downloadgit-47478802daddf3f9916111307f153c6298ffc0bc.tar.gz
git-47478802daddf3f9916111307f153c6298ffc0bc.zip
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()
Diffstat (limited to 'refs.c')
-rw-r--r--refs.c171
1 files changed, 144 insertions, 27 deletions
diff --git a/refs.c b/refs.c
index 998d5c3565..5ade33835f 100644
--- a/refs.c
+++ b/refs.c
@@ -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 {