aboutsummaryrefslogtreecommitdiffstats
path: root/refs
diff options
context:
space:
mode:
Diffstat (limited to 'refs')
-rw-r--r--refs/debug.c2
-rw-r--r--refs/files-backend.c320
-rw-r--r--refs/packed-backend.c73
-rw-r--r--refs/refs-internal.h51
-rw-r--r--refs/reftable-backend.c502
5 files changed, 479 insertions, 469 deletions
diff --git a/refs/debug.c b/refs/debug.c
index 5390fa9c18..485e3079d7 100644
--- a/refs/debug.c
+++ b/refs/debug.c
@@ -227,7 +227,7 @@ static int debug_read_raw_ref(struct ref_store *ref_store, const char *refname,
struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store;
int res = 0;
- oidcpy(oid, null_oid());
+ oidcpy(oid, null_oid(ref_store->repo->hash_algo));
res = drefs->refs->be->read_raw_ref(drefs->refs, refname, oid, referent,
type, failure_errno);
diff --git a/refs/files-backend.c b/refs/files-backend.c
index ff54a4bb7e..4d1f65a57a 100644
--- a/refs/files-backend.c
+++ b/refs/files-backend.c
@@ -663,7 +663,7 @@ static void unlock_ref(struct ref_lock *lock)
* broken, lock the reference anyway but clear old_oid.
*
* Return 0 on success. On failure, write an error message to err and
- * return TRANSACTION_NAME_CONFLICT or TRANSACTION_GENERIC_ERROR.
+ * return REF_TRANSACTION_ERROR_NAME_CONFLICT or REF_TRANSACTION_ERROR_GENERIC.
*
* Implementation note: This function is basically
*
@@ -676,19 +676,22 @@ static void unlock_ref(struct ref_lock *lock)
* avoided, namely if we were successfully able to read the ref
* - Generate informative error messages in the case of failure
*/
-static int lock_raw_ref(struct files_ref_store *refs,
- const char *refname, int mustexist,
- struct string_list *refnames_to_check,
- const struct string_list *extras,
- struct ref_lock **lock_p,
- struct strbuf *referent,
- unsigned int *type,
- struct strbuf *err)
-{
+static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs,
+ struct ref_update *update,
+ size_t update_idx,
+ int mustexist,
+ struct string_list *refnames_to_check,
+ const struct string_list *extras,
+ struct ref_lock **lock_p,
+ struct strbuf *referent,
+ struct strbuf *err)
+{
+ enum ref_transaction_error ret = REF_TRANSACTION_ERROR_GENERIC;
+ const char *refname = update->refname;
+ unsigned int *type = &update->type;
struct ref_lock *lock;
struct strbuf ref_file = STRBUF_INIT;
int attempts_remaining = 3;
- int ret = TRANSACTION_GENERIC_ERROR;
int failure_errno;
assert(err);
@@ -705,7 +708,7 @@ static int lock_raw_ref(struct files_ref_store *refs,
files_ref_path(refs, &ref_file, refname);
retry:
- switch (safe_create_leading_directories(ref_file.buf)) {
+ switch (safe_create_leading_directories(the_repository, ref_file.buf)) {
case SCLD_OK:
break; /* success */
case SCLD_EXISTS:
@@ -728,13 +731,14 @@ retry:
strbuf_reset(err);
strbuf_addf(err, "unable to resolve reference '%s'",
refname);
+ ret = REF_TRANSACTION_ERROR_NONEXISTENT_REF;
} else {
/*
* The error message set by
* refs_verify_refname_available() is
* OK.
*/
- ret = TRANSACTION_NAME_CONFLICT;
+ ret = REF_TRANSACTION_ERROR_NAME_CONFLICT;
}
} else {
/*
@@ -783,11 +787,14 @@ retry:
if (files_read_raw_ref(&refs->base, refname, &lock->old_oid, referent,
type, &failure_errno)) {
+ struct string_list_item *item;
+
if (failure_errno == ENOENT) {
if (mustexist) {
/* Garden variety missing reference. */
strbuf_addf(err, "unable to resolve reference '%s'",
refname);
+ ret = REF_TRANSACTION_ERROR_NONEXISTENT_REF;
goto error_return;
} else {
/*
@@ -820,6 +827,7 @@ retry:
/* Garden variety missing reference. */
strbuf_addf(err, "unable to resolve reference '%s'",
refname);
+ ret = REF_TRANSACTION_ERROR_NONEXISTENT_REF;
goto error_return;
} else if (remove_dir_recursively(&ref_file,
REMOVE_DIR_EMPTY_ONLY)) {
@@ -830,7 +838,7 @@ retry:
* The error message set by
* verify_refname_available() is OK.
*/
- ret = TRANSACTION_NAME_CONFLICT;
+ ret = REF_TRANSACTION_ERROR_NAME_CONFLICT;
goto error_return;
} else {
/*
@@ -860,7 +868,9 @@ retry:
* make sure there is no existing packed ref that conflicts
* with refname. This check is deferred so that we can batch it.
*/
- string_list_append(refnames_to_check, refname);
+ item = string_list_append(refnames_to_check, refname);
+ item->util = xmalloc(sizeof(update_idx));
+ memcpy(item->util, &update_idx, sizeof(update_idx));
}
ret = 0;
@@ -1109,7 +1119,7 @@ retry_fn:
strbuf_addstr(&path_copy, path);
do {
- scld_result = safe_create_leading_directories(path_copy.buf);
+ scld_result = safe_create_leading_directories(the_repository, path_copy.buf);
if (scld_result == SCLD_OK)
goto retry_fn;
} while (scld_result == SCLD_VANISHED && create_directories_remaining-- > 0);
@@ -1265,7 +1275,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r)
ref_transaction_add_update(
transaction, r->name,
REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING,
- null_oid(), &r->oid, NULL, NULL, NULL, NULL);
+ null_oid(the_hash_algo), &r->oid, NULL, NULL, NULL, NULL);
if (ref_transaction_commit(transaction, &err))
goto cleanup;
@@ -1517,10 +1527,11 @@ static int rename_tmp_log(struct files_ref_store *refs, const char *newrefname)
return ret;
}
-static int write_ref_to_lockfile(struct files_ref_store *refs,
- struct ref_lock *lock,
- const struct object_id *oid,
- int skip_oid_verification, struct strbuf *err);
+static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs,
+ struct ref_lock *lock,
+ const struct object_id *oid,
+ int skip_oid_verification,
+ struct strbuf *err);
static int commit_ref_update(struct files_ref_store *refs,
struct ref_lock *lock,
const struct object_id *oid, const char *logmsg,
@@ -1926,10 +1937,11 @@ static int files_log_ref_write(struct files_ref_store *refs,
* Write oid into the open lockfile, then close the lockfile. On
* errors, rollback the lockfile, fill in *err and return -1.
*/
-static int write_ref_to_lockfile(struct files_ref_store *refs,
- struct ref_lock *lock,
- const struct object_id *oid,
- int skip_oid_verification, struct strbuf *err)
+static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs,
+ struct ref_lock *lock,
+ const struct object_id *oid,
+ int skip_oid_verification,
+ struct strbuf *err)
{
static char term = '\n';
struct object *o;
@@ -1943,7 +1955,7 @@ static int write_ref_to_lockfile(struct files_ref_store *refs,
"trying to write ref '%s' with nonexistent object %s",
lock->ref_name, oid_to_hex(oid));
unlock_ref(lock);
- return -1;
+ return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
}
if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) {
strbuf_addf(
@@ -1951,7 +1963,7 @@ static int write_ref_to_lockfile(struct files_ref_store *refs,
"trying to write non-commit object %s to branch '%s'",
oid_to_hex(oid), lock->ref_name);
unlock_ref(lock);
- return -1;
+ return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
}
}
fd = get_lock_file_fd(&lock->lk);
@@ -1962,7 +1974,7 @@ static int write_ref_to_lockfile(struct files_ref_store *refs,
strbuf_addf(err,
"couldn't write '%s'", get_lock_file_path(&lock->lk));
unlock_ref(lock);
- return -1;
+ return REF_TRANSACTION_ERROR_GENERIC;
}
return 0;
}
@@ -2376,13 +2388,11 @@ static struct ref_iterator *files_reflog_iterator_begin(struct ref_store *ref_st
* If update is a direct update of head_ref (the reference pointed to
* by HEAD), then add an extra REF_LOG_ONLY update for HEAD.
*/
-static int split_head_update(struct ref_update *update,
- struct ref_transaction *transaction,
- const char *head_ref,
- struct string_list *affected_refnames,
- struct strbuf *err)
+static enum ref_transaction_error split_head_update(struct ref_update *update,
+ struct ref_transaction *transaction,
+ const char *head_ref,
+ struct strbuf *err)
{
- struct string_list_item *item;
struct ref_update *new_update;
if ((update->flags & REF_LOG_ONLY) ||
@@ -2399,13 +2409,13 @@ static int split_head_update(struct ref_update *update,
* 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 "
"via its referent '%s') are not allowed",
update->refname);
- return TRANSACTION_NAME_CONFLICT;
+ return REF_TRANSACTION_ERROR_NAME_CONFLICT;
}
new_update = ref_transaction_add_update(
@@ -2421,8 +2431,6 @@ static int split_head_update(struct ref_update *update,
*/
if (strcmp(new_update->refname, "HEAD"))
BUG("%s unexpectedly not 'HEAD'", new_update->refname);
- item = string_list_insert(affected_refnames, new_update->refname);
- item->util = new_update;
return 0;
}
@@ -2435,13 +2443,11 @@ static int split_head_update(struct ref_update *update,
* Note that the new update will itself be subject to splitting when
* the iteration gets to it.
*/
-static int split_symref_update(struct ref_update *update,
- const char *referent,
- struct ref_transaction *transaction,
- struct string_list *affected_refnames,
- struct strbuf *err)
+static enum ref_transaction_error split_symref_update(struct ref_update *update,
+ const char *referent,
+ struct ref_transaction *transaction,
+ struct strbuf *err)
{
- struct string_list_item *item;
struct ref_update *new_update;
unsigned int new_flags;
@@ -2451,13 +2457,13 @@ static int split_symref_update(struct ref_update *update,
* size, but it happens at most once per symref in a
* transaction.
*/
- if (string_list_has_string(affected_refnames, referent)) {
+ if (string_list_has_string(&transaction->refnames, referent)) {
/* An entry already exists */
strbuf_addf(err,
"multiple updates for '%s' (including one "
"via symref '%s') are not allowed",
referent, update->refname);
- return TRANSACTION_NAME_CONFLICT;
+ return REF_TRANSACTION_ERROR_NAME_CONFLICT;
}
new_flags = update->flags;
@@ -2489,19 +2495,6 @@ static int split_symref_update(struct ref_update *update,
update->flags |= REF_LOG_ONLY | REF_NO_DEREF;
update->flags &= ~REF_HAVE_OLD;
- /*
- * Add the referent. This insertion is O(N) in the transaction
- * size, but it happens at most once per symref in a
- * transaction. Make sure to add new_update->refname, which will
- * be valid as long as affected_refnames is in use, and NOT
- * referent, which might soon be freed by our caller.
- */
- item = string_list_insert(affected_refnames, new_update->refname);
- if (item->util)
- BUG("%s unexpectedly found in affected_refnames",
- new_update->refname);
- item->util = new_update;
-
return 0;
}
@@ -2511,11 +2504,10 @@ static int split_symref_update(struct ref_update *update,
* everything is OK, return 0; otherwise, write an error message to
* err and return -1.
*/
-static int check_old_oid(struct ref_update *update, struct object_id *oid,
- struct strbuf *err)
+static enum ref_transaction_error check_old_oid(struct ref_update *update,
+ struct object_id *oid,
+ struct strbuf *err)
{
- int ret = TRANSACTION_GENERIC_ERROR;
-
if (!(update->flags & REF_HAVE_OLD) ||
oideq(oid, &update->old_oid))
return 0;
@@ -2524,21 +2516,20 @@ static int check_old_oid(struct ref_update *update, struct object_id *oid,
strbuf_addf(err, "cannot lock ref '%s': "
"reference already exists",
ref_update_original_update_refname(update));
- ret = TRANSACTION_CREATE_EXISTS;
- }
- else if (is_null_oid(oid))
+ return REF_TRANSACTION_ERROR_CREATE_EXISTS;
+ } else if (is_null_oid(oid)) {
strbuf_addf(err, "cannot lock ref '%s': "
"reference is missing but expected %s",
ref_update_original_update_refname(update),
oid_to_hex(&update->old_oid));
- else
- strbuf_addf(err, "cannot lock ref '%s': "
- "is at %s but expected %s",
- ref_update_original_update_refname(update),
- oid_to_hex(oid),
- oid_to_hex(&update->old_oid));
+ return REF_TRANSACTION_ERROR_NONEXISTENT_REF;
+ }
- return ret;
+ strbuf_addf(err, "cannot lock ref '%s': is at %s but expected %s",
+ ref_update_original_update_refname(update), oid_to_hex(oid),
+ oid_to_hex(&update->old_oid));
+
+ return REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE;
}
struct files_transaction_backend_data {
@@ -2560,18 +2551,18 @@ struct files_transaction_backend_data {
* - If it is an update of head_ref, add a corresponding REF_LOG_ONLY
* update of HEAD.
*/
-static int lock_ref_for_update(struct files_ref_store *refs,
- struct ref_update *update,
- struct ref_transaction *transaction,
- const char *head_ref,
- struct string_list *refnames_to_check,
- struct string_list *affected_refnames,
- struct strbuf *err)
+static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *refs,
+ struct ref_update *update,
+ size_t update_idx,
+ struct ref_transaction *transaction,
+ const char *head_ref,
+ struct string_list *refnames_to_check,
+ struct strbuf *err)
{
struct strbuf referent = STRBUF_INIT;
int mustexist = ref_update_expects_existing_old_ref(update);
struct files_transaction_backend_data *backend_data;
- int ret = 0;
+ enum ref_transaction_error ret = 0;
struct ref_lock *lock;
files_assert_main_repository(refs, "lock_ref_for_update");
@@ -2582,8 +2573,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
update->flags |= REF_DELETING;
if (head_ref) {
- ret = split_head_update(update, transaction, head_ref,
- affected_refnames, err);
+ ret = split_head_update(update, transaction, head_ref, err);
if (ret)
goto out;
}
@@ -2592,10 +2582,9 @@ static int lock_ref_for_update(struct files_ref_store *refs,
if (lock) {
lock->count++;
} else {
- ret = lock_raw_ref(refs, update->refname, mustexist,
- refnames_to_check, affected_refnames,
- &lock, &referent,
- &update->type, err);
+ ret = lock_raw_ref(refs, update, update_idx, mustexist,
+ refnames_to_check, &transaction->refnames,
+ &lock, &referent, err);
if (ret) {
char *reason;
@@ -2625,22 +2614,17 @@ static int lock_ref_for_update(struct files_ref_store *refs,
strbuf_addf(err, "cannot lock ref '%s': "
"error reading reference",
ref_update_original_update_refname(update));
- ret = TRANSACTION_GENERIC_ERROR;
+ ret = REF_TRANSACTION_ERROR_GENERIC;
goto out;
}
}
- if (update->old_target) {
- if (ref_update_check_old_target(referent.buf, update, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
- goto out;
- }
- } else {
+ if (update->old_target)
+ ret = ref_update_check_old_target(referent.buf, update, err);
+ else
ret = check_old_oid(update, &lock->old_oid, err);
- if (ret) {
- goto out;
- }
- }
+ if (ret)
+ goto out;
} else {
/*
* Create a new update for the reference this
@@ -2649,9 +2633,8 @@ static int lock_ref_for_update(struct files_ref_store *refs,
* of processing the split-off update, so we
* don't have to do it here.
*/
- ret = split_symref_update(update,
- referent.buf, transaction,
- affected_refnames, err);
+ ret = split_symref_update(update, referent.buf,
+ transaction, err);
if (ret)
goto out;
}
@@ -2668,7 +2651,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
"but is a regular ref"),
ref_update_original_update_refname(update),
update->old_target);
- ret = TRANSACTION_GENERIC_ERROR;
+ ret = REF_TRANSACTION_ERROR_EXPECTED_SYMREF;
goto out;
} else {
ret = check_old_oid(update, &lock->old_oid, err);
@@ -2692,14 +2675,14 @@ static int lock_ref_for_update(struct files_ref_store *refs,
if (update->new_target && !(update->flags & REF_LOG_ONLY)) {
if (create_symref_lock(lock, update->new_target, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
+ ret = REF_TRANSACTION_ERROR_GENERIC;
goto out;
}
if (close_ref_gently(lock)) {
strbuf_addf(err, "couldn't close '%s.lock'",
update->refname);
- ret = TRANSACTION_GENERIC_ERROR;
+ ret = REF_TRANSACTION_ERROR_GENERIC;
goto out;
}
@@ -2717,25 +2700,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,
* The reference already has the desired
* value, so we don't need to write it.
*/
- } else if (write_ref_to_lockfile(
- refs, lock, &update->new_oid,
- update->flags & REF_SKIP_OID_VERIFICATION,
- err)) {
- char *write_err = strbuf_detach(err, NULL);
-
- /*
- * The lock was freed upon failure of
- * write_ref_to_lockfile():
- */
- update->backend_data = NULL;
- strbuf_addf(err,
- "cannot update ref '%s': %s",
- update->refname, write_err);
- free(write_err);
- ret = TRANSACTION_GENERIC_ERROR;
- goto out;
} else {
- update->flags |= REF_NEEDS_COMMIT;
+ ret = write_ref_to_lockfile(
+ refs, lock, &update->new_oid,
+ update->flags & REF_SKIP_OID_VERIFICATION,
+ err);
+ if (ret) {
+ char *write_err = strbuf_detach(err, NULL);
+
+ /*
+ * The lock was freed upon failure of
+ * write_ref_to_lockfile():
+ */
+ update->backend_data = NULL;
+ strbuf_addf(err,
+ "cannot update ref '%s': %s",
+ update->refname, write_err);
+ free(write_err);
+ goto out;
+ } else {
+ update->flags |= REF_NEEDS_COMMIT;
+ }
}
}
if (!(update->flags & REF_NEEDS_COMMIT)) {
@@ -2747,7 +2732,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
if (close_ref_gently(lock)) {
strbuf_addf(err, "couldn't close '%s.lock'",
update->refname);
- ret = TRANSACTION_GENERIC_ERROR;
+ ret = REF_TRANSACTION_ERROR_GENERIC;
goto out;
}
}
@@ -2806,7 +2791,6 @@ static int files_transaction_prepare(struct ref_store *ref_store,
"ref_transaction_prepare");
size_t i;
int ret = 0;
- struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
struct string_list refnames_to_check = STRING_LIST_INIT_NODUP;
char *head_ref = NULL;
int head_type;
@@ -2825,36 +2809,14 @@ static int files_transaction_prepare(struct ref_store *ref_store,
transaction->backend_data = backend_data;
/*
- * Fail if a refname appears more than once in the
- * transaction. (If we end up splitting up any updates using
- * split_symref_update() or split_head_update(), those
- * functions will check that the new updates don't have the
- * same refname as any existing ones.) Also fail if any of the
- * updates use REF_IS_PRUNING without REF_NO_DEREF.
+ * Fail if any of the updates use REF_IS_PRUNING without REF_NO_DEREF.
*/
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
- struct string_list_item *item;
if ((update->flags & REF_IS_PRUNING) &&
!(update->flags & REF_NO_DEREF))
BUG("REF_IS_PRUNING set without REF_NO_DEREF");
-
- if (update->flags & REF_LOG_ONLY)
- continue;
-
- item = string_list_append(&affected_refnames, update->refname);
- /*
- * We store a pointer to update in item->util, but at
- * the moment we never use the value of this field
- * except to check whether it is non-NULL.
- */
- item->util = update;
- }
- string_list_sort(&affected_refnames);
- if (ref_update_reject_duplicates(&affected_refnames, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
- goto cleanup;
}
/*
@@ -2894,11 +2856,18 @@ static int files_transaction_prepare(struct ref_store *ref_store,
for (i = 0; i < transaction->nr; i++) {
struct ref_update *update = transaction->updates[i];
- ret = lock_ref_for_update(refs, update, transaction,
+ ret = lock_ref_for_update(refs, update, i, transaction,
head_ref, &refnames_to_check,
- &affected_refnames, err);
- if (ret)
+ err);
+ if (ret) {
+ if (ref_transaction_maybe_set_rejected(transaction, i, ret)) {
+ strbuf_reset(err);
+ ret = 0;
+
+ continue;
+ }
goto cleanup;
+ }
if (update->flags & REF_DELETING &&
!(update->flags & REF_LOG_ONLY) &&
@@ -2912,7 +2881,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
refs->packed_ref_store,
transaction->flags, err);
if (!packed_transaction) {
- ret = TRANSACTION_GENERIC_ERROR;
+ ret = REF_TRANSACTION_ERROR_GENERIC;
goto cleanup;
}
@@ -2943,14 +2912,15 @@ static int files_transaction_prepare(struct ref_store *ref_store,
* So instead, we accept the race for now.
*/
if (refs_verify_refnames_available(refs->packed_ref_store, &refnames_to_check,
- &affected_refnames, NULL, 0, err)) {
- ret = TRANSACTION_NAME_CONFLICT;
+ &transaction->refnames, NULL, transaction,
+ 0, err)) {
+ ret = REF_TRANSACTION_ERROR_NAME_CONFLICT;
goto cleanup;
}
if (packed_transaction) {
if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
+ ret = REF_TRANSACTION_ERROR_GENERIC;
goto cleanup;
}
backend_data->packed_refs_locked = 1;
@@ -2981,7 +2951,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
*/
backend_data->packed_transaction = NULL;
if (ref_transaction_abort(packed_transaction, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
+ ret = REF_TRANSACTION_ERROR_GENERIC;
goto cleanup;
}
}
@@ -2989,8 +2959,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
cleanup:
free(head_ref);
- string_list_clear(&affected_refnames, 0);
- string_list_clear(&refnames_to_check, 0);
+ string_list_clear(&refnames_to_check, 1);
if (ret)
files_transaction_cleanup(refs, transaction);
@@ -3064,17 +3033,6 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
if (transaction->state != REF_TRANSACTION_PREPARED)
BUG("commit called for transaction that is not prepared");
- /* Fail if a refname appears more than once in the transaction: */
- for (i = 0; i < transaction->nr; i++)
- if (!(transaction->updates[i]->flags & REF_LOG_ONLY))
- string_list_append(&affected_refnames,
- transaction->updates[i]->refname);
- string_list_sort(&affected_refnames);
- if (ref_update_reject_duplicates(&affected_refnames, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
- goto cleanup;
- }
-
/*
* It's really undefined to call this function in an active
* repository or when there are existing references: we are
@@ -3088,13 +3046,13 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
* that we are creating already exists.
*/
if (refs_for_each_rawref(&refs->base, ref_present,
- &affected_refnames))
+ &transaction->refnames))
BUG("initial ref transaction called with existing refs");
packed_transaction = ref_store_transaction_begin(refs->packed_ref_store,
transaction->flags, err);
if (!packed_transaction) {
- ret = TRANSACTION_GENERIC_ERROR;
+ ret = REF_TRANSACTION_ERROR_GENERIC;
goto cleanup;
}
@@ -3117,7 +3075,7 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
if (!loose_transaction) {
loose_transaction = ref_store_transaction_begin(&refs->base, 0, err);
if (!loose_transaction) {
- ret = TRANSACTION_GENERIC_ERROR;
+ ret = REF_TRANSACTION_ERROR_GENERIC;
goto cleanup;
}
}
@@ -3142,19 +3100,20 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
}
if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
+ ret = REF_TRANSACTION_ERROR_GENERIC;
goto cleanup;
}
if (refs_verify_refnames_available(&refs->base, &refnames_to_check,
- &affected_refnames, NULL, 1, err)) {
+ &affected_refnames, NULL, transaction,
+ 1, err)) {
packed_refs_unlock(refs->packed_ref_store);
- ret = TRANSACTION_NAME_CONFLICT;
+ ret = REF_TRANSACTION_ERROR_NAME_CONFLICT;
goto cleanup;
}
if (ref_transaction_commit(packed_transaction, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
+ ret = REF_TRANSACTION_ERROR_GENERIC;
goto cleanup;
}
packed_refs_unlock(refs->packed_ref_store);
@@ -3162,7 +3121,7 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
if (loose_transaction) {
if (ref_transaction_prepare(loose_transaction, err) ||
ref_transaction_commit(loose_transaction, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
+ ret = REF_TRANSACTION_ERROR_GENERIC;
goto cleanup;
}
}
@@ -3208,10 +3167,13 @@ static int files_transaction_finish(struct ref_store *ref_store,
struct ref_update *update = transaction->updates[i];
struct ref_lock *lock = update->backend_data;
+ if (update->rejection_err)
+ continue;
+
if (update->flags & REF_NEEDS_COMMIT ||
update->flags & REF_LOG_ONLY) {
if (parse_and_write_reflog(refs, update, lock, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
+ ret = REF_TRANSACTION_ERROR_GENERIC;
goto cleanup;
}
}
@@ -3230,7 +3192,7 @@ static int files_transaction_finish(struct ref_store *ref_store,
strbuf_addf(err, "couldn't set '%s'", lock->ref_name);
unlock_ref(lock);
update->backend_data = NULL;
- ret = TRANSACTION_GENERIC_ERROR;
+ ret = REF_TRANSACTION_ERROR_GENERIC;
goto cleanup;
}
}
@@ -3286,7 +3248,7 @@ static int files_transaction_finish(struct ref_store *ref_store,
strbuf_reset(&sb);
files_ref_path(refs, &sb, lock->ref_name);
if (unlink_or_msg(sb.buf, err)) {
- ret = TRANSACTION_GENERIC_ERROR;
+ ret = REF_TRANSACTION_ERROR_GENERIC;
goto cleanup;
}
}
diff --git a/refs/packed-backend.c b/refs/packed-backend.c
index b4289a7d9c..3ad1ed0787 100644
--- a/refs/packed-backend.c
+++ b/refs/packed-backend.c
@@ -980,9 +980,9 @@ static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator)
continue;
while (prefix && *prefix) {
- if (*refname < *prefix)
+ if ((unsigned char)*refname < (unsigned char)*prefix)
BUG("packed-refs backend yielded reference preceding its prefix");
- else if (*refname > *prefix)
+ else if ((unsigned char)*refname > (unsigned char)*prefix)
return ITER_DONE;
prefix++;
refname++;
@@ -1351,10 +1351,12 @@ static int packed_ref_store_remove_on_disk(struct ref_store *ref_store,
* The packfile must be locked before calling this function and will
* remain locked when it is done.
*/
-static int write_with_updates(struct packed_ref_store *refs,
- struct string_list *updates,
- struct strbuf *err)
+static enum ref_transaction_error write_with_updates(struct packed_ref_store *refs,
+ struct ref_transaction *transaction,
+ struct strbuf *err)
{
+ enum ref_transaction_error ret = REF_TRANSACTION_ERROR_GENERIC;
+ struct string_list *updates = &transaction->refnames;
struct ref_iterator *iter = NULL;
size_t i;
int ok;
@@ -1378,7 +1380,7 @@ static int write_with_updates(struct packed_ref_store *refs,
strbuf_addf(err, "unable to create file %s: %s",
sb.buf, strerror(errno));
strbuf_release(&sb);
- return -1;
+ return REF_TRANSACTION_ERROR_GENERIC;
}
strbuf_release(&sb);
@@ -1434,6 +1436,14 @@ static int write_with_updates(struct packed_ref_store *refs,
strbuf_addf(err, "cannot update ref '%s': "
"reference already exists",
update->refname);
+ ret = REF_TRANSACTION_ERROR_CREATE_EXISTS;
+
+ if (ref_transaction_maybe_set_rejected(transaction, i, ret)) {
+ strbuf_reset(err);
+ ret = 0;
+ continue;
+ }
+
goto error;
} else if (!oideq(&update->old_oid, iter->oid)) {
strbuf_addf(err, "cannot update ref '%s': "
@@ -1441,6 +1451,14 @@ static int write_with_updates(struct packed_ref_store *refs,
update->refname,
oid_to_hex(iter->oid),
oid_to_hex(&update->old_oid));
+ ret = REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE;
+
+ if (ref_transaction_maybe_set_rejected(transaction, i, ret)) {
+ strbuf_reset(err);
+ ret = 0;
+ continue;
+ }
+
goto error;
}
}
@@ -1477,6 +1495,14 @@ static int write_with_updates(struct packed_ref_store *refs,
"reference is missing but expected %s",
update->refname,
oid_to_hex(&update->old_oid));
+ ret = REF_TRANSACTION_ERROR_NONEXISTENT_REF;
+
+ if (ref_transaction_maybe_set_rejected(transaction, i, ret)) {
+ strbuf_reset(err);
+ ret = 0;
+ continue;
+ }
+
goto error;
}
}
@@ -1534,7 +1560,7 @@ static int write_with_updates(struct packed_ref_store *refs,
strerror(errno));
strbuf_release(&sb);
delete_tempfile(&refs->tempfile);
- return -1;
+ return REF_TRANSACTION_ERROR_GENERIC;
}
return 0;
@@ -1542,11 +1568,12 @@ static int write_with_updates(struct packed_ref_store *refs,
write_error:
strbuf_addf(err, "error writing to %s: %s",
get_tempfile_path(refs->tempfile), strerror(errno));
+ ret = REF_TRANSACTION_ERROR_GENERIC;
error:
ref_iterator_free(iter);
delete_tempfile(&refs->tempfile);
- return -1;
+ return ret;
}
int is_packed_transaction_needed(struct ref_store *ref_store,
@@ -1647,8 +1674,6 @@ int is_packed_transaction_needed(struct ref_store *ref_store,
struct packed_transaction_backend_data {
/* True iff the transaction owns the packed-refs lock. */
int own_lock;
-
- struct string_list updates;
};
static void packed_transaction_cleanup(struct packed_ref_store *refs,
@@ -1657,8 +1682,6 @@ static void packed_transaction_cleanup(struct packed_ref_store *refs,
struct packed_transaction_backend_data *data = transaction->backend_data;
if (data) {
- string_list_clear(&data->updates, 0);
-
if (is_tempfile_active(refs->tempfile))
delete_tempfile(&refs->tempfile);
@@ -1683,8 +1706,7 @@ static int packed_transaction_prepare(struct ref_store *ref_store,
REF_STORE_READ | REF_STORE_WRITE | REF_STORE_ODB,
"ref_transaction_prepare");
struct packed_transaction_backend_data *data;
- size_t i;
- int ret = TRANSACTION_GENERIC_ERROR;
+ enum ref_transaction_error ret = REF_TRANSACTION_ERROR_GENERIC;
/*
* Note that we *don't* skip transactions with zero updates,
@@ -1696,34 +1718,17 @@ static int packed_transaction_prepare(struct ref_store *ref_store,
*/
CALLOC_ARRAY(data, 1);
- string_list_init_nodup(&data->updates);
transaction->backend_data = data;
- /*
- * Stick the updates in a string list by refname so that we
- * can sort them:
- */
- for (i = 0; i < transaction->nr; i++) {
- struct ref_update *update = transaction->updates[i];
- struct string_list_item *item =
- string_list_append(&data->updates, update->refname);
-
- /* Store a pointer to update in item->util: */
- item->util = update;
- }
- string_list_sort(&data->updates);
-
- if (ref_update_reject_duplicates(&data->updates, err))
- goto failure;
-
if (!is_lock_file_locked(&refs->lock)) {
if (packed_refs_lock(ref_store, 0, err))
goto failure;
data->own_lock = 1;
}
- if (write_with_updates(refs, &data->updates, err))
+ ret = write_with_updates(refs, transaction, err);
+ if (ret)
goto failure;
transaction->state = REF_TRANSACTION_PREPARED;
@@ -1755,7 +1760,7 @@ static int packed_transaction_finish(struct ref_store *ref_store,
ref_store,
REF_STORE_READ | REF_STORE_WRITE | REF_STORE_ODB,
"ref_transaction_finish");
- int ret = TRANSACTION_GENERIC_ERROR;
+ int ret = REF_TRANSACTION_ERROR_GENERIC;
char *packed_refs_path;
clear_snapshot(refs);
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index e5862757a7..f868870851 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -3,6 +3,7 @@
#include "refs.h"
#include "iterator.h"
+#include "string-list.h"
struct fsck_options;
struct ref_transaction;
@@ -123,6 +124,12 @@ struct ref_update {
uint64_t index;
/*
+ * Used in batched reference updates to mark if a given update
+ * was rejected.
+ */
+ enum ref_transaction_error rejection_err;
+
+ /*
* If this ref_update was split off of a symref update via
* split_symref_update(), then this member points at that
* update. This is used for two purposes:
@@ -142,12 +149,11 @@ int refs_read_raw_ref(struct ref_store *ref_store, const char *refname,
unsigned int *type, int *failure_errno);
/*
- * 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.
+ * Mark a given update as rejected with a given reason.
*/
-int ref_update_reject_duplicates(struct string_list *refnames,
- struct strbuf *err);
+int ref_transaction_maybe_set_rejected(struct ref_transaction *transaction,
+ size_t update_idx,
+ enum ref_transaction_error err);
/*
* Add a ref_update with the specified properties to transaction, and
@@ -191,6 +197,18 @@ enum ref_transaction_state {
};
/*
+ * Data structure to hold indices of updates which were rejected, for batched
+ * reference updates. While the updates themselves hold the rejection error,
+ * this structure allows a transaction to iterate only over the rejected
+ * updates.
+ */
+struct ref_transaction_rejections {
+ size_t *update_indices;
+ size_t alloc;
+ size_t nr;
+};
+
+/*
* Data structure for holding a reference transaction, which can
* consist of checks and updates to multiple references, carried out
* as atomically as possible. This structure is opaque to callers.
@@ -198,9 +216,11 @@ enum ref_transaction_state {
struct ref_transaction {
struct ref_store *ref_store;
struct ref_update **updates;
+ struct string_list refnames;
size_t alloc;
size_t nr;
enum ref_transaction_state state;
+ struct ref_transaction_rejections *rejections;
void *backend_data;
unsigned int flags;
uint64_t max_index;
@@ -776,8 +796,9 @@ int ref_update_has_null_new_value(struct ref_update *update);
* If everything is OK, return 0; otherwise, write an error message to
* err and return -1.
*/
-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);
/*
* Check if the ref must exist, this means that the old_oid or
@@ -785,4 +806,20 @@ int ref_update_check_old_target(const char *referent, struct ref_update *update,
*/
int ref_update_expects_existing_old_ref(struct ref_update *update);
+/*
+ * Same as `refs_verify_refname_available()`, but checking for a list of
+ * refnames instead of only a single item. This is more efficient in the case
+ * where one needs to check multiple refnames.
+ *
+ * If using batched updates, then individual updates are marked rejected,
+ * reference backends are then in charge of not committing those updates.
+ */
+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);
+
#endif /* REFS_REFS_INTERNAL_H */
diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c
index ae434cd248..4c3817f4ec 100644
--- a/refs/reftable-backend.c
+++ b/refs/reftable-backend.c
@@ -1069,6 +1069,244 @@ static int queue_transaction_update(struct reftable_ref_store *refs,
return 0;
}
+static enum ref_transaction_error prepare_single_update(struct reftable_ref_store *refs,
+ struct reftable_transaction_data *tx_data,
+ struct ref_transaction *transaction,
+ struct reftable_backend *be,
+ struct ref_update *u,
+ size_t update_idx,
+ struct string_list *refnames_to_check,
+ unsigned int head_type,
+ struct strbuf *head_referent,
+ struct strbuf *referent,
+ struct strbuf *err)
+{
+ enum ref_transaction_error ret = 0;
+ struct object_id current_oid = {0};
+ const char *rewritten_ref;
+
+ /*
+ * There is no need to reload the respective backends here as
+ * we have already reloaded them when preparing the transaction
+ * update. And given that the stacks have been locked there
+ * shouldn't have been any concurrent modifications of the
+ * stack.
+ */
+ ret = backend_for(&be, refs, u->refname, &rewritten_ref, 0);
+ if (ret)
+ return REF_TRANSACTION_ERROR_GENERIC;
+
+ /* Verify that the new object ID is valid. */
+ if ((u->flags & REF_HAVE_NEW) && !is_null_oid(&u->new_oid) &&
+ !(u->flags & REF_SKIP_OID_VERIFICATION) &&
+ !(u->flags & REF_LOG_ONLY)) {
+ struct object *o = parse_object(refs->base.repo, &u->new_oid);
+ if (!o) {
+ strbuf_addf(err,
+ _("trying to write ref '%s' with nonexistent object %s"),
+ u->refname, oid_to_hex(&u->new_oid));
+ return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
+ }
+
+ if (o->type != OBJ_COMMIT && is_branch(u->refname)) {
+ strbuf_addf(err, _("trying to write non-commit object %s to branch '%s'"),
+ oid_to_hex(&u->new_oid), u->refname);
+ return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
+ }
+ }
+
+ /*
+ * When we update the reference that HEAD points to we enqueue
+ * a second log-only update for HEAD so that its reflog is
+ * updated accordingly.
+ */
+ if (head_type == REF_ISSYMREF &&
+ !(u->flags & REF_LOG_ONLY) &&
+ !(u->flags & REF_UPDATE_VIA_HEAD) &&
+ !strcmp(rewritten_ref, head_referent->buf)) {
+ /*
+ * 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(&transaction->refnames, "HEAD")) {
+ /* An entry already existed */
+ strbuf_addf(err,
+ _("multiple updates for 'HEAD' (including one "
+ "via its referent '%s') are not allowed"),
+ u->refname);
+ return REF_TRANSACTION_ERROR_NAME_CONFLICT;
+ }
+
+ 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,
+ &current_oid, referent, &u->type);
+ if (ret < 0)
+ return REF_TRANSACTION_ERROR_GENERIC;
+ if (ret > 0 && !ref_update_expects_existing_old_ref(u)) {
+ struct string_list_item *item;
+ /*
+ * The reference does not exist, and we either have no
+ * old object ID or expect the reference to not exist.
+ * We can thus skip below safety checks as well as the
+ * symref splitting. But we do want to verify that
+ * there is no conflicting reference here so that we
+ * can output a proper error message instead of failing
+ * at a later point.
+ */
+ item = string_list_append(refnames_to_check, u->refname);
+ item->util = xmalloc(sizeof(update_idx));
+ memcpy(item->util, &update_idx, sizeof(update_idx));
+
+ /*
+ * There is no need to write the reference deletion
+ * when the reference in question doesn't exist.
+ */
+ if ((u->flags & REF_HAVE_NEW) && !ref_update_has_null_new_value(u)) {
+ ret = queue_transaction_update(refs, tx_data, u,
+ &current_oid, err);
+ if (ret)
+ return REF_TRANSACTION_ERROR_GENERIC;
+ }
+
+ return 0;
+ }
+ if (ret > 0) {
+ /* The reference does not exist, but we expected it to. */
+ strbuf_addf(err, _("cannot lock ref '%s': "
+
+
+ "unable to resolve reference '%s'"),
+ ref_update_original_update_refname(u), u->refname);
+ return REF_TRANSACTION_ERROR_NONEXISTENT_REF;
+ }
+
+ if (u->type & REF_ISSYMREF) {
+ /*
+ * The reftable stack is locked at this point already,
+ * so it is safe to call `refs_resolve_ref_unsafe()`
+ * here without causing races.
+ */
+ const char *resolved = refs_resolve_ref_unsafe(&refs->base, u->refname, 0,
+ &current_oid, NULL);
+
+ if (u->flags & REF_NO_DEREF) {
+ if (u->flags & REF_HAVE_OLD && !resolved) {
+ strbuf_addf(err, _("cannot lock ref '%s': "
+ "error reading reference"), u->refname);
+ return REF_TRANSACTION_ERROR_GENERIC;
+ }
+ } else {
+ struct ref_update *new_update;
+ int new_flags;
+
+ new_flags = u->flags;
+ 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);
+ return REF_TRANSACTION_ERROR_NAME_CONFLICT;
+ }
+
+ /*
+ * If we are updating a symref (eg. HEAD), we should also
+ * update the branch that the symref points to.
+ *
+ * This is generic functionality, and would be better
+ * done in refs.c, but the current implementation is
+ * intertwined with the locking in files-backend.c.
+ */
+ new_update = ref_transaction_add_update(
+ transaction, referent->buf, new_flags,
+ u->new_target ? NULL : &u->new_oid,
+ u->old_target ? NULL : &u->old_oid,
+ u->new_target, u->old_target,
+ u->committer_info, u->msg);
+
+ new_update->parent_update = u;
+
+ /*
+ * Change the symbolic ref update to log only. Also, it
+ * doesn't need to check its old OID value, as that will be
+ * done when new_update is processed.
+ */
+ u->flags |= REF_LOG_ONLY | REF_NO_DEREF;
+ u->flags &= ~REF_HAVE_OLD;
+ }
+ }
+
+ /*
+ * Verify that the old object matches our expectations. Note
+ * that the error messages here do not make a lot of sense in
+ * the context of the reftable backend as we never lock
+ * individual refs. But the error messages match what the files
+ * backend returns, which keeps our tests happy.
+ */
+ if (u->old_target) {
+ if (!(u->type & REF_ISSYMREF)) {
+ strbuf_addf(err, _("cannot lock ref '%s': "
+ "expected symref with target '%s': "
+ "but is a regular ref"),
+ ref_update_original_update_refname(u),
+ u->old_target);
+ return REF_TRANSACTION_ERROR_EXPECTED_SYMREF;
+ }
+
+ ret = ref_update_check_old_target(referent->buf, u, err);
+ if (ret)
+ return ret;
+ } else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {
+ if (is_null_oid(&u->old_oid)) {
+ strbuf_addf(err, _("cannot lock ref '%s': "
+ "reference already exists"),
+ ref_update_original_update_refname(u));
+ return REF_TRANSACTION_ERROR_CREATE_EXISTS;
+ } else if (is_null_oid(&current_oid)) {
+ strbuf_addf(err, _("cannot lock ref '%s': "
+ "reference is missing but expected %s"),
+ ref_update_original_update_refname(u),
+ oid_to_hex(&u->old_oid));
+ return REF_TRANSACTION_ERROR_NONEXISTENT_REF;
+ } else {
+ strbuf_addf(err, _("cannot lock ref '%s': "
+ "is at %s but expected %s"),
+ ref_update_original_update_refname(u),
+ oid_to_hex(&current_oid),
+ oid_to_hex(&u->old_oid));
+ return REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE;
+ }
+ }
+
+ /*
+ * If all of the following conditions are true:
+ *
+ * - We're not about to write a symref.
+ * - We're not about to write a log-only entry.
+ * - Old and new object ID are different.
+ *
+ * Then we're essentially doing a no-op update that can be
+ * skipped. This is not only for the sake of efficiency, but
+ * also skips writing unneeded reflog entries.
+ */
+ if ((u->type & REF_ISSYMREF) ||
+ (u->flags & REF_LOG_ONLY) ||
+ (u->flags & REF_HAVE_NEW && !oideq(&current_oid, &u->new_oid)))
+ if (queue_transaction_update(refs, tx_data, u, &current_oid, err))
+ return REF_TRANSACTION_ERROR_GENERIC;
+
+ return 0;
+}
+
static int reftable_be_transaction_prepare(struct ref_store *ref_store,
struct ref_transaction *transaction,
struct strbuf *err)
@@ -1076,7 +1314,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 +1338,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);
}
/*
@@ -1117,17 +1350,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
}
/*
- * 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
* stacks which are _not_ part of the "HEAD" stack. In that case we
@@ -1149,241 +1371,24 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
ret = 0;
for (i = 0; i < transaction->nr; i++) {
- struct ref_update *u = transaction->updates[i];
- struct object_id current_oid = {0};
- const char *rewritten_ref;
-
- /*
- * There is no need to reload the respective backends here as
- * we have already reloaded them when preparing the transaction
- * update. And given that the stacks have been locked there
- * shouldn't have been any concurrent modifications of the
- * stack.
- */
- ret = backend_for(&be, refs, u->refname, &rewritten_ref, 0);
- if (ret)
- goto done;
-
- /* Verify that the new object ID is valid. */
- if ((u->flags & REF_HAVE_NEW) && !is_null_oid(&u->new_oid) &&
- !(u->flags & REF_SKIP_OID_VERIFICATION) &&
- !(u->flags & REF_LOG_ONLY)) {
- struct object *o = parse_object(refs->base.repo, &u->new_oid);
- if (!o) {
- strbuf_addf(err,
- _("trying to write ref '%s' with nonexistent object %s"),
- u->refname, oid_to_hex(&u->new_oid));
- ret = -1;
- goto done;
- }
-
- if (o->type != OBJ_COMMIT && is_branch(u->refname)) {
- strbuf_addf(err, _("trying to write non-commit object %s to branch '%s'"),
- oid_to_hex(&u->new_oid), u->refname);
- ret = -1;
- goto done;
- }
- }
-
- /*
- * When we update the reference that HEAD points to we enqueue
- * a second log-only update for HEAD so that its reflog is
- * updated accordingly.
- */
- if (head_type == REF_ISSYMREF &&
- !(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")) {
- /* An entry already existed */
- strbuf_addf(err,
- _("multiple updates for 'HEAD' (including one "
- "via its referent '%s') are not allowed"),
- u->refname);
- ret = TRANSACTION_NAME_CONFLICT;
- 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);
- }
-
- ret = reftable_backend_read_ref(be, rewritten_ref,
- &current_oid, &referent, &u->type);
- if (ret < 0)
- goto done;
- if (ret > 0 && !ref_update_expects_existing_old_ref(u)) {
- /*
- * The reference does not exist, and we either have no
- * old object ID or expect the reference to not exist.
- * We can thus skip below safety checks as well as the
- * symref splitting. But we do want to verify that
- * there is no conflicting reference here so that we
- * can output a proper error message instead of failing
- * at a later point.
- */
- string_list_append(&refnames_to_check, u->refname);
-
- /*
- * There is no need to write the reference deletion
- * when the reference in question doesn't exist.
- */
- if ((u->flags & REF_HAVE_NEW) && !ref_update_has_null_new_value(u)) {
- ret = queue_transaction_update(refs, tx_data, u,
- &current_oid, err);
- if (ret)
- goto done;
- }
-
- continue;
- }
- if (ret > 0) {
- /* The reference does not exist, but we expected it to. */
- strbuf_addf(err, _("cannot lock ref '%s': "
- "unable to resolve reference '%s'"),
- ref_update_original_update_refname(u), u->refname);
- ret = -1;
- goto done;
- }
-
- if (u->type & REF_ISSYMREF) {
- /*
- * The reftable stack is locked at this point already,
- * so it is safe to call `refs_resolve_ref_unsafe()`
- * here without causing races.
- */
- const char *resolved = refs_resolve_ref_unsafe(&refs->base, u->refname, 0,
- &current_oid, NULL);
-
- if (u->flags & REF_NO_DEREF) {
- if (u->flags & REF_HAVE_OLD && !resolved) {
- strbuf_addf(err, _("cannot lock ref '%s': "
- "error reading reference"), u->refname);
- ret = -1;
- goto done;
- }
- } else {
- struct ref_update *new_update;
- int new_flags;
-
- new_flags = u->flags;
- if (!strcmp(rewritten_ref, "HEAD"))
- new_flags |= REF_UPDATE_VIA_HEAD;
-
- /*
- * If we are updating a symref (eg. HEAD), we should also
- * update the branch that the symref points to.
- *
- * This is generic functionality, and would be better
- * done in refs.c, but the current implementation is
- * intertwined with the locking in files-backend.c.
- */
- new_update = ref_transaction_add_update(
- transaction, referent.buf, new_flags,
- u->new_target ? NULL : &u->new_oid,
- u->old_target ? NULL : &u->old_oid,
- u->new_target, u->old_target,
- u->committer_info, u->msg);
-
- new_update->parent_update = u;
-
- /*
- * Change the symbolic ref update to log only. Also, it
- * doesn't need to check its old OID value, as that will be
- * done when new_update is processed.
- */
- 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);
- }
- }
-
- /*
- * Verify that the old object matches our expectations. Note
- * that the error messages here do not make a lot of sense in
- * the context of the reftable backend as we never lock
- * individual refs. But the error messages match what the files
- * backend returns, which keeps our tests happy.
- */
- if (u->old_target) {
- if (!(u->type & REF_ISSYMREF)) {
- strbuf_addf(err, _("cannot lock ref '%s': "
- "expected symref with target '%s': "
- "but is a regular ref"),
- ref_update_original_update_refname(u),
- u->old_target);
- ret = -1;
- goto done;
- }
+ ret = prepare_single_update(refs, tx_data, transaction, be,
+ transaction->updates[i], i,
+ &refnames_to_check, head_type,
+ &head_referent, &referent, err);
+ if (ret) {
+ if (ref_transaction_maybe_set_rejected(transaction, i, ret)) {
+ strbuf_reset(err);
+ ret = 0;
- if (ref_update_check_old_target(referent.buf, u, err)) {
- ret = -1;
- goto done;
- }
- } else if ((u->flags & REF_HAVE_OLD) && !oideq(&current_oid, &u->old_oid)) {
- ret = TRANSACTION_NAME_CONFLICT;
- if (is_null_oid(&u->old_oid)) {
- strbuf_addf(err, _("cannot lock ref '%s': "
- "reference already exists"),
- ref_update_original_update_refname(u));
- ret = TRANSACTION_CREATE_EXISTS;
+ continue;
}
- else if (is_null_oid(&current_oid))
- strbuf_addf(err, _("cannot lock ref '%s': "
- "reference is missing but expected %s"),
- ref_update_original_update_refname(u),
- oid_to_hex(&u->old_oid));
- else
- strbuf_addf(err, _("cannot lock ref '%s': "
- "is at %s but expected %s"),
- ref_update_original_update_refname(u),
- oid_to_hex(&current_oid),
- oid_to_hex(&u->old_oid));
goto done;
}
-
- /*
- * If all of the following conditions are true:
- *
- * - We're not about to write a symref.
- * - We're not about to write a log-only entry.
- * - Old and new object ID are different.
- *
- * Then we're essentially doing a no-op update that can be
- * skipped. This is not only for the sake of efficiency, but
- * also skips writing unneeded reflog entries.
- */
- if ((u->type & REF_ISSYMREF) ||
- (u->flags & REF_LOG_ONLY) ||
- (u->flags & REF_HAVE_NEW && !oideq(&current_oid, &u->new_oid))) {
- ret = queue_transaction_update(refs, tx_data, u,
- &current_oid, err);
- if (ret)
- goto done;
- }
}
- 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,
transaction->flags & REF_TRANSACTION_FLAG_INITIAL,
err);
if (ret < 0)
@@ -1393,7 +1398,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
transaction->state = REF_TRANSACTION_PREPARED;
done:
- assert(ret != REFTABLE_API_ERROR);
if (ret < 0) {
free_transaction_data(tx_data);
transaction->state = REF_TRANSACTION_CLOSED;
@@ -1401,10 +1405,9 @@ 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);
+ string_list_clear(&refnames_to_check, 1);
return ret;
}
@@ -1463,6 +1466,9 @@ static int write_transaction_table(struct reftable_writer *writer, void *cb_data
struct reftable_transaction_update *tx_update = &arg->updates[i];
struct ref_update *u = tx_update->update;
+ if (u->rejection_err)
+ continue;
+
/*
* Write a reflog entry when updating a ref to point to
* something new in either of the following cases: