ref_transaction_prepare(): new optional step for reference updates

In the future, compound reference stores will sometimes need to modify
references in two different reference stores at the same time, meaning
that a single logical reference transaction might have to be
implemented as two internal sub-transactions. They won't want to call
`ref_transaction_commit()` for the two sub-transactions one after the
other, because that wouldn't be atomic (the first commit could succeed
and the second one fail). Instead, they will want to prepare both
sub-transactions (i.e., obtain any necessary locks and do any
pre-checks), and only if both prepare steps succeed, then commit both
sub-transactions.

Start preparing for that day by adding a new, optional
`ref_transaction_prepare()` step to the reference transaction
sequence, which obtains the locks and does any prechecks, reporting
any errors that occur. Also add a `ref_transaction_abort()` function
that can be used to abort a sub-transaction even if it has already
been prepared.

That is on the side of the public-facing API. On the side of the
`ref_store` VTABLE, get rid of `transaction_commit` and instead add
methods `transaction_prepare`, `transaction_finish`, and
`transaction_abort`. A `ref_transaction_commit()` now basically calls
methods `transaction_prepare` then `transaction_finish`.

Signed-off-by: Michael Haggerty <mhagger@alum.mit.edu>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Michael Haggerty
2017-05-22 16:17:44 +02:00
committed by Junio C Hamano
parent 8d4240d3c8
commit 30173b8851
4 changed files with 250 additions and 50 deletions

74
refs.c
View File

@ -853,6 +853,19 @@ void ref_transaction_free(struct ref_transaction *transaction)
if (!transaction)
return;
switch (transaction->state) {
case REF_TRANSACTION_OPEN:
case REF_TRANSACTION_CLOSED:
/* OK */
break;
case REF_TRANSACTION_PREPARED:
die("BUG: free called on a prepared reference transaction");
break;
default:
die("BUG: unexpected reference transaction state");
break;
}
for (i = 0; i < transaction->nr; i++) {
free(transaction->updates[i]->msg);
free(transaction->updates[i]);
@ -1689,8 +1702,8 @@ int create_symref(const char *ref_target, const char *refs_heads_master,
refs_heads_master, logmsg);
}
int ref_transaction_commit(struct ref_transaction *transaction,
struct strbuf *err)
int ref_transaction_prepare(struct ref_transaction *transaction,
struct strbuf *err)
{
struct ref_store *refs = transaction->ref_store;
@ -1698,6 +1711,9 @@ int ref_transaction_commit(struct ref_transaction *transaction,
case REF_TRANSACTION_OPEN:
/* Good. */
break;
case REF_TRANSACTION_PREPARED:
die("BUG: prepare called twice on reference transaction");
break;
case REF_TRANSACTION_CLOSED:
die("BUG: prepare called on a closed reference transaction");
break;
@ -1712,7 +1728,59 @@ int ref_transaction_commit(struct ref_transaction *transaction,
return -1;
}
return refs->be->transaction_commit(refs, transaction, err);
return refs->be->transaction_prepare(refs, transaction, err);
}
int ref_transaction_abort(struct ref_transaction *transaction,
struct strbuf *err)
{
struct ref_store *refs = transaction->ref_store;
int ret = 0;
switch (transaction->state) {
case REF_TRANSACTION_OPEN:
/* No need to abort explicitly. */
break;
case REF_TRANSACTION_PREPARED:
ret = refs->be->transaction_abort(refs, transaction, err);
break;
case REF_TRANSACTION_CLOSED:
die("BUG: abort called on a closed reference transaction");
break;
default:
die("BUG: unexpected reference transaction state");
break;
}
ref_transaction_free(transaction);
return ret;
}
int ref_transaction_commit(struct ref_transaction *transaction,
struct strbuf *err)
{
struct ref_store *refs = transaction->ref_store;
int ret;
switch (transaction->state) {
case REF_TRANSACTION_OPEN:
/* Need to prepare first. */
ret = ref_transaction_prepare(transaction, err);
if (ret)
return ret;
break;
case REF_TRANSACTION_PREPARED:
/* Fall through to finish. */
break;
case REF_TRANSACTION_CLOSED:
die("BUG: commit called on a closed reference transaction");
break;
default:
die("BUG: unexpected reference transaction state");
break;
}
return refs->be->transaction_finish(refs, transaction, err);
}
int refs_verify_refname_available(struct ref_store *refs,