Merge branch 'ps/refname-avail-check-optim'

The code paths to check whether a refname X is available (by seeing
if another ref X/Y exists, etc.) have been optimized.

* ps/refname-avail-check-optim:
  refs: reuse iterators when determining refname availability
  refs/iterator: implement seeking for files iterators
  refs/iterator: implement seeking for packed-ref iterators
  refs/iterator: implement seeking for ref-cache iterators
  refs/iterator: implement seeking for reftable iterators
  refs/iterator: implement seeking for merged iterators
  refs/iterator: provide infrastructure to re-seek iterators
  refs/iterator: separate lifecycle from iteration
  refs: stop re-verifying common prefixes for availability
  refs/files: batch refname availability checks for initial transactions
  refs/files: batch refname availability checks for normal transactions
  refs/reftable: batch refname availability checks
  refs: introduce function to batch refname availability checks
  builtin/update-ref: skip ambiguity checks when parsing object IDs
  object-name: allow skipping ambiguity checks in `get_oid()` family
  object-name: introduce `repo_get_oid_with_flags()`
main
Junio C Hamano 2025-03-29 16:39:07 +09:00
commit 8d6413a1be
18 changed files with 545 additions and 362 deletions

View File

@ -342,6 +342,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest,
strbuf_setlen(src, src_len);
die(_("failed to iterate over '%s'"), src->buf);
}

dir_iterator_free(iter);
}

static void clone_local(const char *src_repo, const char *dest_repo)

View File

@ -179,7 +179,8 @@ static int parse_next_oid(const char **next, const char *end,
(*next)++;
*next = parse_arg(*next, &arg);
if (arg.len) {
if (repo_get_oid(the_repository, arg.buf, oid))
if (repo_get_oid_with_flags(the_repository, arg.buf, oid,
GET_OID_SKIP_AMBIGUITY_CHECK))
goto invalid;
} else {
/* Without -z, an empty value means all zeros: */
@ -197,7 +198,8 @@ static int parse_next_oid(const char **next, const char *end,
*next += arg.len;

if (arg.len) {
if (repo_get_oid(the_repository, arg.buf, oid))
if (repo_get_oid_with_flags(the_repository, arg.buf, oid,
GET_OID_SKIP_AMBIGUITY_CHECK))
goto invalid;
} else if (flags & PARSE_SHA1_ALLOW_EMPTY) {
/* With -z, treat an empty value as all zeros: */
@ -299,7 +301,8 @@ static void parse_cmd_symref_update(struct ref_transaction *transaction,
die("symref-update %s: expected old value", refname);

if (!strcmp(old_arg, "oid")) {
if (repo_get_oid(the_repository, old_target, &old_oid))
if (repo_get_oid_with_flags(the_repository, old_target, &old_oid,
GET_OID_SKIP_AMBIGUITY_CHECK))
die("symref-update %s: invalid oid: %s", refname, old_target);

have_old_oid = 1;
@ -772,7 +775,8 @@ int cmd_update_ref(int argc,
refname = argv[0];
value = argv[1];
oldval = argv[2];
if (repo_get_oid(the_repository, value, &oid))
if (repo_get_oid_with_flags(the_repository, value, &oid,
GET_OID_SKIP_AMBIGUITY_CHECK))
die("%s: not a valid SHA1", value);
}

@ -783,7 +787,8 @@ int cmd_update_ref(int argc,
* must not already exist:
*/
oidclr(&oldoid, the_repository->hash_algo);
else if (repo_get_oid(the_repository, oldval, &oldoid))
else if (repo_get_oid_with_flags(the_repository, oldval, &oldoid,
GET_OID_SKIP_AMBIGUITY_CHECK))
die("%s: not a valid old SHA1", oldval);
}


View File

@ -193,9 +193,9 @@ int dir_iterator_advance(struct dir_iterator *dir_iterator)

if (S_ISDIR(iter->base.st.st_mode) && push_level(iter)) {
if (errno != ENOENT && iter->flags & DIR_ITERATOR_PEDANTIC)
goto error_out;
return ITER_ERROR;
if (iter->levels_nr == 0)
goto error_out;
return ITER_ERROR;
}

/* Loop until we find an entry that we can give back to the caller. */
@ -211,11 +211,11 @@ int dir_iterator_advance(struct dir_iterator *dir_iterator)
int ret = next_directory_entry(level->dir, iter->base.path.buf, &de);
if (ret < 0) {
if (iter->flags & DIR_ITERATOR_PEDANTIC)
goto error_out;
return ITER_ERROR;
continue;
} else if (ret > 0) {
if (pop_level(iter) == 0)
return dir_iterator_abort(dir_iterator);
return ITER_DONE;
continue;
}

@ -223,7 +223,7 @@ int dir_iterator_advance(struct dir_iterator *dir_iterator)
} else {
if (level->entries_idx >= level->entries.nr) {
if (pop_level(iter) == 0)
return dir_iterator_abort(dir_iterator);
return ITER_DONE;
continue;
}

@ -232,22 +232,21 @@ int dir_iterator_advance(struct dir_iterator *dir_iterator)

if (prepare_next_entry_data(iter, name)) {
if (errno != ENOENT && iter->flags & DIR_ITERATOR_PEDANTIC)
goto error_out;
return ITER_ERROR;
continue;
}

return ITER_OK;
}

error_out:
dir_iterator_abort(dir_iterator);
return ITER_ERROR;
}

int dir_iterator_abort(struct dir_iterator *dir_iterator)
void dir_iterator_free(struct dir_iterator *dir_iterator)
{
struct dir_iterator_int *iter = (struct dir_iterator_int *)dir_iterator;

if (!iter)
return;

for (; iter->levels_nr; iter->levels_nr--) {
struct dir_iterator_level *level =
&iter->levels[iter->levels_nr - 1];
@ -266,7 +265,6 @@ int dir_iterator_abort(struct dir_iterator *dir_iterator)
free(iter->levels);
strbuf_release(&iter->base.path);
free(iter);
return ITER_DONE;
}

struct dir_iterator *dir_iterator_begin(const char *path, unsigned int flags)
@ -301,7 +299,7 @@ struct dir_iterator *dir_iterator_begin(const char *path, unsigned int flags)
return dir_iterator;

error_out:
dir_iterator_abort(dir_iterator);
dir_iterator_free(dir_iterator);
errno = saved_errno;
return NULL;
}

View File

@ -28,7 +28,7 @@
*
* while ((ok = dir_iterator_advance(iter)) == ITER_OK) {
* if (want_to_stop_iteration()) {
* ok = dir_iterator_abort(iter);
* ok = ITER_DONE;
* break;
* }
*
@ -39,6 +39,7 @@
*
* if (ok != ITER_DONE)
* handle_error();
* dir_iterator_free(iter);
*
* Callers are allowed to modify iter->path while they are working,
* but they must restore it to its original contents before calling
@ -107,11 +108,7 @@ struct dir_iterator *dir_iterator_begin(const char *path, unsigned int flags);
*/
int dir_iterator_advance(struct dir_iterator *iterator);

/*
* End the iteration before it has been exhausted. Free the
* dir_iterator and any associated resources and return ITER_DONE. On
* error, free the dir_iterator and return ITER_ERROR.
*/
int dir_iterator_abort(struct dir_iterator *iterator);
/* Free the dir_iterator and any associated resources. */
void dir_iterator_free(struct dir_iterator *iterator);

#endif

23
hash.h
View File

@ -193,17 +193,18 @@ struct object_id {
int algo; /* XXX requires 4-byte alignment */
};

#define GET_OID_QUIETLY 01
#define GET_OID_COMMIT 02
#define GET_OID_COMMITTISH 04
#define GET_OID_TREE 010
#define GET_OID_TREEISH 020
#define GET_OID_BLOB 040
#define GET_OID_FOLLOW_SYMLINKS 0100
#define GET_OID_RECORD_PATH 0200
#define GET_OID_ONLY_TO_DIE 04000
#define GET_OID_REQUIRE_PATH 010000
#define GET_OID_HASH_ANY 020000
#define GET_OID_QUIETLY 01
#define GET_OID_COMMIT 02
#define GET_OID_COMMITTISH 04
#define GET_OID_TREE 010
#define GET_OID_TREEISH 020
#define GET_OID_BLOB 040
#define GET_OID_FOLLOW_SYMLINKS 0100
#define GET_OID_RECORD_PATH 0200
#define GET_OID_ONLY_TO_DIE 04000
#define GET_OID_REQUIRE_PATH 010000
#define GET_OID_HASH_ANY 020000
#define GET_OID_SKIP_AMBIGUITY_CHECK 040000

#define GET_OID_DISAMBIGUATORS \
(GET_OID_COMMIT | GET_OID_COMMITTISH | \

View File

@ -12,7 +12,7 @@
#define ITER_OK 0

/*
* The iterator is exhausted and has been freed.
* The iterator is exhausted.
*/
#define ITER_DONE -1


View File

@ -961,7 +961,9 @@ static int get_oid_basic(struct repository *r, const char *str, int len,
int fatal = !(flags & GET_OID_QUIETLY);

if (len == r->hash_algo->hexsz && !get_oid_hex(str, oid)) {
if (repo_settings_get_warn_ambiguous_refs(r) && warn_on_object_refname_ambiguity) {
if (!(flags & GET_OID_SKIP_AMBIGUITY_CHECK) &&
repo_settings_get_warn_ambiguous_refs(r) &&
warn_on_object_refname_ambiguity) {
refs_found = repo_dwim_ref(r, str, len, &tmp_oid, &real_ref, 0);
if (refs_found > 0) {
warning(warn_msg, len, str);
@ -1794,18 +1796,20 @@ void object_context_release(struct object_context *ctx)
strbuf_release(&ctx->symlink_path);
}

/*
* This is like "get_oid_basic()", except it allows "object ID expressions",
* notably "xyz^" for "parent of xyz"
*/
int repo_get_oid(struct repository *r, const char *name, struct object_id *oid)
int repo_get_oid_with_flags(struct repository *r, const char *name,
struct object_id *oid, unsigned flags)
{
struct object_context unused;
int ret = get_oid_with_context(r, name, 0, oid, &unused);
int ret = get_oid_with_context(r, name, flags, oid, &unused);
object_context_release(&unused);
return ret;
}

int repo_get_oid(struct repository *r, const char *name, struct object_id *oid)
{
return repo_get_oid_with_flags(r, name, oid, 0);
}

/*
* This returns a non-zero value if the string (built using printf
* format and the given arguments) is not a valid object.

View File

@ -51,6 +51,12 @@ void strbuf_repo_add_unique_abbrev(struct strbuf *sb, struct repository *repo,
void strbuf_add_unique_abbrev(struct strbuf *sb, const struct object_id *oid,
int abbrev_len);

/*
* This is like "get_oid_basic()", except it allows "object ID expressions",
* notably "xyz^" for "parent of xyz". Accepts GET_OID_* flags.
*/
int repo_get_oid_with_flags(struct repository *r, const char *str,
struct object_id *oid, unsigned flags);
int repo_get_oid(struct repository *r, const char *str, struct object_id *oid);
__attribute__((format (printf, 2, 3)))
int get_oidf(struct object_id *oid, const char *fmt, ...);

187
refs.c
View File

@ -2495,19 +2495,18 @@ int ref_transaction_commit(struct ref_transaction *transaction,
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)
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)
{
const char *slash;
const char *extra_refname;
struct strbuf dirname = STRBUF_INIT;
struct strbuf referent = STRBUF_INIT;
struct object_id oid;
unsigned int type;
struct string_list_item *item;
struct ref_iterator *iter = NULL;
struct strset dirnames;
int ret = -1;

/*
@ -2517,86 +2516,130 @@ int refs_verify_refname_available(struct ref_store *refs,

assert(err);

strbuf_grow(&dirname, strlen(refname) + 1);
for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) {
/*
* Just saying "Is a directory" when we e.g. can't
* lock some multi-level ref isn't very informative,
* the user won't be told *what* is a directory, so
* 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);
strset_init(&dirnames);

/*
* 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;
for_each_string_list_item(item, refnames) {
const char *refname = item->string;
const char *extra_refname;
struct object_id oid;
unsigned int type;
const char *slash;

if (!initial_transaction &&
!refs_read_raw_ref(refs, dirname.buf, &oid, &referent,
&type, &ignore_errno)) {
strbuf_addf(err, _("'%s' exists; cannot create '%s'"),
dirname.buf, refname);
goto cleanup;
}
strbuf_reset(&dirname);

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;
}
}
for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) {
/*
* Just saying "Is a directory" when we e.g. can't
* lock some multi-level ref isn't very informative,
* the user won't be told *what* is a directory, so
* let's not use strerror() below.
*/
int ignore_errno;

/*
* 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, '/');
/* Expand dirname to the new prefix, not including the trailing slash: */
strbuf_add(&dirname, refname + dirname.len, slash - refname - dirname.len);

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))
/*
* 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;

strbuf_addf(err, _("'%s' exists; cannot create '%s'"),
iter->refname, refname);
ref_iterator_abort(iter);
goto cleanup;
/*
* 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.
*/
if (!strset_add(&dirnames, dirname.buf))
continue;

if (!initial_transaction &&
!refs_read_raw_ref(refs, dirname.buf, &oid, &referent,
&type, &ignore_errno)) {
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) {
int ok;

if (!iter) {
iter = refs_ref_iterator_begin(refs, dirname.buf, NULL, 0,
DO_FOR_EACH_INCLUDE_BROKEN);
} else if (ref_iterator_seek(iter, dirname.buf) < 0) {
goto cleanup;
}

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);
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);
if (extra_refname)
strbuf_addf(err, _("cannot process '%s' and '%s' at the same time"),
refname, extra_refname);
else
ret = 0;
ret = 0;

cleanup:
strbuf_release(&referent);
strbuf_release(&dirname);
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)
{
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 {
each_reflog_fn *fn;
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,
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 should_autocreate_reflog(enum log_refs_config log_all_ref_updates,

View File

@ -169,6 +169,16 @@ static int debug_ref_iterator_advance(struct ref_iterator *ref_iterator)
return res;
}

static int debug_ref_iterator_seek(struct ref_iterator *ref_iterator,
const char *prefix)
{
struct debug_ref_iterator *diter =
(struct debug_ref_iterator *)ref_iterator;
int res = diter->iter->vtable->seek(diter->iter, prefix);
trace_printf_key(&trace_refs, "iterator_seek: %s: %d\n", prefix ? prefix : "", res);
return res;
}

static int debug_ref_iterator_peel(struct ref_iterator *ref_iterator,
struct object_id *peeled)
{
@ -179,19 +189,19 @@ static int debug_ref_iterator_peel(struct ref_iterator *ref_iterator,
return res;
}

static int debug_ref_iterator_abort(struct ref_iterator *ref_iterator)
static void debug_ref_iterator_release(struct ref_iterator *ref_iterator)
{
struct debug_ref_iterator *diter =
(struct debug_ref_iterator *)ref_iterator;
int res = diter->iter->vtable->abort(diter->iter);
trace_printf_key(&trace_refs, "iterator_abort: %d\n", res);
return res;
diter->iter->vtable->release(diter->iter);
trace_printf_key(&trace_refs, "iterator_abort\n");
}

static struct ref_iterator_vtable debug_ref_iterator_vtable = {
.advance = debug_ref_iterator_advance,
.seek = debug_ref_iterator_seek,
.peel = debug_ref_iterator_peel,
.abort = debug_ref_iterator_abort,
.release = debug_ref_iterator_release,
};

static struct ref_iterator *

View File

@ -678,6 +678,7 @@ static void unlock_ref(struct ref_lock *lock)
*/
static int lock_raw_ref(struct files_ref_store *refs,
const char *refname, int mustexist,
struct string_list *refnames_to_check,
const struct string_list *extras,
struct ref_lock **lock_p,
struct strbuf *referent,
@ -855,16 +856,11 @@ retry:
}

/*
* If the ref did not exist and we are creating it,
* make sure there is no existing packed ref that
* conflicts with refname:
* If the ref did not exist and we are creating it, we have to
* make sure there is no existing packed ref that conflicts
* with refname. This check is deferred so that we can batch it.
*/
if (refs_verify_refname_available(
refs->packed_ref_store, refname,
extras, NULL, 0, err)) {
ret = TRANSACTION_NAME_CONFLICT;
goto error_return;
}
string_list_append(refnames_to_check, refname);
}

ret = 0;
@ -919,13 +915,17 @@ static int files_ref_iterator_advance(struct ref_iterator *ref_iterator)
return ITER_OK;
}

iter->iter0 = NULL;
if (ref_iterator_abort(ref_iterator) != ITER_DONE)
ok = ITER_ERROR;

return ok;
}

static int files_ref_iterator_seek(struct ref_iterator *ref_iterator,
const char *prefix)
{
struct files_ref_iterator *iter =
(struct files_ref_iterator *)ref_iterator;
return ref_iterator_seek(iter->iter0, prefix);
}

static int files_ref_iterator_peel(struct ref_iterator *ref_iterator,
struct object_id *peeled)
{
@ -935,23 +935,18 @@ static int files_ref_iterator_peel(struct ref_iterator *ref_iterator,
return ref_iterator_peel(iter->iter0, peeled);
}

static int files_ref_iterator_abort(struct ref_iterator *ref_iterator)
static void files_ref_iterator_release(struct ref_iterator *ref_iterator)
{
struct files_ref_iterator *iter =
(struct files_ref_iterator *)ref_iterator;
int ok = ITER_DONE;

if (iter->iter0)
ok = ref_iterator_abort(iter->iter0);

base_ref_iterator_free(ref_iterator);
return ok;
ref_iterator_free(iter->iter0);
}

static struct ref_iterator_vtable files_ref_iterator_vtable = {
.advance = files_ref_iterator_advance,
.seek = files_ref_iterator_seek,
.peel = files_ref_iterator_peel,
.abort = files_ref_iterator_abort,
.release = files_ref_iterator_release,
};

static struct ref_iterator *files_ref_iterator_begin(
@ -1382,7 +1377,7 @@ static int should_pack_refs(struct files_ref_store *refs,
iter->flags, opts))
refcount++;
if (refcount >= limit) {
ref_iterator_abort(iter);
ref_iterator_free(iter);
return 1;
}
}
@ -1390,6 +1385,7 @@ static int should_pack_refs(struct files_ref_store *refs,
if (ret != ITER_DONE)
die("error while iterating over references");

ref_iterator_free(iter);
return 0;
}

@ -1456,6 +1452,7 @@ static int files_pack_refs(struct ref_store *ref_store,
packed_refs_unlock(refs->packed_ref_store);

prune_refs(refs, &refs_to_prune);
ref_iterator_free(iter);
strbuf_release(&err);
return 0;
}
@ -2303,35 +2300,33 @@ static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator)
return ITER_OK;
}

iter->dir_iterator = NULL;
if (ref_iterator_abort(ref_iterator) == ITER_ERROR)
ok = ITER_ERROR;
return ok;
}

static int files_reflog_iterator_seek(struct ref_iterator *ref_iterator UNUSED,
const char *prefix UNUSED)
{
BUG("ref_iterator_seek() called for reflog_iterator");
}

static int files_reflog_iterator_peel(struct ref_iterator *ref_iterator UNUSED,
struct object_id *peeled UNUSED)
{
BUG("ref_iterator_peel() called for reflog_iterator");
}

static int files_reflog_iterator_abort(struct ref_iterator *ref_iterator)
static void files_reflog_iterator_release(struct ref_iterator *ref_iterator)
{
struct files_reflog_iterator *iter =
(struct files_reflog_iterator *)ref_iterator;
int ok = ITER_DONE;

if (iter->dir_iterator)
ok = dir_iterator_abort(iter->dir_iterator);

base_ref_iterator_free(ref_iterator);
return ok;
dir_iterator_free(iter->dir_iterator);
}

static struct ref_iterator_vtable files_reflog_iterator_vtable = {
.advance = files_reflog_iterator_advance,
.seek = files_reflog_iterator_seek,
.peel = files_reflog_iterator_peel,
.abort = files_reflog_iterator_abort,
.release = files_reflog_iterator_release,
};

static struct ref_iterator *reflog_iterator_begin(struct ref_store *ref_store,
@ -2569,6 +2564,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
struct ref_update *update,
struct ref_transaction *transaction,
const char *head_ref,
struct string_list *refnames_to_check,
struct string_list *affected_refnames,
struct strbuf *err)
{
@ -2597,7 +2593,7 @@ static int lock_ref_for_update(struct files_ref_store *refs,
lock->count++;
} else {
ret = lock_raw_ref(refs, update->refname, mustexist,
affected_refnames,
refnames_to_check, affected_refnames,
&lock, &referent,
&update->type, err);
if (ret) {
@ -2811,6 +2807,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
size_t i;
int ret = 0;
struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
struct string_list refnames_to_check = STRING_LIST_INIT_NODUP;
char *head_ref = NULL;
int head_type;
struct files_transaction_backend_data *backend_data;
@ -2898,7 +2895,8 @@ static int files_transaction_prepare(struct ref_store *ref_store,
struct ref_update *update = transaction->updates[i];

ret = lock_ref_for_update(refs, update, transaction,
head_ref, &affected_refnames, err);
head_ref, &refnames_to_check,
&affected_refnames, err);
if (ret)
goto cleanup;

@ -2930,6 +2928,26 @@ static int files_transaction_prepare(struct ref_store *ref_store,
}
}

/*
* Verify that none of the loose reference that we're about to write
* conflict with any existing packed references. Ideally, we'd do this
* check after the packed-refs are locked so that the file cannot
* change underneath our feet. But introducing such a lock now would
* probably do more harm than good as users rely on there not being a
* global lock with the "files" backend.
*
* Another alternative would be to do the check after the (optional)
* lock, but that would extend the time we spend in the globally-locked
* state.
*
* So instead, we accept the race for now.
*/
if (refs_verify_refnames_available(refs->packed_ref_store, &refnames_to_check,
&affected_refnames, NULL, 0, err)) {
ret = TRANSACTION_NAME_CONFLICT;
goto cleanup;
}

if (packed_transaction) {
if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
ret = TRANSACTION_GENERIC_ERROR;
@ -2972,6 +2990,7 @@ static int files_transaction_prepare(struct ref_store *ref_store,
cleanup:
free(head_ref);
string_list_clear(&affected_refnames, 0);
string_list_clear(&refnames_to_check, 0);

if (ret)
files_transaction_cleanup(refs, transaction);
@ -3036,6 +3055,7 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
size_t i;
int ret = 0;
struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
struct string_list refnames_to_check = STRING_LIST_INIT_NODUP;
struct ref_transaction *packed_transaction = NULL;
struct ref_transaction *loose_transaction = NULL;

@ -3085,11 +3105,7 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
!is_null_oid(&update->old_oid))
BUG("initial ref transaction with old_sha1 set");

if (refs_verify_refname_available(&refs->base, update->refname,
&affected_refnames, NULL, 1, err)) {
ret = TRANSACTION_NAME_CONFLICT;
goto cleanup;
}
string_list_append(&refnames_to_check, update->refname);

/*
* packed-refs don't support symbolic refs, root refs and reflogs,
@ -3125,8 +3141,19 @@ static int files_transaction_finish_initial(struct files_ref_store *refs,
}
}

if (packed_refs_lock(refs->packed_ref_store, 0, err) ||
ref_transaction_commit(packed_transaction, err)) {
if (packed_refs_lock(refs->packed_ref_store, 0, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
}

if (refs_verify_refnames_available(&refs->base, &refnames_to_check,
&affected_refnames, NULL, 1, err)) {
packed_refs_unlock(refs->packed_ref_store);
ret = TRANSACTION_NAME_CONFLICT;
goto cleanup;
}

if (ref_transaction_commit(packed_transaction, err)) {
ret = TRANSACTION_GENERIC_ERROR;
goto cleanup;
}
@ -3147,6 +3174,7 @@ cleanup:
ref_transaction_free(packed_transaction);
transaction->state = REF_TRANSACTION_CLOSED;
string_list_clear(&affected_refnames, 0);
string_list_clear(&refnames_to_check, 0);
return ret;
}

@ -3808,6 +3836,7 @@ static int files_fsck_refs_dir(struct ref_store *ref_store,
ret = error(_("failed to iterate over '%s'"), sb.buf);

out:
dir_iterator_free(iter);
strbuf_release(&sb);
strbuf_release(&refname);
return ret;

View File

@ -15,15 +15,26 @@ int ref_iterator_advance(struct ref_iterator *ref_iterator)
return ref_iterator->vtable->advance(ref_iterator);
}

int ref_iterator_seek(struct ref_iterator *ref_iterator,
const char *prefix)
{
return ref_iterator->vtable->seek(ref_iterator, prefix);
}

int ref_iterator_peel(struct ref_iterator *ref_iterator,
struct object_id *peeled)
{
return ref_iterator->vtable->peel(ref_iterator, peeled);
}

int ref_iterator_abort(struct ref_iterator *ref_iterator)
void ref_iterator_free(struct ref_iterator *ref_iterator)
{
return ref_iterator->vtable->abort(ref_iterator);
if (ref_iterator) {
ref_iterator->vtable->release(ref_iterator);
/* Help make use-after-free bugs fail quickly: */
ref_iterator->vtable = NULL;
free(ref_iterator);
}
}

void base_ref_iterator_init(struct ref_iterator *iter,
@ -36,20 +47,19 @@ void base_ref_iterator_init(struct ref_iterator *iter,
iter->flags = 0;
}

void base_ref_iterator_free(struct ref_iterator *iter)
{
/* Help make use-after-free bugs fail quickly: */
iter->vtable = NULL;
free(iter);
}

struct empty_ref_iterator {
struct ref_iterator base;
};

static int empty_ref_iterator_advance(struct ref_iterator *ref_iterator)
static int empty_ref_iterator_advance(struct ref_iterator *ref_iterator UNUSED)
{
return ref_iterator_abort(ref_iterator);
return ITER_DONE;
}

static int empty_ref_iterator_seek(struct ref_iterator *ref_iterator UNUSED,
const char *prefix UNUSED)
{
return 0;
}

static int empty_ref_iterator_peel(struct ref_iterator *ref_iterator UNUSED,
@ -58,16 +68,15 @@ static int empty_ref_iterator_peel(struct ref_iterator *ref_iterator UNUSED,
BUG("peel called for empty iterator");
}

static int empty_ref_iterator_abort(struct ref_iterator *ref_iterator)
static void empty_ref_iterator_release(struct ref_iterator *ref_iterator UNUSED)
{
base_ref_iterator_free(ref_iterator);
return ITER_DONE;
}

static struct ref_iterator_vtable empty_ref_iterator_vtable = {
.advance = empty_ref_iterator_advance,
.seek = empty_ref_iterator_seek,
.peel = empty_ref_iterator_peel,
.abort = empty_ref_iterator_abort,
.release = empty_ref_iterator_release,
};

struct ref_iterator *empty_ref_iterator_begin(void)
@ -87,7 +96,8 @@ int is_empty_ref_iterator(struct ref_iterator *ref_iterator)
struct merge_ref_iterator {
struct ref_iterator base;

struct ref_iterator *iter0, *iter1;
struct ref_iterator *iter0, *iter0_owned;
struct ref_iterator *iter1, *iter1_owned;

ref_iterator_select_fn *select;
void *cb_data;
@ -179,9 +189,8 @@ static int merge_ref_iterator_advance(struct ref_iterator *ref_iterator)
iter->select(iter->iter0, iter->iter1, iter->cb_data);

if (selection == ITER_SELECT_DONE) {
return ref_iterator_abort(ref_iterator);
return ITER_DONE;
} else if (selection == ITER_SELECT_ERROR) {
ref_iterator_abort(ref_iterator);
return ITER_ERROR;
}

@ -211,10 +220,31 @@ static int merge_ref_iterator_advance(struct ref_iterator *ref_iterator)
}

error:
ref_iterator_abort(ref_iterator);
return ITER_ERROR;
}

static int merge_ref_iterator_seek(struct ref_iterator *ref_iterator,
const char *prefix)
{
struct merge_ref_iterator *iter =
(struct merge_ref_iterator *)ref_iterator;
int ret;

iter->current = NULL;
iter->iter0 = iter->iter0_owned;
iter->iter1 = iter->iter1_owned;

ret = ref_iterator_seek(iter->iter0, prefix);
if (ret < 0)
return ret;

ret = ref_iterator_seek(iter->iter1, prefix);
if (ret < 0)
return ret;

return 0;
}

static int merge_ref_iterator_peel(struct ref_iterator *ref_iterator,
struct object_id *peeled)
{
@ -227,28 +257,19 @@ static int merge_ref_iterator_peel(struct ref_iterator *ref_iterator,
return ref_iterator_peel(*iter->current, peeled);
}

static int merge_ref_iterator_abort(struct ref_iterator *ref_iterator)
static void merge_ref_iterator_release(struct ref_iterator *ref_iterator)
{
struct merge_ref_iterator *iter =
(struct merge_ref_iterator *)ref_iterator;
int ok = ITER_DONE;

if (iter->iter0) {
if (ref_iterator_abort(iter->iter0) != ITER_DONE)
ok = ITER_ERROR;
}
if (iter->iter1) {
if (ref_iterator_abort(iter->iter1) != ITER_DONE)
ok = ITER_ERROR;
}
base_ref_iterator_free(ref_iterator);
return ok;
ref_iterator_free(iter->iter0_owned);
ref_iterator_free(iter->iter1_owned);
}

static struct ref_iterator_vtable merge_ref_iterator_vtable = {
.advance = merge_ref_iterator_advance,
.seek = merge_ref_iterator_seek,
.peel = merge_ref_iterator_peel,
.abort = merge_ref_iterator_abort,
.release = merge_ref_iterator_release,
};

struct ref_iterator *merge_ref_iterator_begin(
@ -267,8 +288,8 @@ struct ref_iterator *merge_ref_iterator_begin(
*/

base_ref_iterator_init(ref_iterator, &merge_ref_iterator_vtable);
iter->iter0 = iter0;
iter->iter1 = iter1;
iter->iter0 = iter->iter0_owned = iter0;
iter->iter1 = iter->iter1_owned = iter1;
iter->select = select;
iter->cb_data = cb_data;
iter->current = NULL;
@ -310,10 +331,10 @@ struct ref_iterator *overlay_ref_iterator_begin(
* them.
*/
if (is_empty_ref_iterator(front)) {
ref_iterator_abort(front);
ref_iterator_free(front);
return back;
} else if (is_empty_ref_iterator(back)) {
ref_iterator_abort(back);
ref_iterator_free(back);
return front;
}

@ -350,19 +371,15 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)

while ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) {
int cmp = compare_prefix(iter->iter0->refname, iter->prefix);

if (cmp < 0)
continue;

if (cmp > 0) {
/*
* As the source iterator is ordered, we
* can stop the iteration as soon as we see a
* refname that comes after the prefix:
*/
ok = ref_iterator_abort(iter->iter0);
break;
}
/*
* As the source iterator is ordered, we
* can stop the iteration as soon as we see a
* refname that comes after the prefix:
*/
if (cmp > 0)
return ITER_DONE;

if (iter->trim) {
/*
@ -386,12 +403,19 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator)
return ITER_OK;
}

iter->iter0 = NULL;
if (ref_iterator_abort(ref_iterator) != ITER_DONE)
return ITER_ERROR;
return ok;
}

static int prefix_ref_iterator_seek(struct ref_iterator *ref_iterator,
const char *prefix)
{
struct prefix_ref_iterator *iter =
(struct prefix_ref_iterator *)ref_iterator;
free(iter->prefix);
iter->prefix = xstrdup_or_null(prefix);
return ref_iterator_seek(iter->iter0, prefix);
}

static int prefix_ref_iterator_peel(struct ref_iterator *ref_iterator,
struct object_id *peeled)
{
@ -401,23 +425,19 @@ static int prefix_ref_iterator_peel(struct ref_iterator *ref_iterator,
return ref_iterator_peel(iter->iter0, peeled);
}

static int prefix_ref_iterator_abort(struct ref_iterator *ref_iterator)
static void prefix_ref_iterator_release(struct ref_iterator *ref_iterator)
{
struct prefix_ref_iterator *iter =
(struct prefix_ref_iterator *)ref_iterator;
int ok = ITER_DONE;

if (iter->iter0)
ok = ref_iterator_abort(iter->iter0);
ref_iterator_free(iter->iter0);
free(iter->prefix);
base_ref_iterator_free(ref_iterator);
return ok;
}

static struct ref_iterator_vtable prefix_ref_iterator_vtable = {
.advance = prefix_ref_iterator_advance,
.seek = prefix_ref_iterator_seek,
.peel = prefix_ref_iterator_peel,
.abort = prefix_ref_iterator_abort,
.release = prefix_ref_iterator_release,
};

struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0,
@ -453,20 +473,14 @@ int do_for_each_ref_iterator(struct ref_iterator *iter,
current_ref_iter = iter;
while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
retval = fn(iter->refname, iter->referent, iter->oid, iter->flags, cb_data);
if (retval) {
/*
* If ref_iterator_abort() returns ITER_ERROR,
* we ignore that error in deference to the
* callback function's return value.
*/
ref_iterator_abort(iter);
if (retval)
goto out;
}
}

out:
current_ref_iter = old_ref_iter;
if (ok == ITER_ERROR)
return -1;
retval = -1;
ref_iterator_free(iter);
return retval;
}

View File

@ -841,6 +841,8 @@ struct packed_ref_iterator {

struct snapshot *snapshot;

char *prefix;

/* The current position in the snapshot's buffer: */
const char *pos;

@ -863,11 +865,9 @@ struct packed_ref_iterator {
};

/*
* Move the iterator to the next record in the snapshot, without
* respect for whether the record is actually required by the current
* iteration. Adjust the fields in `iter` and return `ITER_OK` or
* `ITER_DONE`. This function does not free the iterator in the case
* of `ITER_DONE`.
* Move the iterator to the next record in the snapshot. Adjust the fields in
* `iter` and return `ITER_OK` or `ITER_DONE`. This function does not free the
* iterator in the case of `ITER_DONE`.
*/
static int next_record(struct packed_ref_iterator *iter)
{
@ -967,6 +967,9 @@ static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator)
int ok;

while ((ok = next_record(iter)) == ITER_OK) {
const char *refname = iter->base.refname;
const char *prefix = iter->prefix;

if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY &&
!is_per_worktree_ref(iter->base.refname))
continue;
@ -976,15 +979,41 @@ static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator)
&iter->oid, iter->flags))
continue;

while (prefix && *prefix) {
if (*refname < *prefix)
BUG("packed-refs backend yielded reference preceding its prefix");
else if (*refname > *prefix)
return ITER_DONE;
prefix++;
refname++;
}

return ITER_OK;
}

if (ref_iterator_abort(ref_iterator) != ITER_DONE)
ok = ITER_ERROR;

return ok;
}

static int packed_ref_iterator_seek(struct ref_iterator *ref_iterator,
const char *prefix)
{
struct packed_ref_iterator *iter =
(struct packed_ref_iterator *)ref_iterator;
const char *start;

if (prefix && *prefix)
start = find_reference_location(iter->snapshot, prefix, 0);
else
start = iter->snapshot->start;

free(iter->prefix);
iter->prefix = xstrdup_or_null(prefix);
iter->pos = start;
iter->eof = iter->snapshot->eof;

return 0;
}

static int packed_ref_iterator_peel(struct ref_iterator *ref_iterator,
struct object_id *peeled)
{
@ -1001,23 +1030,21 @@ static int packed_ref_iterator_peel(struct ref_iterator *ref_iterator,
}
}

static int packed_ref_iterator_abort(struct ref_iterator *ref_iterator)
static void packed_ref_iterator_release(struct ref_iterator *ref_iterator)
{
struct packed_ref_iterator *iter =
(struct packed_ref_iterator *)ref_iterator;
int ok = ITER_DONE;

strbuf_release(&iter->refname_buf);
free(iter->jump);
free(iter->prefix);
release_snapshot(iter->snapshot);
base_ref_iterator_free(ref_iterator);
return ok;
}

static struct ref_iterator_vtable packed_ref_iterator_vtable = {
.advance = packed_ref_iterator_advance,
.seek = packed_ref_iterator_seek,
.peel = packed_ref_iterator_peel,
.abort = packed_ref_iterator_abort
.release = packed_ref_iterator_release,
};

static int jump_list_entry_cmp(const void *va, const void *vb)
@ -1129,7 +1156,6 @@ static struct ref_iterator *packed_ref_iterator_begin(
{
struct packed_ref_store *refs;
struct snapshot *snapshot;
const char *start;
struct packed_ref_iterator *iter;
struct ref_iterator *ref_iterator;
unsigned int required_flags = REF_STORE_READ;
@ -1145,14 +1171,6 @@ static struct ref_iterator *packed_ref_iterator_begin(
*/
snapshot = get_snapshot(refs);

if (prefix && *prefix)
start = find_reference_location(snapshot, prefix, 0);
else
start = snapshot->start;

if (start == snapshot->eof)
return empty_ref_iterator_begin();

CALLOC_ARRAY(iter, 1);
ref_iterator = &iter->base;
base_ref_iterator_init(ref_iterator, &packed_ref_iterator_vtable);
@ -1162,19 +1180,15 @@ static struct ref_iterator *packed_ref_iterator_begin(

iter->snapshot = snapshot;
acquire_snapshot(snapshot);

iter->pos = start;
iter->eof = snapshot->eof;
strbuf_init(&iter->refname_buf, 0);

iter->base.oid = &iter->oid;

iter->repo = ref_store->repo;
iter->flags = flags;

if (prefix && *prefix)
/* Stop iteration after we've gone *past* prefix: */
ref_iterator = prefix_ref_iterator_begin(ref_iterator, prefix, 0);
if (packed_ref_iterator_seek(&iter->base, prefix) < 0) {
ref_iterator_free(&iter->base);
return NULL;
}

return ref_iterator;
}
@ -1387,8 +1401,10 @@ static int write_with_updates(struct packed_ref_store *refs,
*/
iter = packed_ref_iterator_begin(&refs->base, "", NULL,
DO_FOR_EACH_INCLUDE_BROKEN);
if ((ok = ref_iterator_advance(iter)) != ITER_OK)
if ((ok = ref_iterator_advance(iter)) != ITER_OK) {
ref_iterator_free(iter);
iter = NULL;
}

i = 0;

@ -1436,8 +1452,10 @@ static int write_with_updates(struct packed_ref_store *refs,
* the iterator over the unneeded
* value.
*/
if ((ok = ref_iterator_advance(iter)) != ITER_OK)
if ((ok = ref_iterator_advance(iter)) != ITER_OK) {
ref_iterator_free(iter);
iter = NULL;
}
cmp = +1;
} else {
/*
@ -1474,8 +1492,10 @@ static int write_with_updates(struct packed_ref_store *refs,
peel_error ? NULL : &peeled))
goto write_error;

if ((ok = ref_iterator_advance(iter)) != ITER_OK)
if ((ok = ref_iterator_advance(iter)) != ITER_OK) {
ref_iterator_free(iter);
iter = NULL;
}
} else if (is_null_oid(&update->new_oid)) {
/*
* The update wants to delete the reference,
@ -1524,9 +1544,7 @@ write_error:
get_tempfile_path(refs->tempfile), strerror(errno));

error:
if (iter)
ref_iterator_abort(iter);

ref_iterator_free(iter);
delete_tempfile(&refs->tempfile);
return -1;
}

View File

@ -362,9 +362,7 @@ struct cache_ref_iterator {
struct ref_iterator base;

/*
* The number of levels currently on the stack. This is always
* at least 1, because when it becomes zero the iteration is
* ended and this struct is freed.
* The number of levels currently on the stack.
*/
size_t levels_nr;

@ -376,7 +374,7 @@ struct cache_ref_iterator {
* The prefix is matched textually, without regard for path
* component boundaries.
*/
const char *prefix;
char *prefix;

/*
* A stack of levels. levels[0] is the uppermost level that is
@ -389,6 +387,9 @@ struct cache_ref_iterator {
struct cache_ref_iterator_level *levels;

struct repository *repo;
struct ref_cache *cache;

int prime_dir;
};

static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)
@ -396,6 +397,9 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)
struct cache_ref_iterator *iter =
(struct cache_ref_iterator *)ref_iterator;

if (!iter->levels_nr)
return ITER_DONE;

while (1) {
struct cache_ref_iterator_level *level =
&iter->levels[iter->levels_nr - 1];
@ -409,7 +413,7 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)
if (++level->index == level->dir->nr) {
/* This level is exhausted; pop up a level */
if (--iter->levels_nr == 0)
return ref_iterator_abort(ref_iterator);
return ITER_DONE;

continue;
}
@ -444,6 +448,41 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)
}
}

static int cache_ref_iterator_seek(struct ref_iterator *ref_iterator,
const char *prefix)
{
struct cache_ref_iterator *iter =
(struct cache_ref_iterator *)ref_iterator;
struct cache_ref_iterator_level *level;
struct ref_dir *dir;

dir = get_ref_dir(iter->cache->root);
if (prefix && *prefix)
dir = find_containing_dir(dir, prefix);
if (!dir) {
iter->levels_nr = 0;
return 0;
}

if (iter->prime_dir)
prime_ref_dir(dir, prefix);
iter->levels_nr = 1;
level = &iter->levels[0];
level->index = -1;
level->dir = dir;

if (prefix && *prefix) {
free(iter->prefix);
iter->prefix = xstrdup(prefix);
level->prefix_state = PREFIX_WITHIN_DIR;
} else {
FREE_AND_NULL(iter->prefix);
level->prefix_state = PREFIX_CONTAINS_DIR;
}

return 0;
}

static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator,
struct object_id *peeled)
{
@ -452,21 +491,19 @@ static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator,
return peel_object(iter->repo, ref_iterator->oid, peeled) ? -1 : 0;
}

static int cache_ref_iterator_abort(struct ref_iterator *ref_iterator)
static void cache_ref_iterator_release(struct ref_iterator *ref_iterator)
{
struct cache_ref_iterator *iter =
(struct cache_ref_iterator *)ref_iterator;

free((char *)iter->prefix);
free(iter->prefix);
free(iter->levels);
base_ref_iterator_free(ref_iterator);
return ITER_DONE;
}

static struct ref_iterator_vtable cache_ref_iterator_vtable = {
.advance = cache_ref_iterator_advance,
.seek = cache_ref_iterator_seek,
.peel = cache_ref_iterator_peel,
.abort = cache_ref_iterator_abort
.release = cache_ref_iterator_release,
};

struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache,
@ -474,39 +511,22 @@ struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache,
struct repository *repo,
int prime_dir)
{
struct ref_dir *dir;
struct cache_ref_iterator *iter;
struct ref_iterator *ref_iterator;
struct cache_ref_iterator_level *level;

dir = get_ref_dir(cache->root);
if (prefix && *prefix)
dir = find_containing_dir(dir, prefix);
if (!dir)
/* There's nothing to iterate over. */
return empty_ref_iterator_begin();

if (prime_dir)
prime_ref_dir(dir, prefix);

CALLOC_ARRAY(iter, 1);
ref_iterator = &iter->base;
base_ref_iterator_init(ref_iterator, &cache_ref_iterator_vtable);
ALLOC_GROW(iter->levels, 10, iter->levels_alloc);

iter->levels_nr = 1;
level = &iter->levels[0];
level->index = -1;
level->dir = dir;

if (prefix && *prefix) {
iter->prefix = xstrdup(prefix);
level->prefix_state = PREFIX_WITHIN_DIR;
} else {
level->prefix_state = PREFIX_CONTAINS_DIR;
}

iter->repo = repo;
iter->cache = cache;
iter->prime_dir = prime_dir;

if (cache_ref_iterator_seek(&iter->base, prefix) < 0) {
ref_iterator_free(&iter->base);
return NULL;
}

return ref_iterator;
}

View File

@ -273,11 +273,11 @@ enum do_for_each_ref_flags {
* the next reference and returns ITER_OK. The data pointed at by
* refname and oid belong to the iterator; if you want to retain them
* after calling ref_iterator_advance() again or calling
* ref_iterator_abort(), you must make a copy. When the iteration has
* ref_iterator_free(), you must make a copy. When the iteration has
* been exhausted, ref_iterator_advance() releases any resources
* associated with the iteration, frees the ref_iterator object, and
* returns ITER_DONE. If you want to abort the iteration early, call
* ref_iterator_abort(), which also frees the ref_iterator object and
* ref_iterator_free(), which also frees the ref_iterator object and
* any associated resources. If there was an internal error advancing
* to the next entry, ref_iterator_advance() aborts the iteration,
* frees the ref_iterator, and returns ITER_ERROR.
@ -293,7 +293,7 @@ enum do_for_each_ref_flags {
*
* while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
* if (want_to_stop_iteration()) {
* ok = ref_iterator_abort(iter);
* ok = ITER_DONE;
* break;
* }
*
@ -307,6 +307,7 @@ enum do_for_each_ref_flags {
*
* if (ok != ITER_DONE)
* handle_error();
* ref_iterator_free(iter);
*/
struct ref_iterator {
struct ref_iterator_vtable *vtable;
@ -326,6 +327,22 @@ struct ref_iterator {
*/
int ref_iterator_advance(struct ref_iterator *ref_iterator);

/*
* Seek the iterator to the first reference with the given prefix.
* The prefix is matched as a literal string, without regard for path
* separators. If prefix is NULL or the empty string, seek the iterator to the
* first reference again.
*
* This function is expected to behave as if a new ref iterator with the same
* prefix had been created, but allows reuse of iterators and thus may allow
* the backend to optimize. Parameters other than the prefix that have been
* passed when creating the iterator will remain unchanged.
*
* Returns 0 on success, a negative error code otherwise.
*/
int ref_iterator_seek(struct ref_iterator *ref_iterator,
const char *prefix);

/*
* If possible, peel the reference currently being viewed by the
* iterator. Return 0 on success.
@ -333,12 +350,8 @@ int ref_iterator_advance(struct ref_iterator *ref_iterator);
int ref_iterator_peel(struct ref_iterator *ref_iterator,
struct object_id *peeled);

/*
* End the iteration before it has been exhausted, freeing the
* reference iterator and any associated resources and returning
* ITER_DONE. If the abort itself failed, return ITER_ERROR.
*/
int ref_iterator_abort(struct ref_iterator *ref_iterator);
/* Free the reference iterator and any associated resources. */
void ref_iterator_free(struct ref_iterator *ref_iterator);

/*
* An iterator over nothing (its first ref_iterator_advance() call
@ -438,13 +451,6 @@ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0,
void base_ref_iterator_init(struct ref_iterator *iter,
struct ref_iterator_vtable *vtable);

/*
* Base class destructor for ref_iterators. Destroy the ref_iterator
* part of iter and shallow-free the object. This is meant to be
* called only by the destructors of derived classes.
*/
void base_ref_iterator_free(struct ref_iterator *iter);

/* Virtual function declarations for ref_iterators: */

/*
@ -455,6 +461,13 @@ void base_ref_iterator_free(struct ref_iterator *iter);
*/
typedef int ref_iterator_advance_fn(struct ref_iterator *ref_iterator);

/*
* Seek the iterator to the first reference matching the given prefix. Should
* behave the same as if a new iterator was created with the same prefix.
*/
typedef int ref_iterator_seek_fn(struct ref_iterator *ref_iterator,
const char *prefix);

/*
* Peels the current ref, returning 0 for success or -1 for failure.
*/
@ -463,15 +476,15 @@ typedef int ref_iterator_peel_fn(struct ref_iterator *ref_iterator,

/*
* Implementations of this function should free any resources specific
* to the derived class, then call base_ref_iterator_free() to clean
* up and free the ref_iterator object.
* to the derived class.
*/
typedef int ref_iterator_abort_fn(struct ref_iterator *ref_iterator);
typedef void ref_iterator_release_fn(struct ref_iterator *ref_iterator);

struct ref_iterator_vtable {
ref_iterator_advance_fn *advance;
ref_iterator_seek_fn *seek;
ref_iterator_peel_fn *peel;
ref_iterator_abort_fn *abort;
ref_iterator_release_fn *release;
};

/*

View File

@ -547,7 +547,7 @@ struct reftable_ref_iterator {
struct reftable_ref_record ref;
struct object_id oid;

const char *prefix;
char *prefix;
size_t prefix_len;
char **exclude_patterns;
size_t exclude_patterns_index;
@ -711,20 +711,27 @@ static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator)
break;
}

if (iter->err > 0) {
if (ref_iterator_abort(ref_iterator) != ITER_DONE)
return ITER_ERROR;
if (iter->err > 0)
return ITER_DONE;
}

if (iter->err < 0) {
ref_iterator_abort(ref_iterator);
if (iter->err < 0)
return ITER_ERROR;
}

return ITER_OK;
}

static int reftable_ref_iterator_seek(struct ref_iterator *ref_iterator,
const char *prefix)
{
struct reftable_ref_iterator *iter =
(struct reftable_ref_iterator *)ref_iterator;

free(iter->prefix);
iter->prefix = xstrdup_or_null(prefix);
iter->prefix_len = prefix ? strlen(prefix) : 0;
iter->err = reftable_iterator_seek_ref(&iter->iter, prefix);

return iter->err;
}

static int reftable_ref_iterator_peel(struct ref_iterator *ref_iterator,
struct object_id *peeled)
{
@ -740,7 +747,7 @@ static int reftable_ref_iterator_peel(struct ref_iterator *ref_iterator,
return -1;
}

static int reftable_ref_iterator_abort(struct ref_iterator *ref_iterator)
static void reftable_ref_iterator_release(struct ref_iterator *ref_iterator)
{
struct reftable_ref_iterator *iter =
(struct reftable_ref_iterator *)ref_iterator;
@ -751,14 +758,14 @@ static int reftable_ref_iterator_abort(struct ref_iterator *ref_iterator)
free(iter->exclude_patterns[i]);
free(iter->exclude_patterns);
}
free(iter);
return ITER_DONE;
free(iter->prefix);
}

static struct ref_iterator_vtable reftable_ref_iterator_vtable = {
.advance = reftable_ref_iterator_advance,
.seek = reftable_ref_iterator_seek,
.peel = reftable_ref_iterator_peel,
.abort = reftable_ref_iterator_abort
.release = reftable_ref_iterator_release,
};

static int qsort_strcmp(const void *va, const void *vb)
@ -815,8 +822,6 @@ static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_

iter = xcalloc(1, sizeof(*iter));
base_ref_iterator_init(&iter->base, &reftable_ref_iterator_vtable);
iter->prefix = prefix;
iter->prefix_len = prefix ? strlen(prefix) : 0;
iter->base.oid = &iter->oid;
iter->flags = flags;
iter->refs = refs;
@ -830,8 +835,11 @@ static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_
if (ret)
goto done;

reftable_stack_init_ref_iterator(stack, &iter->iter);
ret = reftable_iterator_seek_ref(&iter->iter, prefix);
ret = reftable_stack_init_ref_iterator(stack, &iter->iter);
if (ret)
goto done;

ret = reftable_ref_iterator_seek(&iter->base, prefix);
if (ret)
goto done;

@ -1069,6 +1077,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
reftable_be_downcast(ref_store, REF_STORE_WRITE|REF_STORE_MAIN, "ref_transaction_prepare");
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 reftable_transaction_data *tx_data = NULL;
struct reftable_backend *be;
struct object_id head_oid;
@ -1224,12 +1233,7 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
* can output a proper error message instead of failing
* at a later point.
*/
ret = refs_verify_refname_available(ref_store, u->refname,
&affected_refnames, NULL,
transaction->flags & REF_TRANSACTION_FLAG_INITIAL,
err);
if (ret < 0)
goto done;
string_list_append(&refnames_to_check, u->refname);

/*
* There is no need to write the reference deletion
@ -1379,6 +1383,12 @@ static int reftable_be_transaction_prepare(struct ref_store *ref_store,
}
}

ret = refs_verify_refnames_available(ref_store, &refnames_to_check, &affected_refnames, NULL,
transaction->flags & REF_TRANSACTION_FLAG_INITIAL,
err);
if (ret < 0)
goto done;

transaction->backend_data = tx_data;
transaction->state = REF_TRANSACTION_PREPARED;

@ -1394,6 +1404,7 @@ done:
string_list_clear(&affected_refnames, 0);
strbuf_release(&referent);
strbuf_release(&head_referent);
string_list_clear(&refnames_to_check, 0);

return ret;
}
@ -2017,20 +2028,20 @@ static int reftable_reflog_iterator_advance(struct ref_iterator *ref_iterator)
break;
}

if (iter->err > 0) {
if (ref_iterator_abort(ref_iterator) != ITER_DONE)
return ITER_ERROR;
if (iter->err > 0)
return ITER_DONE;
}

if (iter->err < 0) {
ref_iterator_abort(ref_iterator);
if (iter->err < 0)
return ITER_ERROR;
}

return ITER_OK;
}

static int reftable_reflog_iterator_seek(struct ref_iterator *ref_iterator UNUSED,
const char *prefix UNUSED)
{
BUG("reftable reflog iterator cannot be seeked");
return -1;
}

static int reftable_reflog_iterator_peel(struct ref_iterator *ref_iterator UNUSED,
struct object_id *peeled UNUSED)
{
@ -2038,21 +2049,20 @@ static int reftable_reflog_iterator_peel(struct ref_iterator *ref_iterator UNUSE
return -1;
}

static int reftable_reflog_iterator_abort(struct ref_iterator *ref_iterator)
static void reftable_reflog_iterator_release(struct ref_iterator *ref_iterator)
{
struct reftable_reflog_iterator *iter =
(struct reftable_reflog_iterator *)ref_iterator;
reftable_log_record_release(&iter->log);
reftable_iterator_destroy(&iter->iter);
strbuf_release(&iter->last_name);
free(iter);
return ITER_DONE;
}

static struct ref_iterator_vtable reftable_reflog_iterator_vtable = {
.advance = reftable_reflog_iterator_advance,
.seek = reftable_reflog_iterator_seek,
.peel = reftable_reflog_iterator_peel,
.abort = reftable_reflog_iterator_abort
.release = reftable_reflog_iterator_release,
};

static struct reftable_reflog_iterator *reflog_iterator_for_stack(struct reftable_ref_store *refs,

View File

@ -53,6 +53,7 @@ int cmd__dir_iterator(int argc, const char **argv)
printf("(%s) [%s] %s\n", diter->relative_path, diter->basename,
diter->path.buf);
}
dir_iterator_free(diter);

if (iter_status != ITER_DONE) {
printf("dir_iterator_advance failure\n");