refs: introduce function to batch refname availability checks

The `refs_verify_refname_available()` functions checks whether a
reference update can be committed or whether it would conflict with
either a prefix or suffix thereof. This function needs to be called once
per reference that one wants to check, which requires us to redo a
couple of checks every time the function is called.

Introduce a new function `refs_verify_refnames_available()` that does
the same, but for a list of references. For now, the new function uses
the exact same implementation, except that we loop through all refnames
provided by the caller. This will be tuned in subsequent commits.

The existing `refs_verify_refname_available()` function is reimplemented
on top of the new function. As such, the diff is best viewed with the
`--ignore-space-change option`.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Patrick Steinhardt
2025-03-12 16:56:10 +01:00
committed by Junio C Hamano
parent 3c20bf0c85
commit 2ff58dec49
2 changed files with 111 additions and 73 deletions

172
refs.c
View File

@@ -2475,19 +2475,16 @@ int ref_transaction_commit(struct ref_transaction *transaction,
return ret; return ret;
} }
int refs_verify_refname_available(struct ref_store *refs, int refs_verify_refnames_available(struct ref_store *refs,
const char *refname, 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, unsigned int initial_transaction,
struct strbuf *err) struct strbuf *err)
{ {
const char *slash;
const char *extra_refname;
struct strbuf dirname = STRBUF_INIT; struct strbuf dirname = STRBUF_INIT;
struct strbuf referent = STRBUF_INIT; struct strbuf referent = STRBUF_INIT;
struct object_id oid; struct string_list_item *item;
unsigned int type;
int ret = -1; int ret = -1;
/* /*
@@ -2497,79 +2494,91 @@ int refs_verify_refname_available(struct ref_store *refs,
assert(err); assert(err);
strbuf_grow(&dirname, strlen(refname) + 1); for_each_string_list_item(item, refnames) {
for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) { const char *refname = item->string;
/* const char *extra_refname;
* Just saying "Is a directory" when we e.g. can't struct object_id oid;
* lock some multi-level ref isn't very informative, unsigned int type;
* the user won't be told *what* is a directory, so const char *slash;
* let's not use strerror() below.
*/
int ignore_errno;
/* Expand dirname to the new prefix, not including the trailing slash: */
strbuf_add(&dirname, refname + dirname.len, slash - refname - dirname.len);
/* strbuf_reset(&dirname);
* We are still at a leading dir of the refname (e.g.,
* "refs/foo"; if there is a reference with that name,
* it is a conflict, *unless* it is in skip.
*/
if (skip && string_list_has_string(skip, dirname.buf))
continue;
if (!initial_transaction && for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) {
!refs_read_raw_ref(refs, dirname.buf, &oid, &referent, /*
&type, &ignore_errno)) { * Just saying "Is a directory" when we e.g. can't
strbuf_addf(err, _("'%s' exists; cannot create '%s'"), * lock some multi-level ref isn't very informative,
dirname.buf, refname); * the user won't be told *what* is a directory, so
goto cleanup; * let's not use strerror() below.
} */
int ignore_errno;
if (extras && string_list_has_string(extras, dirname.buf)) { /* Expand dirname to the new prefix, not including the trailing slash: */
strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"), strbuf_add(&dirname, refname + dirname.len, slash - refname - dirname.len);
refname, dirname.buf);
goto cleanup;
}
}
/* /*
* We are at the leaf of our refname (e.g., "refs/foo/bar"). * We are still at a leading dir of the refname (e.g.,
* There is no point in searching for a reference with that * "refs/foo"; if there is a reference with that name,
* name, because a refname isn't considered to conflict with * it is a conflict, *unless* it is in skip.
* itself. But we still need to check for references whose */
* names are in the "refs/foo/bar/" namespace, because they if (skip && string_list_has_string(skip, dirname.buf))
* *do* conflict.
*/
strbuf_addstr(&dirname, refname + dirname.len);
strbuf_addch(&dirname, '/');
if (!initial_transaction) {
struct ref_iterator *iter;
int ok;
iter = refs_ref_iterator_begin(refs, dirname.buf, NULL, 0,
DO_FOR_EACH_INCLUDE_BROKEN);
while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
if (skip &&
string_list_has_string(skip, iter->refname))
continue; continue;
strbuf_addf(err, _("'%s' exists; cannot create '%s'"), if (!initial_transaction &&
iter->refname, refname); !refs_read_raw_ref(refs, dirname.buf, &oid, &referent,
ref_iterator_abort(iter); &type, &ignore_errno)) {
goto cleanup; strbuf_addf(err, _("'%s' exists; cannot create '%s'"),
dirname.buf, refname);
goto cleanup;
}
if (extras && string_list_has_string(extras, dirname.buf)) {
strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"),
refname, dirname.buf);
goto cleanup;
}
} }
if (ok != ITER_DONE) /*
BUG("error while iterating over references"); * We are at the leaf of our refname (e.g., "refs/foo/bar").
* There is no point in searching for a reference with that
* name, because a refname isn't considered to conflict with
* itself. But we still need to check for references whose
* names are in the "refs/foo/bar/" namespace, because they
* *do* conflict.
*/
strbuf_addstr(&dirname, refname + dirname.len);
strbuf_addch(&dirname, '/');
if (!initial_transaction) {
struct ref_iterator *iter;
int ok;
iter = refs_ref_iterator_begin(refs, dirname.buf, NULL, 0,
DO_FOR_EACH_INCLUDE_BROKEN);
while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
if (skip &&
string_list_has_string(skip, iter->refname))
continue;
strbuf_addf(err, _("'%s' exists; cannot create '%s'"),
iter->refname, refname);
ref_iterator_abort(iter);
goto cleanup;
}
if (ok != ITER_DONE)
BUG("error while iterating over references");
}
extra_refname = find_descendant_ref(dirname.buf, extras, skip);
if (extra_refname) {
strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"),
refname, extra_refname);
goto cleanup;
}
} }
extra_refname = find_descendant_ref(dirname.buf, extras, skip); ret = 0;
if (extra_refname)
strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"),
refname, extra_refname);
else
ret = 0;
cleanup: cleanup:
strbuf_release(&referent); strbuf_release(&referent);
@@ -2577,6 +2586,23 @@ cleanup:
return ret; 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)
{
struct string_list_item item = { .string = (char *) refname };
struct string_list refnames = {
.items = &item,
.nr = 1,
};
return refs_verify_refnames_available(refs, &refnames, extras, skip,
initial_transaction, err);
}
struct do_for_each_reflog_help { struct do_for_each_reflog_help {
each_reflog_fn *fn; each_reflog_fn *fn;
void *cb_data; void *cb_data;

12
refs.h
View File

@@ -124,6 +124,18 @@ int refs_verify_refname_available(struct ref_store *refs,
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);
int should_autocreate_reflog(enum log_refs_config log_all_ref_updates, int should_autocreate_reflog(enum log_refs_config log_all_ref_updates,