return 0;
}
+static int 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,
+ struct string_list *refnames_to_check,
+ unsigned int head_type,
+ struct strbuf *head_referent,
+ struct strbuf *referent,
+ struct strbuf *err)
+{
+ struct object_id current_oid = {0};
+ const char *rewritten_ref;
+ int ret = 0;
+
+ /*
+ * 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 ret;
+
+ /* 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 -1;
+ }
+
+ 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 -1;
+ }
+ }
+
+ /*
+ * 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 TRANSACTION_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,
+ ¤t_oid, referent, &u->type);
+ if (ret < 0)
+ return ret;
+ 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,
+ ¤t_oid, err);
+ if (ret)
+ return ret;
+ }
+
+ 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 -1;
+ }
+
+ 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,
+ ¤t_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 -1;
+ }
+ } 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 TRANSACTION_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 -1;
+ }
+
+ if (ref_update_check_old_target(referent->buf, u, err)) {
+ return -1;
+ }
+ } else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_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 TRANSACTION_CREATE_EXISTS;
+ }
+ else if (is_null_oid(¤t_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(¤t_oid),
+ oid_to_hex(&u->old_oid));
+ return TRANSACTION_NAME_CONFLICT;
+ }
+
+ /*
+ * 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(¤t_oid, &u->new_oid)))
+ return queue_transaction_update(refs, tx_data, u,
+ ¤t_oid, err);
+
+ return 0;
+}
+
static int reftable_be_transaction_prepare(struct ref_store *ref_store,
struct ref_transaction *transaction,
struct strbuf *err)
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);
+ ret = prepare_single_update(refs, tx_data, transaction, be,
+ transaction->updates[i],
+ &refnames_to_check, head_type,
+ &head_referent, &referent, err);
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)) {
- /*
- * 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);
- ret = TRANSACTION_NAME_CONFLICT;
- goto done;
- }
-
- 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,
- ¤t_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,
- ¤t_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,
- ¤t_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 (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.
- *
- * 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);
- ret = -1;
- goto done;
- }
-
- if (ref_update_check_old_target(referent.buf, u, err)) {
- ret = -1;
- goto done;
- }
- } else if ((u->flags & REF_HAVE_OLD) && !oideq(¤t_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;
- }
- else if (is_null_oid(¤t_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(¤t_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(¤t_oid, &u->new_oid))) {
- ret = queue_transaction_update(refs, tx_data, u,
- ¤t_oid, err);
- if (ret)
- goto done;
- }
}
ret = refs_verify_refnames_available(ref_store, &refnames_to_check,