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()maint
commit
47478802da
|
@ -7,8 +7,10 @@ git-update-ref - Update the object name stored in a ref safely
|
||||||
|
|
||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
--------
|
--------
|
||||||
[verse]
|
[synopsis]
|
||||||
'git update-ref' [-m <reason>] [--no-deref] (-d <ref> [<old-oid>] | [--create-reflog] <ref> <new-oid> [<old-oid>] | --stdin [-z])
|
git update-ref [-m <reason>] [--no-deref] -d <ref> [<old-oid>]
|
||||||
|
git update-ref [-m <reason>] [--no-deref] [--create-reflog] <ref> <new-oid> [<old-oid>]
|
||||||
|
git update-ref [-m <reason>] [--no-deref] --stdin [-z] [--batch-updates]
|
||||||
|
|
||||||
DESCRIPTION
|
DESCRIPTION
|
||||||
-----------
|
-----------
|
||||||
|
@ -57,6 +59,14 @@ performs all modifications together. Specify commands of the form:
|
||||||
With `--create-reflog`, update-ref will create a reflog for each ref
|
With `--create-reflog`, update-ref will create a reflog for each ref
|
||||||
even if one would not ordinarily be created.
|
even if one would not ordinarily be created.
|
||||||
|
|
||||||
|
With `--batch-updates`, update-ref executes the updates in a batch but allows
|
||||||
|
individual updates to fail due to invalid or incorrect user input, applying only
|
||||||
|
the successful updates. However, system-related errors—such as I/O failures or
|
||||||
|
memory issues—will result in a full failure of all batched updates. Any failed
|
||||||
|
updates will be reported in the following format:
|
||||||
|
|
||||||
|
rejected SP (<old-oid> | <old-target>) SP (<new-oid> | <new-target>) SP <rejection-reason> LF
|
||||||
|
|
||||||
Quote fields containing whitespace as if they were strings in C source
|
Quote fields containing whitespace as if they were strings in C source
|
||||||
code; i.e., surrounded by double-quotes and with backslash escapes.
|
code; i.e., surrounded by double-quotes and with backslash escapes.
|
||||||
Use 40 "0" characters or the empty string to specify a zero value. To
|
Use 40 "0" characters or the empty string to specify a zero value. To
|
||||||
|
|
|
@ -687,7 +687,7 @@ static int s_update_ref(const char *action,
|
||||||
switch (ref_transaction_commit(our_transaction, &err)) {
|
switch (ref_transaction_commit(our_transaction, &err)) {
|
||||||
case 0:
|
case 0:
|
||||||
break;
|
break;
|
||||||
case TRANSACTION_NAME_CONFLICT:
|
case REF_TRANSACTION_ERROR_NAME_CONFLICT:
|
||||||
ret = STORE_REF_ERROR_DF_CONFLICT;
|
ret = STORE_REF_ERROR_DF_CONFLICT;
|
||||||
goto out;
|
goto out;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "gettext.h"
|
#include "gettext.h"
|
||||||
#include "hash.h"
|
#include "hash.h"
|
||||||
|
#include "hex.h"
|
||||||
#include "refs.h"
|
#include "refs.h"
|
||||||
#include "object-name.h"
|
#include "object-name.h"
|
||||||
#include "parse-options.h"
|
#include "parse-options.h"
|
||||||
|
@ -13,7 +14,7 @@
|
||||||
static const char * const git_update_ref_usage[] = {
|
static const char * const git_update_ref_usage[] = {
|
||||||
N_("git update-ref [<options>] -d <refname> [<old-oid>]"),
|
N_("git update-ref [<options>] -d <refname> [<old-oid>]"),
|
||||||
N_("git update-ref [<options>] <refname> <new-oid> [<old-oid>]"),
|
N_("git update-ref [<options>] <refname> <new-oid> [<old-oid>]"),
|
||||||
N_("git update-ref [<options>] --stdin [-z]"),
|
N_("git update-ref [<options>] --stdin [-z] [--batch-updates]"),
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -565,6 +566,49 @@ static void parse_cmd_abort(struct ref_transaction *transaction,
|
||||||
report_ok("abort");
|
report_ok("abort");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void print_rejected_refs(const char *refname,
|
||||||
|
const struct object_id *old_oid,
|
||||||
|
const struct object_id *new_oid,
|
||||||
|
const char *old_target,
|
||||||
|
const char *new_target,
|
||||||
|
enum ref_transaction_error err,
|
||||||
|
void *cb_data UNUSED)
|
||||||
|
{
|
||||||
|
struct strbuf sb = STRBUF_INIT;
|
||||||
|
const char *reason = "";
|
||||||
|
|
||||||
|
switch (err) {
|
||||||
|
case REF_TRANSACTION_ERROR_NAME_CONFLICT:
|
||||||
|
reason = "refname conflict";
|
||||||
|
break;
|
||||||
|
case REF_TRANSACTION_ERROR_CREATE_EXISTS:
|
||||||
|
reason = "reference already exists";
|
||||||
|
break;
|
||||||
|
case REF_TRANSACTION_ERROR_NONEXISTENT_REF:
|
||||||
|
reason = "reference does not exist";
|
||||||
|
break;
|
||||||
|
case REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE:
|
||||||
|
reason = "incorrect old value provided";
|
||||||
|
break;
|
||||||
|
case REF_TRANSACTION_ERROR_INVALID_NEW_VALUE:
|
||||||
|
reason = "invalid new value provided";
|
||||||
|
break;
|
||||||
|
case REF_TRANSACTION_ERROR_EXPECTED_SYMREF:
|
||||||
|
reason = "expected symref but found regular ref";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
reason = "unkown failure";
|
||||||
|
}
|
||||||
|
|
||||||
|
strbuf_addf(&sb, "rejected %s %s %s %s\n", refname,
|
||||||
|
new_oid ? oid_to_hex(new_oid) : new_target,
|
||||||
|
old_oid ? oid_to_hex(old_oid) : old_target,
|
||||||
|
reason);
|
||||||
|
|
||||||
|
fwrite(sb.buf, sb.len, 1, stdout);
|
||||||
|
strbuf_release(&sb);
|
||||||
|
}
|
||||||
|
|
||||||
static void parse_cmd_commit(struct ref_transaction *transaction,
|
static void parse_cmd_commit(struct ref_transaction *transaction,
|
||||||
const char *next, const char *end UNUSED)
|
const char *next, const char *end UNUSED)
|
||||||
{
|
{
|
||||||
|
@ -573,6 +617,10 @@ static void parse_cmd_commit(struct ref_transaction *transaction,
|
||||||
die("commit: extra input: %s", next);
|
die("commit: extra input: %s", next);
|
||||||
if (ref_transaction_commit(transaction, &error))
|
if (ref_transaction_commit(transaction, &error))
|
||||||
die("commit: %s", error.buf);
|
die("commit: %s", error.buf);
|
||||||
|
|
||||||
|
ref_transaction_for_each_rejected_update(transaction,
|
||||||
|
print_rejected_refs, NULL);
|
||||||
|
|
||||||
report_ok("commit");
|
report_ok("commit");
|
||||||
ref_transaction_free(transaction);
|
ref_transaction_free(transaction);
|
||||||
}
|
}
|
||||||
|
@ -609,7 +657,7 @@ static const struct parse_cmd {
|
||||||
{ "commit", parse_cmd_commit, 0, UPDATE_REFS_CLOSED },
|
{ "commit", parse_cmd_commit, 0, UPDATE_REFS_CLOSED },
|
||||||
};
|
};
|
||||||
|
|
||||||
static void update_refs_stdin(void)
|
static void update_refs_stdin(unsigned int flags)
|
||||||
{
|
{
|
||||||
struct strbuf input = STRBUF_INIT, err = STRBUF_INIT;
|
struct strbuf input = STRBUF_INIT, err = STRBUF_INIT;
|
||||||
enum update_refs_state state = UPDATE_REFS_OPEN;
|
enum update_refs_state state = UPDATE_REFS_OPEN;
|
||||||
|
@ -617,7 +665,7 @@ static void update_refs_stdin(void)
|
||||||
int i, j;
|
int i, j;
|
||||||
|
|
||||||
transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
|
transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
|
||||||
0, &err);
|
flags, &err);
|
||||||
if (!transaction)
|
if (!transaction)
|
||||||
die("%s", err.buf);
|
die("%s", err.buf);
|
||||||
|
|
||||||
|
@ -685,7 +733,7 @@ static void update_refs_stdin(void)
|
||||||
*/
|
*/
|
||||||
state = cmd->state;
|
state = cmd->state;
|
||||||
transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
|
transaction = ref_store_transaction_begin(get_main_ref_store(the_repository),
|
||||||
0, &err);
|
flags, &err);
|
||||||
if (!transaction)
|
if (!transaction)
|
||||||
die("%s", err.buf);
|
die("%s", err.buf);
|
||||||
|
|
||||||
|
@ -701,6 +749,8 @@ static void update_refs_stdin(void)
|
||||||
/* Commit by default if no transaction was requested. */
|
/* Commit by default if no transaction was requested. */
|
||||||
if (ref_transaction_commit(transaction, &err))
|
if (ref_transaction_commit(transaction, &err))
|
||||||
die("%s", err.buf);
|
die("%s", err.buf);
|
||||||
|
ref_transaction_for_each_rejected_update(transaction,
|
||||||
|
print_rejected_refs, NULL);
|
||||||
ref_transaction_free(transaction);
|
ref_transaction_free(transaction);
|
||||||
break;
|
break;
|
||||||
case UPDATE_REFS_STARTED:
|
case UPDATE_REFS_STARTED:
|
||||||
|
@ -727,6 +777,8 @@ int cmd_update_ref(int argc,
|
||||||
struct object_id oid, oldoid;
|
struct object_id oid, oldoid;
|
||||||
int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0;
|
int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0;
|
||||||
int create_reflog = 0;
|
int create_reflog = 0;
|
||||||
|
unsigned int flags = 0;
|
||||||
|
|
||||||
struct option options[] = {
|
struct option options[] = {
|
||||||
OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")),
|
OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")),
|
||||||
OPT_BOOL('d', NULL, &delete, N_("delete the reference")),
|
OPT_BOOL('d', NULL, &delete, N_("delete the reference")),
|
||||||
|
@ -735,6 +787,8 @@ int cmd_update_ref(int argc,
|
||||||
OPT_BOOL('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")),
|
OPT_BOOL('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")),
|
||||||
OPT_BOOL( 0 , "stdin", &read_stdin, N_("read updates from stdin")),
|
OPT_BOOL( 0 , "stdin", &read_stdin, N_("read updates from stdin")),
|
||||||
OPT_BOOL( 0 , "create-reflog", &create_reflog, N_("create a reflog")),
|
OPT_BOOL( 0 , "create-reflog", &create_reflog, N_("create a reflog")),
|
||||||
|
OPT_BIT('0', "batch-updates", &flags, N_("batch reference updates"),
|
||||||
|
REF_TRANSACTION_ALLOW_FAILURE),
|
||||||
OPT_END(),
|
OPT_END(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -756,8 +810,10 @@ int cmd_update_ref(int argc,
|
||||||
usage_with_options(git_update_ref_usage, options);
|
usage_with_options(git_update_ref_usage, options);
|
||||||
if (end_null)
|
if (end_null)
|
||||||
line_termination = '\0';
|
line_termination = '\0';
|
||||||
update_refs_stdin();
|
update_refs_stdin(flags);
|
||||||
return 0;
|
return 0;
|
||||||
|
} else if (flags & REF_TRANSACTION_ALLOW_FAILURE) {
|
||||||
|
die("--batch-updates can only be used with --stdin");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (end_null)
|
if (end_null)
|
||||||
|
|
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);
|
CALLOC_ARRAY(tr, 1);
|
||||||
tr->ref_store = refs;
|
tr->ref_store = refs;
|
||||||
tr->flags = flags;
|
tr->flags = flags;
|
||||||
|
string_list_init_dup(&tr->refnames);
|
||||||
|
|
||||||
|
if (flags & REF_TRANSACTION_ALLOW_FAILURE)
|
||||||
|
CALLOC_ARRAY(tr->rejections, 1);
|
||||||
|
|
||||||
return tr;
|
return tr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1206,10 +1211,45 @@ void ref_transaction_free(struct ref_transaction *transaction)
|
||||||
free((char *)transaction->updates[i]->old_target);
|
free((char *)transaction->updates[i]->old_target);
|
||||||
free(transaction->updates[i]);
|
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->updates);
|
||||||
free(transaction);
|
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_update *ref_transaction_add_update(
|
||||||
struct ref_transaction *transaction,
|
struct ref_transaction *transaction,
|
||||||
const char *refname, unsigned int flags,
|
const char *refname, unsigned int flags,
|
||||||
|
@ -1219,6 +1259,7 @@ struct ref_update *ref_transaction_add_update(
|
||||||
const char *committer_info,
|
const char *committer_info,
|
||||||
const char *msg)
|
const char *msg)
|
||||||
{
|
{
|
||||||
|
struct string_list_item *item;
|
||||||
struct ref_update *update;
|
struct ref_update *update;
|
||||||
|
|
||||||
if (transaction->state != REF_TRANSACTION_OPEN)
|
if (transaction->state != REF_TRANSACTION_OPEN)
|
||||||
|
@ -1234,6 +1275,7 @@ struct ref_update *ref_transaction_add_update(
|
||||||
transaction->updates[transaction->nr++] = update;
|
transaction->updates[transaction->nr++] = update;
|
||||||
|
|
||||||
update->flags = flags;
|
update->flags = flags;
|
||||||
|
update->rejection_err = 0;
|
||||||
|
|
||||||
update->new_target = xstrdup_or_null(new_target);
|
update->new_target = xstrdup_or_null(new_target);
|
||||||
update->old_target = xstrdup_or_null(old_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);
|
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;
|
return update;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2279,7 +2331,7 @@ int refs_update_symref_extended(struct ref_store *refs, const char *ref,
|
||||||
REF_NO_DEREF, logmsg, &err))
|
REF_NO_DEREF, logmsg, &err))
|
||||||
goto error_return;
|
goto error_return;
|
||||||
prepret = ref_transaction_prepare(transaction, &err);
|
prepret = ref_transaction_prepare(transaction, &err);
|
||||||
if (prepret && prepret != TRANSACTION_CREATE_EXISTS)
|
if (prepret && prepret != REF_TRANSACTION_ERROR_CREATE_EXISTS)
|
||||||
goto error_return;
|
goto error_return;
|
||||||
} else {
|
} else {
|
||||||
if (ref_transaction_update(transaction, ref, NULL, NULL,
|
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;
|
goto cleanup;
|
||||||
|
|
||||||
if (ref_transaction_commit(transaction, &err))
|
if (ref_transaction_commit(transaction, &err))
|
||||||
|
@ -2311,8 +2363,13 @@ cleanup:
|
||||||
return ret;
|
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;
|
size_t i, n = refnames->nr;
|
||||||
|
|
||||||
|
@ -2426,6 +2483,10 @@ int ref_transaction_prepare(struct ref_transaction *transaction,
|
||||||
return -1;
|
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);
|
ret = refs->be->transaction_prepare(refs, transaction, err);
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
|
@ -2496,19 +2557,21 @@ int ref_transaction_commit(struct ref_transaction *transaction,
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int refs_verify_refnames_available(struct ref_store *refs,
|
enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs,
|
||||||
const struct string_list *refnames,
|
const struct string_list *refnames,
|
||||||
const struct string_list *extras,
|
const struct string_list *extras,
|
||||||
const struct string_list *skip,
|
const struct string_list *skip,
|
||||||
unsigned int initial_transaction,
|
struct ref_transaction *transaction,
|
||||||
struct strbuf *err)
|
unsigned int initial_transaction,
|
||||||
|
struct strbuf *err)
|
||||||
{
|
{
|
||||||
struct strbuf dirname = STRBUF_INIT;
|
struct strbuf dirname = STRBUF_INIT;
|
||||||
struct strbuf referent = STRBUF_INIT;
|
struct strbuf referent = STRBUF_INIT;
|
||||||
struct string_list_item *item;
|
struct string_list_item *item;
|
||||||
struct ref_iterator *iter = NULL;
|
struct ref_iterator *iter = NULL;
|
||||||
|
struct strset conflicting_dirnames;
|
||||||
struct strset dirnames;
|
struct strset dirnames;
|
||||||
int ret = -1;
|
int ret = REF_TRANSACTION_ERROR_NAME_CONFLICT;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* For the sake of comments in this function, suppose that
|
* 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);
|
assert(err);
|
||||||
|
|
||||||
|
strset_init(&conflicting_dirnames);
|
||||||
strset_init(&dirnames);
|
strset_init(&dirnames);
|
||||||
|
|
||||||
for_each_string_list_item(item, refnames) {
|
for_each_string_list_item(item, refnames) {
|
||||||
|
const size_t *update_idx = (size_t *)item->util;
|
||||||
const char *refname = item->string;
|
const char *refname = item->string;
|
||||||
const char *extra_refname;
|
const char *extra_refname;
|
||||||
struct object_id oid;
|
struct object_id oid;
|
||||||
|
@ -2557,14 +2622,30 @@ int refs_verify_refnames_available(struct ref_store *refs,
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (!initial_transaction &&
|
if (!initial_transaction &&
|
||||||
!refs_read_raw_ref(refs, dirname.buf, &oid, &referent,
|
(strset_contains(&conflicting_dirnames, dirname.buf) ||
|
||||||
&type, &ignore_errno)) {
|
!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'"),
|
strbuf_addf(err, _("'%s' exists; cannot create '%s'"),
|
||||||
dirname.buf, refname);
|
dirname.buf, refname);
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (extras && string_list_has_string(extras, dirname.buf)) {
|
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"),
|
strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"),
|
||||||
refname, dirname.buf);
|
refname, dirname.buf);
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
@ -2597,6 +2678,11 @@ int refs_verify_refnames_available(struct ref_store *refs,
|
||||||
string_list_has_string(skip, iter->refname))
|
string_list_has_string(skip, iter->refname))
|
||||||
continue;
|
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'"),
|
strbuf_addf(err, _("'%s' exists; cannot create '%s'"),
|
||||||
iter->refname, refname);
|
iter->refname, refname);
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
@ -2608,6 +2694,11 @@ int refs_verify_refnames_available(struct ref_store *refs,
|
||||||
|
|
||||||
extra_refname = find_descendant_ref(dirname.buf, extras, skip);
|
extra_refname = find_descendant_ref(dirname.buf, extras, skip);
|
||||||
if (extra_refname) {
|
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"),
|
strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"),
|
||||||
refname, extra_refname);
|
refname, extra_refname);
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
@ -2619,17 +2710,19 @@ int refs_verify_refnames_available(struct ref_store *refs,
|
||||||
cleanup:
|
cleanup:
|
||||||
strbuf_release(&referent);
|
strbuf_release(&referent);
|
||||||
strbuf_release(&dirname);
|
strbuf_release(&dirname);
|
||||||
|
strset_clear(&conflicting_dirnames);
|
||||||
strset_clear(&dirnames);
|
strset_clear(&dirnames);
|
||||||
ref_iterator_free(iter);
|
ref_iterator_free(iter);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int refs_verify_refname_available(struct ref_store *refs,
|
enum ref_transaction_error refs_verify_refname_available(
|
||||||
const char *refname,
|
struct ref_store *refs,
|
||||||
const struct string_list *extras,
|
const char *refname,
|
||||||
const struct string_list *skip,
|
const struct string_list *extras,
|
||||||
unsigned int initial_transaction,
|
const struct string_list *skip,
|
||||||
struct strbuf *err)
|
unsigned int initial_transaction,
|
||||||
|
struct strbuf *err)
|
||||||
{
|
{
|
||||||
struct string_list_item item = { .string = (char *) refname };
|
struct string_list_item item = { .string = (char *) refname };
|
||||||
struct string_list refnames = {
|
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,
|
return refs_verify_refnames_available(refs, &refnames, extras, skip,
|
||||||
initial_transaction, err);
|
NULL, initial_transaction, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct do_for_each_reflog_help {
|
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,
|
int refs_delete_refs(struct ref_store *refs, const char *logmsg,
|
||||||
struct string_list *refnames, unsigned int flags)
|
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);
|
return !update->new_target && is_null_oid(&update->new_oid);
|
||||||
}
|
}
|
||||||
|
|
||||||
int ref_update_check_old_target(const char *referent, struct ref_update *update,
|
enum ref_transaction_error ref_update_check_old_target(const char *referent,
|
||||||
struct strbuf *err)
|
struct ref_update *update,
|
||||||
|
struct strbuf *err)
|
||||||
{
|
{
|
||||||
if (!update->old_target)
|
if (!update->old_target)
|
||||||
BUG("called without old_target set");
|
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))
|
if (!strcmp(referent, update->old_target))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (!strcmp(referent, ""))
|
if (!strcmp(referent, "")) {
|
||||||
strbuf_addf(err, "verifying symref target: '%s': "
|
strbuf_addf(err, "verifying symref target: '%s': "
|
||||||
"reference is missing but expected %s",
|
"reference is missing but expected %s",
|
||||||
ref_update_original_update_refname(update),
|
ref_update_original_update_refname(update),
|
||||||
update->old_target);
|
update->old_target);
|
||||||
else
|
return REF_TRANSACTION_ERROR_NONEXISTENT_REF;
|
||||||
strbuf_addf(err, "verifying symref target: '%s': "
|
}
|
||||||
"is at %s but expected %s",
|
|
||||||
|
strbuf_addf(err, "verifying symref target: '%s': is at %s but expected %s",
|
||||||
ref_update_original_update_refname(update),
|
ref_update_original_update_refname(update),
|
||||||
referent, update->old_target);
|
referent, update->old_target);
|
||||||
return -1;
|
return REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct migration_data {
|
struct migration_data {
|
||||||
|
|
70
refs.h
70
refs.h
|
@ -16,6 +16,23 @@ struct worktree;
|
||||||
enum ref_storage_format ref_storage_format_by_name(const char *name);
|
enum ref_storage_format ref_storage_format_by_name(const char *name);
|
||||||
const char *ref_storage_format_to_name(enum ref_storage_format ref_storage_format);
|
const char *ref_storage_format_to_name(enum ref_storage_format ref_storage_format);
|
||||||
|
|
||||||
|
enum ref_transaction_error {
|
||||||
|
/* Default error code */
|
||||||
|
REF_TRANSACTION_ERROR_GENERIC = -1,
|
||||||
|
/* Ref name conflict like A vs A/B */
|
||||||
|
REF_TRANSACTION_ERROR_NAME_CONFLICT = -2,
|
||||||
|
/* Ref to be created already exists */
|
||||||
|
REF_TRANSACTION_ERROR_CREATE_EXISTS = -3,
|
||||||
|
/* ref expected but doesn't exist */
|
||||||
|
REF_TRANSACTION_ERROR_NONEXISTENT_REF = -4,
|
||||||
|
/* Provided old_oid or old_target of reference doesn't match actual */
|
||||||
|
REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE = -5,
|
||||||
|
/* Provided new_oid or new_target is invalid */
|
||||||
|
REF_TRANSACTION_ERROR_INVALID_NEW_VALUE = -6,
|
||||||
|
/* Expected ref to be symref, but is a regular ref */
|
||||||
|
REF_TRANSACTION_ERROR_EXPECTED_SYMREF = -7,
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Resolve a reference, recursively following symbolic references.
|
* Resolve a reference, recursively following symbolic references.
|
||||||
*
|
*
|
||||||
|
@ -117,24 +134,12 @@ int refs_read_symbolic_ref(struct ref_store *ref_store, const char *refname,
|
||||||
*
|
*
|
||||||
* extras and skip must be sorted.
|
* extras and skip must be sorted.
|
||||||
*/
|
*/
|
||||||
int refs_verify_refname_available(struct ref_store *refs,
|
enum ref_transaction_error refs_verify_refname_available(struct ref_store *refs,
|
||||||
const char *refname,
|
const char *refname,
|
||||||
const struct string_list *extras,
|
const struct string_list *extras,
|
||||||
const struct string_list *skip,
|
const struct string_list *skip,
|
||||||
unsigned int initial_transaction,
|
unsigned int initial_transaction,
|
||||||
struct strbuf *err);
|
struct strbuf *err);
|
||||||
|
|
||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
|
|
||||||
int refs_ref_exists(struct ref_store *refs, const char *refname);
|
int refs_ref_exists(struct ref_store *refs, const char *refname);
|
||||||
|
|
||||||
|
@ -650,6 +655,13 @@ enum ref_transaction_flag {
|
||||||
* either be absent or null_oid.
|
* either be absent or null_oid.
|
||||||
*/
|
*/
|
||||||
REF_TRANSACTION_FLAG_INITIAL = (1 << 0),
|
REF_TRANSACTION_FLAG_INITIAL = (1 << 0),
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The transaction mechanism by default fails all updates if any conflict
|
||||||
|
* is detected. This flag allows transactions to partially apply updates
|
||||||
|
* while rejecting updates which do not match the expected state.
|
||||||
|
*/
|
||||||
|
REF_TRANSACTION_ALLOW_FAILURE = (1 << 1),
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -830,13 +842,6 @@ int ref_transaction_verify(struct ref_transaction *transaction,
|
||||||
unsigned int flags,
|
unsigned int flags,
|
||||||
struct strbuf *err);
|
struct strbuf *err);
|
||||||
|
|
||||||
/* Naming conflict (for example, the ref names A and A/B conflict). */
|
|
||||||
#define TRANSACTION_NAME_CONFLICT -1
|
|
||||||
/* When only creation was requested, but the ref already exists. */
|
|
||||||
#define TRANSACTION_CREATE_EXISTS -2
|
|
||||||
/* All other errors. */
|
|
||||||
#define TRANSACTION_GENERIC_ERROR -3
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Perform the preparatory stages of committing `transaction`. Acquire
|
* Perform the preparatory stages of committing `transaction`. Acquire
|
||||||
* any needed locks, check preconditions, etc.; basically, do as much
|
* any needed locks, check preconditions, etc.; basically, do as much
|
||||||
|
@ -887,6 +892,21 @@ void ref_transaction_for_each_queued_update(struct ref_transaction *transaction,
|
||||||
ref_transaction_for_each_queued_update_fn cb,
|
ref_transaction_for_each_queued_update_fn cb,
|
||||||
void *cb_data);
|
void *cb_data);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Execute the given callback function for each of the reference updates which
|
||||||
|
* have been rejected in the given transaction.
|
||||||
|
*/
|
||||||
|
typedef void ref_transaction_for_each_rejected_update_fn(const char *refname,
|
||||||
|
const struct object_id *old_oid,
|
||||||
|
const struct object_id *new_oid,
|
||||||
|
const char *old_target,
|
||||||
|
const char *new_target,
|
||||||
|
enum ref_transaction_error err,
|
||||||
|
void *cb_data);
|
||||||
|
void ref_transaction_for_each_rejected_update(struct ref_transaction *transaction,
|
||||||
|
ref_transaction_for_each_rejected_update_fn cb,
|
||||||
|
void *cb_data);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Free `*transaction` and all associated data.
|
* Free `*transaction` and all associated data.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -663,7 +663,7 @@ static void unlock_ref(struct ref_lock *lock)
|
||||||
* broken, lock the reference anyway but clear old_oid.
|
* broken, lock the reference anyway but clear old_oid.
|
||||||
*
|
*
|
||||||
* Return 0 on success. On failure, write an error message to err and
|
* 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
|
* 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
|
* avoided, namely if we were successfully able to read the ref
|
||||||
* - Generate informative error messages in the case of failure
|
* - Generate informative error messages in the case of failure
|
||||||
*/
|
*/
|
||||||
static int lock_raw_ref(struct files_ref_store *refs,
|
static enum ref_transaction_error lock_raw_ref(struct files_ref_store *refs,
|
||||||
const char *refname, int mustexist,
|
struct ref_update *update,
|
||||||
struct string_list *refnames_to_check,
|
size_t update_idx,
|
||||||
const struct string_list *extras,
|
int mustexist,
|
||||||
struct ref_lock **lock_p,
|
struct string_list *refnames_to_check,
|
||||||
struct strbuf *referent,
|
const struct string_list *extras,
|
||||||
unsigned int *type,
|
struct ref_lock **lock_p,
|
||||||
struct strbuf *err)
|
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 ref_lock *lock;
|
||||||
struct strbuf ref_file = STRBUF_INIT;
|
struct strbuf ref_file = STRBUF_INIT;
|
||||||
int attempts_remaining = 3;
|
int attempts_remaining = 3;
|
||||||
int ret = TRANSACTION_GENERIC_ERROR;
|
|
||||||
int failure_errno;
|
int failure_errno;
|
||||||
|
|
||||||
assert(err);
|
assert(err);
|
||||||
|
@ -728,13 +731,14 @@ retry:
|
||||||
strbuf_reset(err);
|
strbuf_reset(err);
|
||||||
strbuf_addf(err, "unable to resolve reference '%s'",
|
strbuf_addf(err, "unable to resolve reference '%s'",
|
||||||
refname);
|
refname);
|
||||||
|
ret = REF_TRANSACTION_ERROR_NONEXISTENT_REF;
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
* The error message set by
|
* The error message set by
|
||||||
* refs_verify_refname_available() is
|
* refs_verify_refname_available() is
|
||||||
* OK.
|
* OK.
|
||||||
*/
|
*/
|
||||||
ret = TRANSACTION_NAME_CONFLICT;
|
ret = REF_TRANSACTION_ERROR_NAME_CONFLICT;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
|
@ -783,11 +787,14 @@ retry:
|
||||||
|
|
||||||
if (files_read_raw_ref(&refs->base, refname, &lock->old_oid, referent,
|
if (files_read_raw_ref(&refs->base, refname, &lock->old_oid, referent,
|
||||||
type, &failure_errno)) {
|
type, &failure_errno)) {
|
||||||
|
struct string_list_item *item;
|
||||||
|
|
||||||
if (failure_errno == ENOENT) {
|
if (failure_errno == ENOENT) {
|
||||||
if (mustexist) {
|
if (mustexist) {
|
||||||
/* Garden variety missing reference. */
|
/* Garden variety missing reference. */
|
||||||
strbuf_addf(err, "unable to resolve reference '%s'",
|
strbuf_addf(err, "unable to resolve reference '%s'",
|
||||||
refname);
|
refname);
|
||||||
|
ret = REF_TRANSACTION_ERROR_NONEXISTENT_REF;
|
||||||
goto error_return;
|
goto error_return;
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
|
@ -820,6 +827,7 @@ retry:
|
||||||
/* Garden variety missing reference. */
|
/* Garden variety missing reference. */
|
||||||
strbuf_addf(err, "unable to resolve reference '%s'",
|
strbuf_addf(err, "unable to resolve reference '%s'",
|
||||||
refname);
|
refname);
|
||||||
|
ret = REF_TRANSACTION_ERROR_NONEXISTENT_REF;
|
||||||
goto error_return;
|
goto error_return;
|
||||||
} else if (remove_dir_recursively(&ref_file,
|
} else if (remove_dir_recursively(&ref_file,
|
||||||
REMOVE_DIR_EMPTY_ONLY)) {
|
REMOVE_DIR_EMPTY_ONLY)) {
|
||||||
|
@ -830,7 +838,7 @@ retry:
|
||||||
* The error message set by
|
* The error message set by
|
||||||
* verify_refname_available() is OK.
|
* verify_refname_available() is OK.
|
||||||
*/
|
*/
|
||||||
ret = TRANSACTION_NAME_CONFLICT;
|
ret = REF_TRANSACTION_ERROR_NAME_CONFLICT;
|
||||||
goto error_return;
|
goto error_return;
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
|
@ -860,7 +868,9 @@ retry:
|
||||||
* make sure there is no existing packed ref that conflicts
|
* make sure there is no existing packed ref that conflicts
|
||||||
* with refname. This check is deferred so that we can batch it.
|
* 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;
|
ret = 0;
|
||||||
|
@ -1517,10 +1527,11 @@ static int rename_tmp_log(struct files_ref_store *refs, const char *newrefname)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int write_ref_to_lockfile(struct files_ref_store *refs,
|
static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs,
|
||||||
struct ref_lock *lock,
|
struct ref_lock *lock,
|
||||||
const struct object_id *oid,
|
const struct object_id *oid,
|
||||||
int skip_oid_verification, struct strbuf *err);
|
int skip_oid_verification,
|
||||||
|
struct strbuf *err);
|
||||||
static int commit_ref_update(struct files_ref_store *refs,
|
static int commit_ref_update(struct files_ref_store *refs,
|
||||||
struct ref_lock *lock,
|
struct ref_lock *lock,
|
||||||
const struct object_id *oid, const char *logmsg,
|
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
|
* Write oid into the open lockfile, then close the lockfile. On
|
||||||
* errors, rollback the lockfile, fill in *err and return -1.
|
* errors, rollback the lockfile, fill in *err and return -1.
|
||||||
*/
|
*/
|
||||||
static int write_ref_to_lockfile(struct files_ref_store *refs,
|
static enum ref_transaction_error write_ref_to_lockfile(struct files_ref_store *refs,
|
||||||
struct ref_lock *lock,
|
struct ref_lock *lock,
|
||||||
const struct object_id *oid,
|
const struct object_id *oid,
|
||||||
int skip_oid_verification, struct strbuf *err)
|
int skip_oid_verification,
|
||||||
|
struct strbuf *err)
|
||||||
{
|
{
|
||||||
static char term = '\n';
|
static char term = '\n';
|
||||||
struct object *o;
|
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",
|
"trying to write ref '%s' with nonexistent object %s",
|
||||||
lock->ref_name, oid_to_hex(oid));
|
lock->ref_name, oid_to_hex(oid));
|
||||||
unlock_ref(lock);
|
unlock_ref(lock);
|
||||||
return -1;
|
return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
|
||||||
}
|
}
|
||||||
if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) {
|
if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) {
|
||||||
strbuf_addf(
|
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'",
|
"trying to write non-commit object %s to branch '%s'",
|
||||||
oid_to_hex(oid), lock->ref_name);
|
oid_to_hex(oid), lock->ref_name);
|
||||||
unlock_ref(lock);
|
unlock_ref(lock);
|
||||||
return -1;
|
return REF_TRANSACTION_ERROR_INVALID_NEW_VALUE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fd = get_lock_file_fd(&lock->lk);
|
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,
|
strbuf_addf(err,
|
||||||
"couldn't write '%s'", get_lock_file_path(&lock->lk));
|
"couldn't write '%s'", get_lock_file_path(&lock->lk));
|
||||||
unlock_ref(lock);
|
unlock_ref(lock);
|
||||||
return -1;
|
return REF_TRANSACTION_ERROR_GENERIC;
|
||||||
}
|
}
|
||||||
return 0;
|
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
|
* 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.
|
* by HEAD), then add an extra REF_LOG_ONLY update for HEAD.
|
||||||
*/
|
*/
|
||||||
static int split_head_update(struct ref_update *update,
|
static enum ref_transaction_error split_head_update(struct ref_update *update,
|
||||||
struct ref_transaction *transaction,
|
struct ref_transaction *transaction,
|
||||||
const char *head_ref,
|
const char *head_ref,
|
||||||
struct string_list *affected_refnames,
|
struct strbuf *err)
|
||||||
struct strbuf *err)
|
|
||||||
{
|
{
|
||||||
struct string_list_item *item;
|
|
||||||
struct ref_update *new_update;
|
struct ref_update *new_update;
|
||||||
|
|
||||||
if ((update->flags & REF_LOG_ONLY) ||
|
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
|
* transaction. This check is O(lg N) in the transaction
|
||||||
* size, but it happens at most once per 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 */
|
/* An entry already existed */
|
||||||
strbuf_addf(err,
|
strbuf_addf(err,
|
||||||
"multiple updates for 'HEAD' (including one "
|
"multiple updates for 'HEAD' (including one "
|
||||||
"via its referent '%s') are not allowed",
|
"via its referent '%s') are not allowed",
|
||||||
update->refname);
|
update->refname);
|
||||||
return TRANSACTION_NAME_CONFLICT;
|
return REF_TRANSACTION_ERROR_NAME_CONFLICT;
|
||||||
}
|
}
|
||||||
|
|
||||||
new_update = ref_transaction_add_update(
|
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"))
|
if (strcmp(new_update->refname, "HEAD"))
|
||||||
BUG("%s unexpectedly not 'HEAD'", new_update->refname);
|
BUG("%s unexpectedly not 'HEAD'", new_update->refname);
|
||||||
item = string_list_insert(affected_refnames, new_update->refname);
|
|
||||||
item->util = new_update;
|
|
||||||
|
|
||||||
return 0;
|
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
|
* Note that the new update will itself be subject to splitting when
|
||||||
* the iteration gets to it.
|
* the iteration gets to it.
|
||||||
*/
|
*/
|
||||||
static int split_symref_update(struct ref_update *update,
|
static enum ref_transaction_error split_symref_update(struct ref_update *update,
|
||||||
const char *referent,
|
const char *referent,
|
||||||
struct ref_transaction *transaction,
|
struct ref_transaction *transaction,
|
||||||
struct string_list *affected_refnames,
|
struct strbuf *err)
|
||||||
struct strbuf *err)
|
|
||||||
{
|
{
|
||||||
struct string_list_item *item;
|
|
||||||
struct ref_update *new_update;
|
struct ref_update *new_update;
|
||||||
unsigned int new_flags;
|
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
|
* size, but it happens at most once per symref in a
|
||||||
* transaction.
|
* transaction.
|
||||||
*/
|
*/
|
||||||
if (string_list_has_string(affected_refnames, referent)) {
|
if (string_list_has_string(&transaction->refnames, referent)) {
|
||||||
/* An entry already exists */
|
/* An entry already exists */
|
||||||
strbuf_addf(err,
|
strbuf_addf(err,
|
||||||
"multiple updates for '%s' (including one "
|
"multiple updates for '%s' (including one "
|
||||||
"via symref '%s') are not allowed",
|
"via symref '%s') are not allowed",
|
||||||
referent, update->refname);
|
referent, update->refname);
|
||||||
return TRANSACTION_NAME_CONFLICT;
|
return REF_TRANSACTION_ERROR_NAME_CONFLICT;
|
||||||
}
|
}
|
||||||
|
|
||||||
new_flags = update->flags;
|
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_LOG_ONLY | REF_NO_DEREF;
|
||||||
update->flags &= ~REF_HAVE_OLD;
|
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;
|
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
|
* everything is OK, return 0; otherwise, write an error message to
|
||||||
* err and return -1.
|
* err and return -1.
|
||||||
*/
|
*/
|
||||||
static int check_old_oid(struct ref_update *update, struct object_id *oid,
|
static enum ref_transaction_error check_old_oid(struct ref_update *update,
|
||||||
struct strbuf *err)
|
struct object_id *oid,
|
||||||
|
struct strbuf *err)
|
||||||
{
|
{
|
||||||
int ret = TRANSACTION_GENERIC_ERROR;
|
|
||||||
|
|
||||||
if (!(update->flags & REF_HAVE_OLD) ||
|
if (!(update->flags & REF_HAVE_OLD) ||
|
||||||
oideq(oid, &update->old_oid))
|
oideq(oid, &update->old_oid))
|
||||||
return 0;
|
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': "
|
strbuf_addf(err, "cannot lock ref '%s': "
|
||||||
"reference already exists",
|
"reference already exists",
|
||||||
ref_update_original_update_refname(update));
|
ref_update_original_update_refname(update));
|
||||||
ret = TRANSACTION_CREATE_EXISTS;
|
return REF_TRANSACTION_ERROR_CREATE_EXISTS;
|
||||||
}
|
} else if (is_null_oid(oid)) {
|
||||||
else if (is_null_oid(oid))
|
|
||||||
strbuf_addf(err, "cannot lock ref '%s': "
|
strbuf_addf(err, "cannot lock ref '%s': "
|
||||||
"reference is missing but expected %s",
|
"reference is missing but expected %s",
|
||||||
ref_update_original_update_refname(update),
|
ref_update_original_update_refname(update),
|
||||||
oid_to_hex(&update->old_oid));
|
oid_to_hex(&update->old_oid));
|
||||||
else
|
return REF_TRANSACTION_ERROR_NONEXISTENT_REF;
|
||||||
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 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 {
|
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
|
* - If it is an update of head_ref, add a corresponding REF_LOG_ONLY
|
||||||
* update of HEAD.
|
* update of HEAD.
|
||||||
*/
|
*/
|
||||||
static int lock_ref_for_update(struct files_ref_store *refs,
|
static enum ref_transaction_error lock_ref_for_update(struct files_ref_store *refs,
|
||||||
struct ref_update *update,
|
struct ref_update *update,
|
||||||
struct ref_transaction *transaction,
|
size_t update_idx,
|
||||||
const char *head_ref,
|
struct ref_transaction *transaction,
|
||||||
struct string_list *refnames_to_check,
|
const char *head_ref,
|
||||||
struct string_list *affected_refnames,
|
struct string_list *refnames_to_check,
|
||||||
struct strbuf *err)
|
struct strbuf *err)
|
||||||
{
|
{
|
||||||
struct strbuf referent = STRBUF_INIT;
|
struct strbuf referent = STRBUF_INIT;
|
||||||
int mustexist = ref_update_expects_existing_old_ref(update);
|
int mustexist = ref_update_expects_existing_old_ref(update);
|
||||||
struct files_transaction_backend_data *backend_data;
|
struct files_transaction_backend_data *backend_data;
|
||||||
int ret = 0;
|
enum ref_transaction_error ret = 0;
|
||||||
struct ref_lock *lock;
|
struct ref_lock *lock;
|
||||||
|
|
||||||
files_assert_main_repository(refs, "lock_ref_for_update");
|
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;
|
update->flags |= REF_DELETING;
|
||||||
|
|
||||||
if (head_ref) {
|
if (head_ref) {
|
||||||
ret = split_head_update(update, transaction, head_ref,
|
ret = split_head_update(update, transaction, head_ref, err);
|
||||||
affected_refnames, err);
|
|
||||||
if (ret)
|
if (ret)
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
@ -2592,10 +2582,9 @@ static int lock_ref_for_update(struct files_ref_store *refs,
|
||||||
if (lock) {
|
if (lock) {
|
||||||
lock->count++;
|
lock->count++;
|
||||||
} else {
|
} else {
|
||||||
ret = lock_raw_ref(refs, update->refname, mustexist,
|
ret = lock_raw_ref(refs, update, update_idx, mustexist,
|
||||||
refnames_to_check, affected_refnames,
|
refnames_to_check, &transaction->refnames,
|
||||||
&lock, &referent,
|
&lock, &referent, err);
|
||||||
&update->type, err);
|
|
||||||
if (ret) {
|
if (ret) {
|
||||||
char *reason;
|
char *reason;
|
||||||
|
|
||||||
|
@ -2625,22 +2614,17 @@ static int lock_ref_for_update(struct files_ref_store *refs,
|
||||||
strbuf_addf(err, "cannot lock ref '%s': "
|
strbuf_addf(err, "cannot lock ref '%s': "
|
||||||
"error reading reference",
|
"error reading reference",
|
||||||
ref_update_original_update_refname(update));
|
ref_update_original_update_refname(update));
|
||||||
ret = TRANSACTION_GENERIC_ERROR;
|
ret = REF_TRANSACTION_ERROR_GENERIC;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (update->old_target) {
|
if (update->old_target)
|
||||||
if (ref_update_check_old_target(referent.buf, update, err)) {
|
ret = ref_update_check_old_target(referent.buf, update, err);
|
||||||
ret = TRANSACTION_GENERIC_ERROR;
|
else
|
||||||
goto out;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ret = check_old_oid(update, &lock->old_oid, err);
|
ret = check_old_oid(update, &lock->old_oid, err);
|
||||||
if (ret) {
|
if (ret)
|
||||||
goto out;
|
goto out;
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
/*
|
/*
|
||||||
* Create a new update for the reference this
|
* 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
|
* of processing the split-off update, so we
|
||||||
* don't have to do it here.
|
* don't have to do it here.
|
||||||
*/
|
*/
|
||||||
ret = split_symref_update(update,
|
ret = split_symref_update(update, referent.buf,
|
||||||
referent.buf, transaction,
|
transaction, err);
|
||||||
affected_refnames, err);
|
|
||||||
if (ret)
|
if (ret)
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
@ -2668,7 +2651,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
|
||||||
"but is a regular ref"),
|
"but is a regular ref"),
|
||||||
ref_update_original_update_refname(update),
|
ref_update_original_update_refname(update),
|
||||||
update->old_target);
|
update->old_target);
|
||||||
ret = TRANSACTION_GENERIC_ERROR;
|
ret = REF_TRANSACTION_ERROR_EXPECTED_SYMREF;
|
||||||
goto out;
|
goto out;
|
||||||
} else {
|
} else {
|
||||||
ret = check_old_oid(update, &lock->old_oid, err);
|
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 (update->new_target && !(update->flags & REF_LOG_ONLY)) {
|
||||||
if (create_symref_lock(lock, update->new_target, err)) {
|
if (create_symref_lock(lock, update->new_target, err)) {
|
||||||
ret = TRANSACTION_GENERIC_ERROR;
|
ret = REF_TRANSACTION_ERROR_GENERIC;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (close_ref_gently(lock)) {
|
if (close_ref_gently(lock)) {
|
||||||
strbuf_addf(err, "couldn't close '%s.lock'",
|
strbuf_addf(err, "couldn't close '%s.lock'",
|
||||||
update->refname);
|
update->refname);
|
||||||
ret = TRANSACTION_GENERIC_ERROR;
|
ret = REF_TRANSACTION_ERROR_GENERIC;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2717,25 +2700,27 @@ static int lock_ref_for_update(struct files_ref_store *refs,
|
||||||
* The reference already has the desired
|
* The reference already has the desired
|
||||||
* value, so we don't need to write it.
|
* 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 {
|
} 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)) {
|
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)) {
|
if (close_ref_gently(lock)) {
|
||||||
strbuf_addf(err, "couldn't close '%s.lock'",
|
strbuf_addf(err, "couldn't close '%s.lock'",
|
||||||
update->refname);
|
update->refname);
|
||||||
ret = TRANSACTION_GENERIC_ERROR;
|
ret = REF_TRANSACTION_ERROR_GENERIC;
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2806,7 +2791,6 @@ static int files_transaction_prepare(struct ref_store *ref_store,
|
||||||
"ref_transaction_prepare");
|
"ref_transaction_prepare");
|
||||||
size_t i;
|
size_t i;
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
|
|
||||||
struct string_list refnames_to_check = STRING_LIST_INIT_NODUP;
|
struct string_list refnames_to_check = STRING_LIST_INIT_NODUP;
|
||||||
char *head_ref = NULL;
|
char *head_ref = NULL;
|
||||||
int head_type;
|
int head_type;
|
||||||
|
@ -2825,36 +2809,14 @@ static int files_transaction_prepare(struct ref_store *ref_store,
|
||||||
transaction->backend_data = backend_data;
|
transaction->backend_data = backend_data;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Fail if a refname appears more than once in the
|
* Fail if any of the updates use REF_IS_PRUNING without REF_NO_DEREF.
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
for (i = 0; i < transaction->nr; i++) {
|
for (i = 0; i < transaction->nr; i++) {
|
||||||
struct ref_update *update = transaction->updates[i];
|
struct ref_update *update = transaction->updates[i];
|
||||||
struct string_list_item *item;
|
|
||||||
|
|
||||||
if ((update->flags & REF_IS_PRUNING) &&
|
if ((update->flags & REF_IS_PRUNING) &&
|
||||||
!(update->flags & REF_NO_DEREF))
|
!(update->flags & REF_NO_DEREF))
|
||||||
BUG("REF_IS_PRUNING set without 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++) {
|
for (i = 0; i < transaction->nr; i++) {
|
||||||
struct ref_update *update = transaction->updates[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,
|
head_ref, &refnames_to_check,
|
||||||
&affected_refnames, err);
|
err);
|
||||||
if (ret)
|
if (ret) {
|
||||||
|
if (ref_transaction_maybe_set_rejected(transaction, i, ret)) {
|
||||||
|
strbuf_reset(err);
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
if (update->flags & REF_DELETING &&
|
if (update->flags & REF_DELETING &&
|
||||||
!(update->flags & REF_LOG_ONLY) &&
|
!(update->flags & REF_LOG_ONLY) &&
|
||||||
|
@ -2912,7 +2881,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
|
||||||
refs->packed_ref_store,
|
refs->packed_ref_store,
|
||||||
transaction->flags, err);
|
transaction->flags, err);
|
||||||
if (!packed_transaction) {
|
if (!packed_transaction) {
|
||||||
ret = TRANSACTION_GENERIC_ERROR;
|
ret = REF_TRANSACTION_ERROR_GENERIC;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2943,14 +2912,15 @@ static int files_transaction_prepare(struct ref_store *ref_store,
|
||||||
* So instead, we accept the race for now.
|
* So instead, we accept the race for now.
|
||||||
*/
|
*/
|
||||||
if (refs_verify_refnames_available(refs->packed_ref_store, &refnames_to_check,
|
if (refs_verify_refnames_available(refs->packed_ref_store, &refnames_to_check,
|
||||||
&affected_refnames, NULL, 0, err)) {
|
&transaction->refnames, NULL, transaction,
|
||||||
ret = TRANSACTION_NAME_CONFLICT;
|
0, err)) {
|
||||||
|
ret = REF_TRANSACTION_ERROR_NAME_CONFLICT;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packed_transaction) {
|
if (packed_transaction) {
|
||||||
if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
|
if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
|
||||||
ret = TRANSACTION_GENERIC_ERROR;
|
ret = REF_TRANSACTION_ERROR_GENERIC;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
backend_data->packed_refs_locked = 1;
|
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;
|
backend_data->packed_transaction = NULL;
|
||||||
if (ref_transaction_abort(packed_transaction, err)) {
|
if (ref_transaction_abort(packed_transaction, err)) {
|
||||||
ret = TRANSACTION_GENERIC_ERROR;
|
ret = REF_TRANSACTION_ERROR_GENERIC;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2989,8 +2959,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
|
||||||
|
|
||||||
cleanup:
|
cleanup:
|
||||||
free(head_ref);
|
free(head_ref);
|
||||||
string_list_clear(&affected_refnames, 0);
|
string_list_clear(&refnames_to_check, 1);
|
||||||
string_list_clear(&refnames_to_check, 0);
|
|
||||||
|
|
||||||
if (ret)
|
if (ret)
|
||||||
files_transaction_cleanup(refs, transaction);
|
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)
|
if (transaction->state != REF_TRANSACTION_PREPARED)
|
||||||
BUG("commit called for transaction that is not 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
|
* It's really undefined to call this function in an active
|
||||||
* repository or when there are existing references: we are
|
* 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.
|
* that we are creating already exists.
|
||||||
*/
|
*/
|
||||||
if (refs_for_each_rawref(&refs->base, ref_present,
|
if (refs_for_each_rawref(&refs->base, ref_present,
|
||||||
&affected_refnames))
|
&transaction->refnames))
|
||||||
BUG("initial ref transaction called with existing refs");
|
BUG("initial ref transaction called with existing refs");
|
||||||
|
|
||||||
packed_transaction = ref_store_transaction_begin(refs->packed_ref_store,
|
packed_transaction = ref_store_transaction_begin(refs->packed_ref_store,
|
||||||
transaction->flags, err);
|
transaction->flags, err);
|
||||||
if (!packed_transaction) {
|
if (!packed_transaction) {
|
||||||
ret = TRANSACTION_GENERIC_ERROR;
|
ret = REF_TRANSACTION_ERROR_GENERIC;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3117,7 +3075,7 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
|
||||||
if (!loose_transaction) {
|
if (!loose_transaction) {
|
||||||
loose_transaction = ref_store_transaction_begin(&refs->base, 0, err);
|
loose_transaction = ref_store_transaction_begin(&refs->base, 0, err);
|
||||||
if (!loose_transaction) {
|
if (!loose_transaction) {
|
||||||
ret = TRANSACTION_GENERIC_ERROR;
|
ret = REF_TRANSACTION_ERROR_GENERIC;
|
||||||
goto cleanup;
|
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)) {
|
if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
|
||||||
ret = TRANSACTION_GENERIC_ERROR;
|
ret = REF_TRANSACTION_ERROR_GENERIC;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (refs_verify_refnames_available(&refs->base, &refnames_to_check,
|
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);
|
packed_refs_unlock(refs->packed_ref_store);
|
||||||
ret = TRANSACTION_NAME_CONFLICT;
|
ret = REF_TRANSACTION_ERROR_NAME_CONFLICT;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ref_transaction_commit(packed_transaction, err)) {
|
if (ref_transaction_commit(packed_transaction, err)) {
|
||||||
ret = TRANSACTION_GENERIC_ERROR;
|
ret = REF_TRANSACTION_ERROR_GENERIC;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
packed_refs_unlock(refs->packed_ref_store);
|
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 (loose_transaction) {
|
||||||
if (ref_transaction_prepare(loose_transaction, err) ||
|
if (ref_transaction_prepare(loose_transaction, err) ||
|
||||||
ref_transaction_commit(loose_transaction, err)) {
|
ref_transaction_commit(loose_transaction, err)) {
|
||||||
ret = TRANSACTION_GENERIC_ERROR;
|
ret = REF_TRANSACTION_ERROR_GENERIC;
|
||||||
goto cleanup;
|
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_update *update = transaction->updates[i];
|
||||||
struct ref_lock *lock = update->backend_data;
|
struct ref_lock *lock = update->backend_data;
|
||||||
|
|
||||||
|
if (update->rejection_err)
|
||||||
|
continue;
|
||||||
|
|
||||||
if (update->flags & REF_NEEDS_COMMIT ||
|
if (update->flags & REF_NEEDS_COMMIT ||
|
||||||
update->flags & REF_LOG_ONLY) {
|
update->flags & REF_LOG_ONLY) {
|
||||||
if (parse_and_write_reflog(refs, update, lock, err)) {
|
if (parse_and_write_reflog(refs, update, lock, err)) {
|
||||||
ret = TRANSACTION_GENERIC_ERROR;
|
ret = REF_TRANSACTION_ERROR_GENERIC;
|
||||||
goto cleanup;
|
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);
|
strbuf_addf(err, "couldn't set '%s'", lock->ref_name);
|
||||||
unlock_ref(lock);
|
unlock_ref(lock);
|
||||||
update->backend_data = NULL;
|
update->backend_data = NULL;
|
||||||
ret = TRANSACTION_GENERIC_ERROR;
|
ret = REF_TRANSACTION_ERROR_GENERIC;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3286,7 +3248,7 @@ static int files_transaction_finish(struct ref_store *ref_store,
|
||||||
strbuf_reset(&sb);
|
strbuf_reset(&sb);
|
||||||
files_ref_path(refs, &sb, lock->ref_name);
|
files_ref_path(refs, &sb, lock->ref_name);
|
||||||
if (unlink_or_msg(sb.buf, err)) {
|
if (unlink_or_msg(sb.buf, err)) {
|
||||||
ret = TRANSACTION_GENERIC_ERROR;
|
ret = REF_TRANSACTION_ERROR_GENERIC;
|
||||||
goto cleanup;
|
goto cleanup;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* The packfile must be locked before calling this function and will
|
||||||
* remain locked when it is done.
|
* remain locked when it is done.
|
||||||
*/
|
*/
|
||||||
static int write_with_updates(struct packed_ref_store *refs,
|
static enum ref_transaction_error write_with_updates(struct packed_ref_store *refs,
|
||||||
struct string_list *updates,
|
struct ref_transaction *transaction,
|
||||||
struct strbuf *err)
|
struct strbuf *err)
|
||||||
{
|
{
|
||||||
|
enum ref_transaction_error ret = REF_TRANSACTION_ERROR_GENERIC;
|
||||||
|
struct string_list *updates = &transaction->refnames;
|
||||||
struct ref_iterator *iter = NULL;
|
struct ref_iterator *iter = NULL;
|
||||||
size_t i;
|
size_t i;
|
||||||
int ok;
|
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",
|
strbuf_addf(err, "unable to create file %s: %s",
|
||||||
sb.buf, strerror(errno));
|
sb.buf, strerror(errno));
|
||||||
strbuf_release(&sb);
|
strbuf_release(&sb);
|
||||||
return -1;
|
return REF_TRANSACTION_ERROR_GENERIC;
|
||||||
}
|
}
|
||||||
strbuf_release(&sb);
|
strbuf_release(&sb);
|
||||||
|
|
||||||
|
@ -1434,6 +1436,14 @@ static int write_with_updates(struct packed_ref_store *refs,
|
||||||
strbuf_addf(err, "cannot update ref '%s': "
|
strbuf_addf(err, "cannot update ref '%s': "
|
||||||
"reference already exists",
|
"reference already exists",
|
||||||
update->refname);
|
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;
|
goto error;
|
||||||
} else if (!oideq(&update->old_oid, iter->oid)) {
|
} else if (!oideq(&update->old_oid, iter->oid)) {
|
||||||
strbuf_addf(err, "cannot update ref '%s': "
|
strbuf_addf(err, "cannot update ref '%s': "
|
||||||
|
@ -1441,6 +1451,14 @@ static int write_with_updates(struct packed_ref_store *refs,
|
||||||
update->refname,
|
update->refname,
|
||||||
oid_to_hex(iter->oid),
|
oid_to_hex(iter->oid),
|
||||||
oid_to_hex(&update->old_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;
|
goto error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1477,6 +1495,14 @@ static int write_with_updates(struct packed_ref_store *refs,
|
||||||
"reference is missing but expected %s",
|
"reference is missing but expected %s",
|
||||||
update->refname,
|
update->refname,
|
||||||
oid_to_hex(&update->old_oid));
|
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;
|
goto error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1534,7 +1560,7 @@ static int write_with_updates(struct packed_ref_store *refs,
|
||||||
strerror(errno));
|
strerror(errno));
|
||||||
strbuf_release(&sb);
|
strbuf_release(&sb);
|
||||||
delete_tempfile(&refs->tempfile);
|
delete_tempfile(&refs->tempfile);
|
||||||
return -1;
|
return REF_TRANSACTION_ERROR_GENERIC;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -1542,11 +1568,12 @@ static int write_with_updates(struct packed_ref_store *refs,
|
||||||
write_error:
|
write_error:
|
||||||
strbuf_addf(err, "error writing to %s: %s",
|
strbuf_addf(err, "error writing to %s: %s",
|
||||||
get_tempfile_path(refs->tempfile), strerror(errno));
|
get_tempfile_path(refs->tempfile), strerror(errno));
|
||||||
|
ret = REF_TRANSACTION_ERROR_GENERIC;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
ref_iterator_free(iter);
|
ref_iterator_free(iter);
|
||||||
delete_tempfile(&refs->tempfile);
|
delete_tempfile(&refs->tempfile);
|
||||||
return -1;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
int is_packed_transaction_needed(struct ref_store *ref_store,
|
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 {
|
struct packed_transaction_backend_data {
|
||||||
/* True iff the transaction owns the packed-refs lock. */
|
/* True iff the transaction owns the packed-refs lock. */
|
||||||
int own_lock;
|
int own_lock;
|
||||||
|
|
||||||
struct string_list updates;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static void packed_transaction_cleanup(struct packed_ref_store *refs,
|
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;
|
struct packed_transaction_backend_data *data = transaction->backend_data;
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
string_list_clear(&data->updates, 0);
|
|
||||||
|
|
||||||
if (is_tempfile_active(refs->tempfile))
|
if (is_tempfile_active(refs->tempfile))
|
||||||
delete_tempfile(&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_STORE_READ | REF_STORE_WRITE | REF_STORE_ODB,
|
||||||
"ref_transaction_prepare");
|
"ref_transaction_prepare");
|
||||||
struct packed_transaction_backend_data *data;
|
struct packed_transaction_backend_data *data;
|
||||||
size_t i;
|
enum ref_transaction_error ret = REF_TRANSACTION_ERROR_GENERIC;
|
||||||
int ret = TRANSACTION_GENERIC_ERROR;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Note that we *don't* skip transactions with zero updates,
|
* 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);
|
CALLOC_ARRAY(data, 1);
|
||||||
string_list_init_nodup(&data->updates);
|
|
||||||
|
|
||||||
transaction->backend_data = data;
|
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 (!is_lock_file_locked(&refs->lock)) {
|
||||||
if (packed_refs_lock(ref_store, 0, err))
|
if (packed_refs_lock(ref_store, 0, err))
|
||||||
goto failure;
|
goto failure;
|
||||||
data->own_lock = 1;
|
data->own_lock = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (write_with_updates(refs, &data->updates, err))
|
ret = write_with_updates(refs, transaction, err);
|
||||||
|
if (ret)
|
||||||
goto failure;
|
goto failure;
|
||||||
|
|
||||||
transaction->state = REF_TRANSACTION_PREPARED;
|
transaction->state = REF_TRANSACTION_PREPARED;
|
||||||
|
@ -1755,7 +1760,7 @@ static int packed_transaction_finish(struct ref_store *ref_store,
|
||||||
ref_store,
|
ref_store,
|
||||||
REF_STORE_READ | REF_STORE_WRITE | REF_STORE_ODB,
|
REF_STORE_READ | REF_STORE_WRITE | REF_STORE_ODB,
|
||||||
"ref_transaction_finish");
|
"ref_transaction_finish");
|
||||||
int ret = TRANSACTION_GENERIC_ERROR;
|
int ret = REF_TRANSACTION_ERROR_GENERIC;
|
||||||
char *packed_refs_path;
|
char *packed_refs_path;
|
||||||
|
|
||||||
clear_snapshot(refs);
|
clear_snapshot(refs);
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
#include "refs.h"
|
#include "refs.h"
|
||||||
#include "iterator.h"
|
#include "iterator.h"
|
||||||
|
#include "string-list.h"
|
||||||
|
|
||||||
struct fsck_options;
|
struct fsck_options;
|
||||||
struct ref_transaction;
|
struct ref_transaction;
|
||||||
|
@ -122,6 +123,12 @@ struct ref_update {
|
||||||
*/
|
*/
|
||||||
uint64_t index;
|
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
|
* If this ref_update was split off of a symref update via
|
||||||
* split_symref_update(), then this member points at that
|
* split_symref_update(), then this member points at that
|
||||||
|
@ -142,12 +149,11 @@ int refs_read_raw_ref(struct ref_store *ref_store, const char *refname,
|
||||||
unsigned int *type, int *failure_errno);
|
unsigned int *type, int *failure_errno);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Write an error to `err` and return a nonzero value iff the same
|
* Mark a given update as rejected with a given reason.
|
||||||
* refname appears multiple times in `refnames`. `refnames` must be
|
|
||||||
* sorted on entry to this function.
|
|
||||||
*/
|
*/
|
||||||
int ref_update_reject_duplicates(struct string_list *refnames,
|
int ref_transaction_maybe_set_rejected(struct ref_transaction *transaction,
|
||||||
struct strbuf *err);
|
size_t update_idx,
|
||||||
|
enum ref_transaction_error err);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Add a ref_update with the specified properties to transaction, and
|
* Add a ref_update with the specified properties to transaction, and
|
||||||
|
@ -190,6 +196,18 @@ enum ref_transaction_state {
|
||||||
REF_TRANSACTION_CLOSED = 2
|
REF_TRANSACTION_CLOSED = 2
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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
|
* Data structure for holding a reference transaction, which can
|
||||||
* consist of checks and updates to multiple references, carried out
|
* consist of checks and updates to multiple references, carried out
|
||||||
|
@ -198,9 +216,11 @@ enum ref_transaction_state {
|
||||||
struct ref_transaction {
|
struct ref_transaction {
|
||||||
struct ref_store *ref_store;
|
struct ref_store *ref_store;
|
||||||
struct ref_update **updates;
|
struct ref_update **updates;
|
||||||
|
struct string_list refnames;
|
||||||
size_t alloc;
|
size_t alloc;
|
||||||
size_t nr;
|
size_t nr;
|
||||||
enum ref_transaction_state state;
|
enum ref_transaction_state state;
|
||||||
|
struct ref_transaction_rejections *rejections;
|
||||||
void *backend_data;
|
void *backend_data;
|
||||||
unsigned int flags;
|
unsigned int flags;
|
||||||
uint64_t max_index;
|
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
|
* If everything is OK, return 0; otherwise, write an error message to
|
||||||
* err and return -1.
|
* err and return -1.
|
||||||
*/
|
*/
|
||||||
int ref_update_check_old_target(const char *referent, struct ref_update *update,
|
enum ref_transaction_error ref_update_check_old_target(const char *referent,
|
||||||
struct strbuf *err);
|
struct ref_update *update,
|
||||||
|
struct strbuf *err);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check if the ref must exist, this means that the old_oid or
|
* 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);
|
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 */
|
#endif /* REFS_REFS_INTERNAL_H */
|
||||||
|
|
|
@ -1069,6 +1069,244 @@ static int queue_transaction_update(struct reftable_ref_store *refs,
|
||||||
return 0;
|
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,
|
||||||
|
¤t_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,
|
||||||
|
¤t_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,
|
||||||
|
¤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 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(¤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 REF_TRANSACTION_ERROR_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));
|
||||||
|
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(¤t_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(¤t_oid, &u->new_oid)))
|
||||||
|
if (queue_transaction_update(refs, tx_data, u, ¤t_oid, err))
|
||||||
|
return REF_TRANSACTION_ERROR_GENERIC;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int reftable_be_transaction_prepare(struct ref_store *ref_store,
|
static int reftable_be_transaction_prepare(struct ref_store *ref_store,
|
||||||
struct ref_transaction *transaction,
|
struct ref_transaction *transaction,
|
||||||
struct strbuf *err)
|
struct strbuf *err)
|
||||||
|
@ -1076,7 +1314,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
|
||||||
struct reftable_ref_store *refs =
|
struct reftable_ref_store *refs =
|
||||||
reftable_be_downcast(ref_store, REF_STORE_WRITE|REF_STORE_MAIN, "ref_transaction_prepare");
|
reftable_be_downcast(ref_store, REF_STORE_WRITE|REF_STORE_MAIN, "ref_transaction_prepare");
|
||||||
struct strbuf referent = STRBUF_INIT, head_referent = STRBUF_INIT;
|
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 string_list refnames_to_check = STRING_LIST_INIT_NODUP;
|
||||||
struct reftable_transaction_data *tx_data = NULL;
|
struct reftable_transaction_data *tx_data = NULL;
|
||||||
struct reftable_backend *be;
|
struct reftable_backend *be;
|
||||||
|
@ -1101,10 +1338,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
|
||||||
transaction->updates[i], err);
|
transaction->updates[i], err);
|
||||||
if (ret)
|
if (ret)
|
||||||
goto done;
|
goto done;
|
||||||
|
|
||||||
if (!(transaction->updates[i]->flags & REF_LOG_ONLY))
|
|
||||||
string_list_append(&affected_refnames,
|
|
||||||
transaction->updates[i]->refname);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -1116,17 +1349,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
|
||||||
tx_data->args[i].updates_alloc = tx_data->args[i].updates_expected;
|
tx_data->args[i].updates_alloc = tx_data->args[i].updates_expected;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* 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"
|
* 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
|
* belongs to or not. In theory, it may happen that we only modify
|
||||||
|
@ -1149,241 +1371,24 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
|
||||||
ret = 0;
|
ret = 0;
|
||||||
|
|
||||||
for (i = 0; i < transaction->nr; i++) {
|
for (i = 0; i < transaction->nr; i++) {
|
||||||
struct ref_update *u = transaction->updates[i];
|
ret = prepare_single_update(refs, tx_data, transaction, be,
|
||||||
struct object_id current_oid = {0};
|
transaction->updates[i], i,
|
||||||
const char *rewritten_ref;
|
&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;
|
||||||
|
|
||||||
/*
|
continue;
|
||||||
* 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;
|
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,
|
|
||||||
¤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 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
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, &affected_refnames, NULL,
|
ret = refs_verify_refnames_available(ref_store, &refnames_to_check,
|
||||||
|
&transaction->refnames, NULL,
|
||||||
|
transaction,
|
||||||
transaction->flags & REF_TRANSACTION_FLAG_INITIAL,
|
transaction->flags & REF_TRANSACTION_FLAG_INITIAL,
|
||||||
err);
|
err);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
|
@ -1393,7 +1398,6 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
|
||||||
transaction->state = REF_TRANSACTION_PREPARED;
|
transaction->state = REF_TRANSACTION_PREPARED;
|
||||||
|
|
||||||
done:
|
done:
|
||||||
assert(ret != REFTABLE_API_ERROR);
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
free_transaction_data(tx_data);
|
free_transaction_data(tx_data);
|
||||||
transaction->state = REF_TRANSACTION_CLOSED;
|
transaction->state = REF_TRANSACTION_CLOSED;
|
||||||
|
@ -1401,10 +1405,9 @@ done:
|
||||||
strbuf_addf(err, _("reftable: transaction prepare: %s"),
|
strbuf_addf(err, _("reftable: transaction prepare: %s"),
|
||||||
reftable_error_str(ret));
|
reftable_error_str(ret));
|
||||||
}
|
}
|
||||||
string_list_clear(&affected_refnames, 0);
|
|
||||||
strbuf_release(&referent);
|
strbuf_release(&referent);
|
||||||
strbuf_release(&head_referent);
|
strbuf_release(&head_referent);
|
||||||
string_list_clear(&refnames_to_check, 0);
|
string_list_clear(&refnames_to_check, 1);
|
||||||
|
|
||||||
return ret;
|
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 reftable_transaction_update *tx_update = &arg->updates[i];
|
||||||
struct ref_update *u = tx_update->update;
|
struct ref_update *u = tx_update->update;
|
||||||
|
|
||||||
|
if (u->rejection_err)
|
||||||
|
continue;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Write a reflog entry when updating a ref to point to
|
* Write a reflog entry when updating a ref to point to
|
||||||
* something new in either of the following cases:
|
* something new in either of the following cases:
|
||||||
|
|
|
@ -2066,6 +2066,239 @@ do
|
||||||
grep "$(git rev-parse $a) $(git rev-parse $a)" actual
|
grep "$(git rev-parse $a) $(git rev-parse $a)" actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success "stdin $type batch-updates" '
|
||||||
|
git init repo &&
|
||||||
|
test_when_finished "rm -fr repo" &&
|
||||||
|
(
|
||||||
|
cd repo &&
|
||||||
|
test_commit commit &&
|
||||||
|
head=$(git rev-parse HEAD) &&
|
||||||
|
|
||||||
|
format_command $type "update refs/heads/ref1" "$head" "$Z" >stdin &&
|
||||||
|
format_command $type "update refs/heads/ref2" "$head" "$Z" >>stdin &&
|
||||||
|
git update-ref $type --stdin --batch-updates <stdin &&
|
||||||
|
echo $head >expect &&
|
||||||
|
git rev-parse refs/heads/ref1 >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
git rev-parse refs/heads/ref2 >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "stdin $type batch-updates with invalid new_oid" '
|
||||||
|
git init repo &&
|
||||||
|
test_when_finished "rm -fr repo" &&
|
||||||
|
(
|
||||||
|
cd repo &&
|
||||||
|
test_commit one &&
|
||||||
|
old_head=$(git rev-parse HEAD) &&
|
||||||
|
test_commit two &&
|
||||||
|
head=$(git rev-parse HEAD) &&
|
||||||
|
git update-ref refs/heads/ref1 $head &&
|
||||||
|
git update-ref refs/heads/ref2 $head &&
|
||||||
|
|
||||||
|
format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin &&
|
||||||
|
format_command $type "update refs/heads/ref2" "$(test_oid 001)" "$head" >>stdin &&
|
||||||
|
git update-ref $type --stdin --batch-updates <stdin >stdout &&
|
||||||
|
echo $old_head >expect &&
|
||||||
|
git rev-parse refs/heads/ref1 >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
echo $head >expect &&
|
||||||
|
git rev-parse refs/heads/ref2 >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
test_grep -q "invalid new value provided" stdout
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "stdin $type batch-updates with non-commit new_oid" '
|
||||||
|
git init repo &&
|
||||||
|
test_when_finished "rm -fr repo" &&
|
||||||
|
(
|
||||||
|
cd repo &&
|
||||||
|
test_commit one &&
|
||||||
|
old_head=$(git rev-parse HEAD) &&
|
||||||
|
test_commit two &&
|
||||||
|
head=$(git rev-parse HEAD) &&
|
||||||
|
head_tree=$(git rev-parse HEAD^{tree}) &&
|
||||||
|
git update-ref refs/heads/ref1 $head &&
|
||||||
|
git update-ref refs/heads/ref2 $head &&
|
||||||
|
|
||||||
|
format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin &&
|
||||||
|
format_command $type "update refs/heads/ref2" "$head_tree" "$head" >>stdin &&
|
||||||
|
git update-ref $type --stdin --batch-updates <stdin >stdout &&
|
||||||
|
echo $old_head >expect &&
|
||||||
|
git rev-parse refs/heads/ref1 >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
echo $head >expect &&
|
||||||
|
git rev-parse refs/heads/ref2 >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
test_grep -q "invalid new value provided" stdout
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "stdin $type batch-updates with non-existent ref" '
|
||||||
|
git init repo &&
|
||||||
|
test_when_finished "rm -fr repo" &&
|
||||||
|
(
|
||||||
|
cd repo &&
|
||||||
|
test_commit one &&
|
||||||
|
old_head=$(git rev-parse HEAD) &&
|
||||||
|
test_commit two &&
|
||||||
|
head=$(git rev-parse HEAD) &&
|
||||||
|
git update-ref refs/heads/ref1 $head &&
|
||||||
|
|
||||||
|
format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin &&
|
||||||
|
format_command $type "update refs/heads/ref2" "$old_head" "$head" >>stdin &&
|
||||||
|
git update-ref $type --stdin --batch-updates <stdin >stdout &&
|
||||||
|
echo $old_head >expect &&
|
||||||
|
git rev-parse refs/heads/ref1 >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
test_must_fail git rev-parse refs/heads/ref2 &&
|
||||||
|
test_grep -q "reference does not exist" stdout
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "stdin $type batch-updates with dangling symref" '
|
||||||
|
git init repo &&
|
||||||
|
test_when_finished "rm -fr repo" &&
|
||||||
|
(
|
||||||
|
cd repo &&
|
||||||
|
test_commit one &&
|
||||||
|
old_head=$(git rev-parse HEAD) &&
|
||||||
|
test_commit two &&
|
||||||
|
head=$(git rev-parse HEAD) &&
|
||||||
|
git update-ref refs/heads/ref1 $head &&
|
||||||
|
git symbolic-ref refs/heads/ref2 refs/heads/nonexistent &&
|
||||||
|
|
||||||
|
format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin &&
|
||||||
|
format_command $type "update refs/heads/ref2" "$old_head" "$head" >>stdin &&
|
||||||
|
git update-ref $type --no-deref --stdin --batch-updates <stdin >stdout &&
|
||||||
|
echo $old_head >expect &&
|
||||||
|
git rev-parse refs/heads/ref1 >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
echo $head >expect &&
|
||||||
|
test_must_fail git rev-parse refs/heads/ref2 &&
|
||||||
|
test_grep -q "reference does not exist" stdout
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "stdin $type batch-updates with regular ref as symref" '
|
||||||
|
git init repo &&
|
||||||
|
test_when_finished "rm -fr repo" &&
|
||||||
|
(
|
||||||
|
cd repo &&
|
||||||
|
test_commit one &&
|
||||||
|
old_head=$(git rev-parse HEAD) &&
|
||||||
|
test_commit two &&
|
||||||
|
head=$(git rev-parse HEAD) &&
|
||||||
|
git update-ref refs/heads/ref1 $head &&
|
||||||
|
git update-ref refs/heads/ref2 $head &&
|
||||||
|
|
||||||
|
format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin &&
|
||||||
|
format_command $type "symref-update refs/heads/ref2" "$old_head" "ref" "refs/heads/nonexistent" >>stdin &&
|
||||||
|
git update-ref $type --no-deref --stdin --batch-updates <stdin >stdout &&
|
||||||
|
echo $old_head >expect &&
|
||||||
|
git rev-parse refs/heads/ref1 >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
echo $head >expect &&
|
||||||
|
echo $head >expect &&
|
||||||
|
git rev-parse refs/heads/ref2 >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
test_grep -q "expected symref but found regular ref" stdout
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "stdin $type batch-updates with invalid old_oid" '
|
||||||
|
git init repo &&
|
||||||
|
test_when_finished "rm -fr repo" &&
|
||||||
|
(
|
||||||
|
cd repo &&
|
||||||
|
test_commit one &&
|
||||||
|
old_head=$(git rev-parse HEAD) &&
|
||||||
|
test_commit two &&
|
||||||
|
head=$(git rev-parse HEAD) &&
|
||||||
|
git update-ref refs/heads/ref1 $head &&
|
||||||
|
git update-ref refs/heads/ref2 $head &&
|
||||||
|
|
||||||
|
format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin &&
|
||||||
|
format_command $type "update refs/heads/ref2" "$old_head" "$Z" >>stdin &&
|
||||||
|
git update-ref $type --stdin --batch-updates <stdin >stdout &&
|
||||||
|
echo $old_head >expect &&
|
||||||
|
git rev-parse refs/heads/ref1 >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
echo $head >expect &&
|
||||||
|
git rev-parse refs/heads/ref2 >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
test_grep -q "reference already exists" stdout
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "stdin $type batch-updates with incorrect old oid" '
|
||||||
|
git init repo &&
|
||||||
|
test_when_finished "rm -fr repo" &&
|
||||||
|
(
|
||||||
|
cd repo &&
|
||||||
|
test_commit one &&
|
||||||
|
old_head=$(git rev-parse HEAD) &&
|
||||||
|
test_commit two &&
|
||||||
|
head=$(git rev-parse HEAD) &&
|
||||||
|
git update-ref refs/heads/ref1 $head &&
|
||||||
|
git update-ref refs/heads/ref2 $head &&
|
||||||
|
|
||||||
|
format_command $type "update refs/heads/ref1" "$old_head" "$head" >stdin &&
|
||||||
|
format_command $type "update refs/heads/ref2" "$head" "$old_head" >>stdin &&
|
||||||
|
git update-ref $type --stdin --batch-updates <stdin >stdout &&
|
||||||
|
echo $old_head >expect &&
|
||||||
|
git rev-parse refs/heads/ref1 >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
echo $head >expect &&
|
||||||
|
git rev-parse refs/heads/ref2 >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
test_grep -q "incorrect old value provided" stdout
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "stdin $type batch-updates refname conflict" '
|
||||||
|
git init repo &&
|
||||||
|
test_when_finished "rm -fr repo" &&
|
||||||
|
(
|
||||||
|
cd repo &&
|
||||||
|
test_commit one &&
|
||||||
|
old_head=$(git rev-parse HEAD) &&
|
||||||
|
test_commit two &&
|
||||||
|
head=$(git rev-parse HEAD) &&
|
||||||
|
git update-ref refs/heads/ref/foo $head &&
|
||||||
|
|
||||||
|
format_command $type "update refs/heads/ref/foo" "$old_head" "$head" >stdin &&
|
||||||
|
format_command $type "update refs/heads/ref" "$old_head" "" >>stdin &&
|
||||||
|
git update-ref $type --stdin --batch-updates <stdin >stdout &&
|
||||||
|
echo $old_head >expect &&
|
||||||
|
git rev-parse refs/heads/ref/foo >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
test_grep -q "refname conflict" stdout
|
||||||
|
)
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "stdin $type batch-updates refname conflict new ref" '
|
||||||
|
git init repo &&
|
||||||
|
test_when_finished "rm -fr repo" &&
|
||||||
|
(
|
||||||
|
cd repo &&
|
||||||
|
test_commit one &&
|
||||||
|
old_head=$(git rev-parse HEAD) &&
|
||||||
|
test_commit two &&
|
||||||
|
head=$(git rev-parse HEAD) &&
|
||||||
|
git update-ref refs/heads/ref/foo $head &&
|
||||||
|
|
||||||
|
format_command $type "update refs/heads/foo" "$old_head" "" >stdin &&
|
||||||
|
format_command $type "update refs/heads/ref" "$old_head" "" >>stdin &&
|
||||||
|
git update-ref $type --stdin --batch-updates <stdin >stdout &&
|
||||||
|
echo $old_head >expect &&
|
||||||
|
git rev-parse refs/heads/foo >actual &&
|
||||||
|
test_cmp expect actual &&
|
||||||
|
test_grep -q "refname conflict" stdout
|
||||||
|
)
|
||||||
|
'
|
||||||
done
|
done
|
||||||
|
|
||||||
test_expect_success 'update-ref should also create reflog for HEAD' '
|
test_expect_success 'update-ref should also create reflog for HEAD' '
|
||||||
|
|
Loading…
Reference in New Issue