aboutsummaryrefslogtreecommitdiffstats
path: root/refs.c
diff options
context:
space:
mode:
Diffstat (limited to 'refs.c')
-rw-r--r--refs.c192
1 files changed, 155 insertions, 37 deletions
diff --git a/refs.c b/refs.c
index f0fe77bd7c..6559db3789 100644
--- a/refs.c
+++ b/refs.c
@@ -19,7 +19,7 @@
#include "run-command.h"
#include "hook.h"
#include "object-name.h"
-#include "object-store-ll.h"
+#include "object-store.h"
#include "object.h"
#include "path.h"
#include "submodule.h"
@@ -664,7 +664,8 @@ char *repo_default_branch_name(struct repository *r, int quiet)
if (!ret) {
ret = xstrdup("master");
if (!quiet)
- advise(_(default_branch_name_advice), ret);
+ advise_if_enabled(ADVICE_DEFAULT_BRANCH_NAME,
+ _(default_branch_name_advice), ret);
}
full_ref = xstrfmt("refs/heads/%s", ret);
@@ -1175,6 +1176,11 @@ struct ref_transaction *ref_store_transaction_begin(struct ref_store *refs,
CALLOC_ARRAY(tr, 1);
tr->ref_store = refs;
tr->flags = flags;
+ string_list_init_dup(&tr->refnames);
+
+ if (flags & REF_TRANSACTION_ALLOW_FAILURE)
+ CALLOC_ARRAY(tr->rejections, 1);
+
return tr;
}
@@ -1205,10 +1211,45 @@ void ref_transaction_free(struct ref_transaction *transaction)
free((char *)transaction->updates[i]->old_target);
free(transaction->updates[i]);
}
+
+ if (transaction->rejections)
+ free(transaction->rejections->update_indices);
+ free(transaction->rejections);
+
+ string_list_clear(&transaction->refnames, 0);
free(transaction->updates);
free(transaction);
}
+int ref_transaction_maybe_set_rejected(struct ref_transaction *transaction,
+ size_t update_idx,
+ enum ref_transaction_error err)
+{
+ if (update_idx >= transaction->nr)
+ BUG("trying to set rejection on invalid update index");
+
+ if (!(transaction->flags & REF_TRANSACTION_ALLOW_FAILURE))
+ return 0;
+
+ if (!transaction->rejections)
+ BUG("transaction not inititalized with failure support");
+
+ /*
+ * Don't accept generic errors, since these errors are not user
+ * input related.
+ */
+ if (err == REF_TRANSACTION_ERROR_GENERIC)
+ return 0;
+
+ transaction->updates[update_idx]->rejection_err = err;
+ ALLOC_GROW(transaction->rejections->update_indices,
+ transaction->rejections->nr + 1,
+ transaction->rejections->alloc);
+ transaction->rejections->update_indices[transaction->rejections->nr++] = update_idx;
+
+ return 1;
+}
+
struct ref_update *ref_transaction_add_update(
struct ref_transaction *transaction,
const char *refname, unsigned int flags,
@@ -1218,6 +1259,7 @@ struct ref_update *ref_transaction_add_update(
const char *committer_info,
const char *msg)
{
+ struct string_list_item *item;
struct ref_update *update;
if (transaction->state != REF_TRANSACTION_OPEN)
@@ -1233,6 +1275,7 @@ struct ref_update *ref_transaction_add_update(
transaction->updates[transaction->nr++] = update;
update->flags = flags;
+ update->rejection_err = 0;
update->new_target = xstrdup_or_null(new_target);
update->old_target = xstrdup_or_null(old_target);
@@ -1245,6 +1288,16 @@ struct ref_update *ref_transaction_add_update(
update->msg = normalize_reflog_message(msg);
}
+ /*
+ * This list is generally used by the backends to avoid duplicates.
+ * But we do support multiple log updates for a given refname within
+ * a single transaction.
+ */
+ if (!(update->flags & REF_LOG_ONLY)) {
+ item = string_list_append(&transaction->refnames, refname);
+ item->util = update;
+ }
+
return update;
}
@@ -1377,7 +1430,7 @@ int ref_transaction_create(struct ref_transaction *transaction,
return 1;
}
return ref_transaction_update(transaction, refname, new_oid,
- null_oid(), new_target, NULL, flags,
+ null_oid(the_hash_algo), new_target, NULL, flags,
msg, err);
}
@@ -1396,7 +1449,7 @@ int ref_transaction_delete(struct ref_transaction *transaction,
if (old_target && !(flags & REF_NO_DEREF))
BUG("delete cannot operate on symrefs with deref mode");
return ref_transaction_update(transaction, refname,
- null_oid(), old_oid,
+ null_oid(the_hash_algo), old_oid,
NULL, old_target, flags,
msg, err);
}
@@ -2180,7 +2233,7 @@ struct ref_store *repo_get_submodule_ref_store(struct repository *repo,
subrepo = xmalloc(sizeof(*subrepo));
if (repo_submodule_init(subrepo, repo, submodule,
- null_oid())) {
+ null_oid(the_hash_algo))) {
free(subrepo);
goto done;
}
@@ -2278,7 +2331,7 @@ int refs_update_symref_extended(struct ref_store *refs, const char *ref,
REF_NO_DEREF, logmsg, &err))
goto error_return;
prepret = ref_transaction_prepare(transaction, &err);
- if (prepret && prepret != TRANSACTION_CREATE_EXISTS)
+ if (prepret && prepret != REF_TRANSACTION_ERROR_CREATE_EXISTS)
goto error_return;
} else {
if (ref_transaction_update(transaction, ref, NULL, NULL,
@@ -2296,7 +2349,7 @@ int refs_update_symref_extended(struct ref_store *refs, const char *ref,
}
}
- if (prepret == TRANSACTION_CREATE_EXISTS)
+ if (prepret == REF_TRANSACTION_ERROR_CREATE_EXISTS)
goto cleanup;
if (ref_transaction_commit(transaction, &err))
@@ -2310,8 +2363,13 @@ cleanup:
return ret;
}
-int ref_update_reject_duplicates(struct string_list *refnames,
- struct strbuf *err)
+/*
+ * Write an error to `err` and return a nonzero value iff the same
+ * refname appears multiple times in `refnames`. `refnames` must be
+ * sorted on entry to this function.
+ */
+static int ref_update_reject_duplicates(struct string_list *refnames,
+ struct strbuf *err)
{
size_t i, n = refnames->nr;
@@ -2365,14 +2423,14 @@ static int run_transaction_hook(struct ref_transaction *transaction,
strbuf_reset(&buf);
if (!(update->flags & REF_HAVE_OLD))
- strbuf_addf(&buf, "%s ", oid_to_hex(null_oid()));
+ strbuf_addf(&buf, "%s ", oid_to_hex(null_oid(the_hash_algo)));
else if (update->old_target)
strbuf_addf(&buf, "ref:%s ", update->old_target);
else
strbuf_addf(&buf, "%s ", oid_to_hex(&update->old_oid));
if (!(update->flags & REF_HAVE_NEW))
- strbuf_addf(&buf, "%s ", oid_to_hex(null_oid()));
+ strbuf_addf(&buf, "%s ", oid_to_hex(null_oid(the_hash_algo)));
else if (update->new_target)
strbuf_addf(&buf, "ref:%s ", update->new_target);
else
@@ -2425,6 +2483,10 @@ int ref_transaction_prepare(struct ref_transaction *transaction,
return -1;
}
+ string_list_sort(&transaction->refnames);
+ if (ref_update_reject_duplicates(&transaction->refnames, err))
+ return REF_TRANSACTION_ERROR_GENERIC;
+
ret = refs->be->transaction_prepare(refs, transaction, err);
if (ret)
return ret;
@@ -2495,19 +2557,21 @@ int ref_transaction_commit(struct ref_transaction *transaction,
return ret;
}
-int refs_verify_refnames_available(struct ref_store *refs,
- const struct string_list *refnames,
- const struct string_list *extras,
- const struct string_list *skip,
- unsigned int initial_transaction,
- struct strbuf *err)
+enum ref_transaction_error refs_verify_refnames_available(struct ref_store *refs,
+ const struct string_list *refnames,
+ const struct string_list *extras,
+ const struct string_list *skip,
+ struct ref_transaction *transaction,
+ unsigned int initial_transaction,
+ struct strbuf *err)
{
struct strbuf dirname = STRBUF_INIT;
struct strbuf referent = STRBUF_INIT;
struct string_list_item *item;
struct ref_iterator *iter = NULL;
+ struct strset conflicting_dirnames;
struct strset dirnames;
- int ret = -1;
+ int ret = REF_TRANSACTION_ERROR_NAME_CONFLICT;
/*
* For the sake of comments in this function, suppose that
@@ -2516,9 +2580,11 @@ int refs_verify_refnames_available(struct ref_store *refs,
assert(err);
+ strset_init(&conflicting_dirnames);
strset_init(&dirnames);
for_each_string_list_item(item, refnames) {
+ const size_t *update_idx = (size_t *)item->util;
const char *refname = item->string;
const char *extra_refname;
struct object_id oid;
@@ -2549,21 +2615,37 @@ int refs_verify_refnames_available(struct ref_store *refs,
/*
* If we've already seen the directory we don't need to
- * process it again. Skip it to avoid checking checking
- * common prefixes like "refs/heads/" repeatedly.
+ * process it again. Skip it to avoid checking common
+ * prefixes like "refs/heads/" repeatedly.
*/
if (!strset_add(&dirnames, dirname.buf))
continue;
if (!initial_transaction &&
- !refs_read_raw_ref(refs, dirname.buf, &oid, &referent,
- &type, &ignore_errno)) {
+ (strset_contains(&conflicting_dirnames, dirname.buf) ||
+ !refs_read_raw_ref(refs, dirname.buf, &oid, &referent,
+ &type, &ignore_errno))) {
+ if (transaction && ref_transaction_maybe_set_rejected(
+ transaction, *update_idx,
+ REF_TRANSACTION_ERROR_NAME_CONFLICT)) {
+ strset_remove(&dirnames, dirname.buf);
+ strset_add(&conflicting_dirnames, dirname.buf);
+ continue;
+ }
+
strbuf_addf(err, _("'%s' exists; cannot create '%s'"),
dirname.buf, refname);
goto cleanup;
}
if (extras && string_list_has_string(extras, dirname.buf)) {
+ if (transaction && ref_transaction_maybe_set_rejected(
+ transaction, *update_idx,
+ REF_TRANSACTION_ERROR_NAME_CONFLICT)) {
+ strset_remove(&dirnames, dirname.buf);
+ continue;
+ }
+
strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"),
refname, dirname.buf);
goto cleanup;
@@ -2596,6 +2678,11 @@ int refs_verify_refnames_available(struct ref_store *refs,
string_list_has_string(skip, iter->refname))
continue;
+ if (transaction && ref_transaction_maybe_set_rejected(
+ transaction, *update_idx,
+ REF_TRANSACTION_ERROR_NAME_CONFLICT))
+ continue;
+
strbuf_addf(err, _("'%s' exists; cannot create '%s'"),
iter->refname, refname);
goto cleanup;
@@ -2607,6 +2694,11 @@ int refs_verify_refnames_available(struct ref_store *refs,
extra_refname = find_descendant_ref(dirname.buf, extras, skip);
if (extra_refname) {
+ if (transaction && ref_transaction_maybe_set_rejected(
+ transaction, *update_idx,
+ REF_TRANSACTION_ERROR_NAME_CONFLICT))
+ continue;
+
strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"),
refname, extra_refname);
goto cleanup;
@@ -2618,17 +2710,19 @@ int refs_verify_refnames_available(struct ref_store *refs,
cleanup:
strbuf_release(&referent);
strbuf_release(&dirname);
+ strset_clear(&conflicting_dirnames);
strset_clear(&dirnames);
ref_iterator_free(iter);
return ret;
}
-int refs_verify_refname_available(struct ref_store *refs,
- const char *refname,
- const struct string_list *extras,
- const struct string_list *skip,
- unsigned int initial_transaction,
- struct strbuf *err)
+enum ref_transaction_error refs_verify_refname_available(
+ struct ref_store *refs,
+ const char *refname,
+ const struct string_list *extras,
+ const struct string_list *skip,
+ unsigned int initial_transaction,
+ struct strbuf *err)
{
struct string_list_item item = { .string = (char *) refname };
struct string_list refnames = {
@@ -2637,7 +2731,7 @@ int refs_verify_refname_available(struct ref_store *refs,
};
return refs_verify_refnames_available(refs, &refnames, extras, skip,
- initial_transaction, err);
+ NULL, initial_transaction, err);
}
struct do_for_each_reflog_help {
@@ -2725,6 +2819,28 @@ void ref_transaction_for_each_queued_update(struct ref_transaction *transaction,
}
}
+void ref_transaction_for_each_rejected_update(struct ref_transaction *transaction,
+ ref_transaction_for_each_rejected_update_fn cb,
+ void *cb_data)
+{
+ if (!transaction->rejections)
+ return;
+
+ for (size_t i = 0; i < transaction->rejections->nr; i++) {
+ size_t update_index = transaction->rejections->update_indices[i];
+ struct ref_update *update = transaction->updates[update_index];
+
+ if (!update->rejection_err)
+ continue;
+
+ cb(update->refname,
+ (update->flags & REF_HAVE_OLD) ? &update->old_oid : NULL,
+ (update->flags & REF_HAVE_NEW) ? &update->new_oid : NULL,
+ update->old_target, update->new_target,
+ update->rejection_err, cb_data);
+ }
+}
+
int refs_delete_refs(struct ref_store *refs, const char *logmsg,
struct string_list *refnames, unsigned int flags)
{
@@ -2816,8 +2932,9 @@ int ref_update_has_null_new_value(struct ref_update *update)
return !update->new_target && is_null_oid(&update->new_oid);
}
-int ref_update_check_old_target(const char *referent, struct ref_update *update,
- struct strbuf *err)
+enum ref_transaction_error ref_update_check_old_target(const char *referent,
+ struct ref_update *update,
+ struct strbuf *err)
{
if (!update->old_target)
BUG("called without old_target set");
@@ -2825,17 +2942,18 @@ int ref_update_check_old_target(const char *referent, struct ref_update *update,
if (!strcmp(referent, update->old_target))
return 0;
- if (!strcmp(referent, ""))
+ if (!strcmp(referent, "")) {
strbuf_addf(err, "verifying symref target: '%s': "
"reference is missing but expected %s",
ref_update_original_update_refname(update),
update->old_target);
- else
- strbuf_addf(err, "verifying symref target: '%s': "
- "is at %s but expected %s",
+ return REF_TRANSACTION_ERROR_NONEXISTENT_REF;
+ }
+
+ strbuf_addf(err, "verifying symref target: '%s': is at %s but expected %s",
ref_update_original_update_refname(update),
referent, update->old_target);
- return -1;
+ return REF_TRANSACTION_ERROR_INCORRECT_OLD_VALUE;
}
struct migration_data {
@@ -2857,7 +2975,7 @@ static int migrate_one_ref(const char *refname, const char *referent UNUSED, con
if (ret < 0)
goto done;
- ret = ref_transaction_update(data->transaction, refname, NULL, null_oid(),
+ ret = ref_transaction_update(data->transaction, refname, NULL, null_oid(the_hash_algo),
symref_target.buf, NULL,
REF_SKIP_CREATE_REFLOG | REF_NO_DEREF, NULL, data->errbuf);
if (ret < 0)