mirror of
https://github.com/git/git.git
synced 2025-12-12 20:36:24 +01:00
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:
171
refs.c
171
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 {
|
||||
|
||||
Reference in New Issue
Block a user