diff options
Diffstat (limited to 'refs')
-rw-r--r-- | refs/debug.c | 459 | ||||
-rw-r--r-- | refs/files-backend.c | 1090 | ||||
-rw-r--r-- | refs/iterator.c | 97 | ||||
-rw-r--r-- | refs/packed-backend.c | 447 | ||||
-rw-r--r-- | refs/packed-backend.h | 4 | ||||
-rw-r--r-- | refs/ref-cache.c | 118 | ||||
-rw-r--r-- | refs/ref-cache.h | 30 | ||||
-rw-r--r-- | refs/refs-internal.h | 210 | ||||
-rw-r--r-- | refs/reftable-backend.c | 2242 |
9 files changed, 3799 insertions, 898 deletions
diff --git a/refs/debug.c b/refs/debug.c new file mode 100644 index 0000000..c7531b1 --- /dev/null +++ b/refs/debug.c @@ -0,0 +1,459 @@ +#include "git-compat-util.h" +#include "hex.h" +#include "refs-internal.h" +#include "string-list.h" +#include "trace.h" + +static struct trace_key trace_refs = TRACE_KEY_INIT(REFS); + +struct debug_ref_store { + struct ref_store base; + struct ref_store *refs; +}; + +extern struct ref_storage_be refs_be_debug; + +struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store) +{ + struct debug_ref_store *res; + struct ref_storage_be *be_copy; + + if (!trace_want(&trace_refs)) { + return store; + } + res = xmalloc(sizeof(struct debug_ref_store)); + be_copy = xmalloc(sizeof(*be_copy)); + *be_copy = refs_be_debug; + /* we never deallocate backends, so safe to copy the pointer. */ + be_copy->name = store->be->name; + trace_printf_key(&trace_refs, "ref_store for %s\n", gitdir); + res->refs = store; + base_ref_store_init((struct ref_store *)res, store->repo, gitdir, + be_copy); + return (struct ref_store *)res; +} + +static int debug_init_db(struct ref_store *refs, int flags, struct strbuf *err) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)refs; + int res = drefs->refs->be->init_db(drefs->refs, flags, err); + trace_printf_key(&trace_refs, "init_db: %d\n", res); + return res; +} + +static int debug_transaction_prepare(struct ref_store *refs, + struct ref_transaction *transaction, + struct strbuf *err) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)refs; + int res; + transaction->ref_store = drefs->refs; + res = drefs->refs->be->transaction_prepare(drefs->refs, transaction, + err); + trace_printf_key(&trace_refs, "transaction_prepare: %d \"%s\"\n", res, + err->buf); + return res; +} + +static void print_update(int i, const char *refname, + const struct object_id *old_oid, + const struct object_id *new_oid, unsigned int flags, + unsigned int type, const char *msg) +{ + char o[GIT_MAX_HEXSZ + 1] = "null"; + char n[GIT_MAX_HEXSZ + 1] = "null"; + if (old_oid) + oid_to_hex_r(o, old_oid); + if (new_oid) + oid_to_hex_r(n, new_oid); + + type &= 0xf; /* see refs.h REF_* */ + flags &= REF_HAVE_NEW | REF_HAVE_OLD | REF_NO_DEREF | + REF_FORCE_CREATE_REFLOG; + trace_printf_key(&trace_refs, "%d: %s %s -> %s (F=0x%x, T=0x%x) \"%s\"\n", i, refname, + o, n, flags, type, msg); +} + +static void print_transaction(struct ref_transaction *transaction) +{ + int i; + trace_printf_key(&trace_refs, "transaction {\n"); + for (i = 0; i < transaction->nr; i++) { + struct ref_update *u = transaction->updates[i]; + print_update(i, u->refname, &u->old_oid, &u->new_oid, u->flags, + u->type, u->msg); + } + trace_printf_key(&trace_refs, "}\n"); +} + +static int debug_transaction_finish(struct ref_store *refs, + struct ref_transaction *transaction, + struct strbuf *err) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)refs; + int res; + transaction->ref_store = drefs->refs; + res = drefs->refs->be->transaction_finish(drefs->refs, transaction, + err); + print_transaction(transaction); + trace_printf_key(&trace_refs, "finish: %d\n", res); + return res; +} + +static int debug_transaction_abort(struct ref_store *refs, + struct ref_transaction *transaction, + struct strbuf *err) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)refs; + int res; + transaction->ref_store = drefs->refs; + res = drefs->refs->be->transaction_abort(drefs->refs, transaction, err); + return res; +} + +static int debug_initial_transaction_commit(struct ref_store *refs, + struct ref_transaction *transaction, + struct strbuf *err) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)refs; + int res; + transaction->ref_store = drefs->refs; + res = drefs->refs->be->initial_transaction_commit(drefs->refs, + transaction, err); + return res; +} + +static int debug_pack_refs(struct ref_store *ref_store, struct pack_refs_opts *opts) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + int res = drefs->refs->be->pack_refs(drefs->refs, opts); + trace_printf_key(&trace_refs, "pack_refs: %d\n", res); + return res; +} + +static int debug_create_symref(struct ref_store *ref_store, + const char *ref_name, const char *target, + const char *logmsg) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + int res = drefs->refs->be->create_symref(drefs->refs, ref_name, target, + logmsg); + trace_printf_key(&trace_refs, "create_symref: %s -> %s \"%s\": %d\n", ref_name, + target, logmsg, res); + return res; +} + +static int debug_rename_ref(struct ref_store *ref_store, const char *oldref, + const char *newref, const char *logmsg) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + int res = drefs->refs->be->rename_ref(drefs->refs, oldref, newref, + logmsg); + trace_printf_key(&trace_refs, "rename_ref: %s -> %s \"%s\": %d\n", oldref, newref, + logmsg, res); + return res; +} + +static int debug_copy_ref(struct ref_store *ref_store, const char *oldref, + const char *newref, const char *logmsg) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + int res = + drefs->refs->be->copy_ref(drefs->refs, oldref, newref, logmsg); + trace_printf_key(&trace_refs, "copy_ref: %s -> %s \"%s\": %d\n", oldref, newref, + logmsg, res); + return res; +} + +struct debug_ref_iterator { + struct ref_iterator base; + struct ref_iterator *iter; +}; + +static int debug_ref_iterator_advance(struct ref_iterator *ref_iterator) +{ + struct debug_ref_iterator *diter = + (struct debug_ref_iterator *)ref_iterator; + int res = diter->iter->vtable->advance(diter->iter); + if (res) + trace_printf_key(&trace_refs, "iterator_advance: (%d)\n", res); + else + trace_printf_key(&trace_refs, "iterator_advance: %s (0)\n", + diter->iter->refname); + + diter->base.refname = diter->iter->refname; + diter->base.oid = diter->iter->oid; + diter->base.flags = diter->iter->flags; + return res; +} + +static int debug_ref_iterator_peel(struct ref_iterator *ref_iterator, + struct object_id *peeled) +{ + struct debug_ref_iterator *diter = + (struct debug_ref_iterator *)ref_iterator; + int res = diter->iter->vtable->peel(diter->iter, peeled); + trace_printf_key(&trace_refs, "iterator_peel: %s: %d\n", diter->iter->refname, res); + return res; +} + +static int debug_ref_iterator_abort(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; +} + +static struct ref_iterator_vtable debug_ref_iterator_vtable = { + .advance = debug_ref_iterator_advance, + .peel = debug_ref_iterator_peel, + .abort = debug_ref_iterator_abort, +}; + +static struct ref_iterator * +debug_ref_iterator_begin(struct ref_store *ref_store, const char *prefix, + const char **exclude_patterns, unsigned int flags) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + struct ref_iterator *res = + drefs->refs->be->iterator_begin(drefs->refs, prefix, + exclude_patterns, flags); + struct debug_ref_iterator *diter = xcalloc(1, sizeof(*diter)); + base_ref_iterator_init(&diter->base, &debug_ref_iterator_vtable); + diter->iter = res; + trace_printf_key(&trace_refs, "ref_iterator_begin: \"%s\" (0x%x)\n", + prefix, flags); + return &diter->base; +} + +static int debug_read_raw_ref(struct ref_store *ref_store, const char *refname, + struct object_id *oid, struct strbuf *referent, + unsigned int *type, int *failure_errno) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + int res = 0; + + oidcpy(oid, null_oid()); + res = drefs->refs->be->read_raw_ref(drefs->refs, refname, oid, referent, + type, failure_errno); + + if (res == 0) { + trace_printf_key(&trace_refs, "read_raw_ref: %s: %s (=> %s) type %x: %d\n", + refname, oid_to_hex(oid), referent->buf, *type, res); + } else { + trace_printf_key(&trace_refs, + "read_raw_ref: %s: %d (errno %d)\n", refname, + res, *failure_errno); + } + return res; +} + +static int debug_read_symbolic_ref(struct ref_store *ref_store, const char *refname, + struct strbuf *referent) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + struct ref_store *refs = drefs->refs; + int res; + + res = refs->be->read_symbolic_ref(refs, refname, referent); + if (!res) + trace_printf_key(&trace_refs, "read_symbolic_ref: %s: (%s)\n", + refname, referent->buf); + else + trace_printf_key(&trace_refs, + "read_symbolic_ref: %s: %d\n", refname, res); + return res; + +} + +static struct ref_iterator * +debug_reflog_iterator_begin(struct ref_store *ref_store) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + struct ref_iterator *res = + drefs->refs->be->reflog_iterator_begin(drefs->refs); + trace_printf_key(&trace_refs, "for_each_reflog_iterator_begin\n"); + return res; +} + +struct debug_reflog { + const char *refname; + each_reflog_ent_fn *fn; + void *cb_data; +}; + +static int debug_print_reflog_ent(struct object_id *old_oid, + struct object_id *new_oid, + const char *committer, timestamp_t timestamp, + int tz, const char *msg, void *cb_data) +{ + struct debug_reflog *dbg = (struct debug_reflog *)cb_data; + int ret; + char o[GIT_MAX_HEXSZ + 1] = "null"; + char n[GIT_MAX_HEXSZ + 1] = "null"; + char *msgend = strchrnul(msg, '\n'); + if (old_oid) + oid_to_hex_r(o, old_oid); + if (new_oid) + oid_to_hex_r(n, new_oid); + + ret = dbg->fn(old_oid, new_oid, committer, timestamp, tz, msg, + dbg->cb_data); + trace_printf_key(&trace_refs, + "reflog_ent %s (ret %d): %s -> %s, %s %ld \"%.*s\"\n", + dbg->refname, ret, o, n, committer, + (long int)timestamp, (int)(msgend - msg), msg); + return ret; +} + +static int debug_for_each_reflog_ent(struct ref_store *ref_store, + const char *refname, each_reflog_ent_fn fn, + void *cb_data) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + struct debug_reflog dbg = { + .refname = refname, + .fn = fn, + .cb_data = cb_data, + }; + + int res = drefs->refs->be->for_each_reflog_ent( + drefs->refs, refname, &debug_print_reflog_ent, &dbg); + trace_printf_key(&trace_refs, "for_each_reflog: %s: %d\n", refname, res); + return res; +} + +static int debug_for_each_reflog_ent_reverse(struct ref_store *ref_store, + const char *refname, + each_reflog_ent_fn fn, + void *cb_data) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + struct debug_reflog dbg = { + .refname = refname, + .fn = fn, + .cb_data = cb_data, + }; + int res = drefs->refs->be->for_each_reflog_ent_reverse( + drefs->refs, refname, &debug_print_reflog_ent, &dbg); + trace_printf_key(&trace_refs, "for_each_reflog_reverse: %s: %d\n", refname, res); + return res; +} + +static int debug_reflog_exists(struct ref_store *ref_store, const char *refname) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + int res = drefs->refs->be->reflog_exists(drefs->refs, refname); + trace_printf_key(&trace_refs, "reflog_exists: %s: %d\n", refname, res); + return res; +} + +static int debug_create_reflog(struct ref_store *ref_store, const char *refname, + struct strbuf *err) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + int res = drefs->refs->be->create_reflog(drefs->refs, refname, err); + trace_printf_key(&trace_refs, "create_reflog: %s: %d\n", refname, res); + return res; +} + +static int debug_delete_reflog(struct ref_store *ref_store, const char *refname) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + int res = drefs->refs->be->delete_reflog(drefs->refs, refname); + trace_printf_key(&trace_refs, "delete_reflog: %s: %d\n", refname, res); + return res; +} + +struct debug_reflog_expiry_should_prune { + reflog_expiry_prepare_fn *prepare; + reflog_expiry_should_prune_fn *should_prune; + reflog_expiry_cleanup_fn *cleanup; + void *cb_data; +}; + +static void debug_reflog_expiry_prepare(const char *refname, + const struct object_id *oid, + void *cb_data) +{ + struct debug_reflog_expiry_should_prune *prune = cb_data; + trace_printf_key(&trace_refs, "reflog_expire_prepare: %s\n", refname); + prune->prepare(refname, oid, prune->cb_data); +} + +static int debug_reflog_expiry_should_prune_fn(struct object_id *ooid, + struct object_id *noid, + const char *email, + timestamp_t timestamp, int tz, + const char *message, void *cb_data) { + struct debug_reflog_expiry_should_prune *prune = cb_data; + + int result = prune->should_prune(ooid, noid, email, timestamp, tz, message, prune->cb_data); + trace_printf_key(&trace_refs, "reflog_expire_should_prune: %s %ld: %d\n", message, (long int) timestamp, result); + return result; +} + +static void debug_reflog_expiry_cleanup(void *cb_data) +{ + struct debug_reflog_expiry_should_prune *prune = cb_data; + prune->cleanup(prune->cb_data); +} + +static int debug_reflog_expire(struct ref_store *ref_store, const char *refname, + unsigned int flags, + reflog_expiry_prepare_fn prepare_fn, + reflog_expiry_should_prune_fn should_prune_fn, + reflog_expiry_cleanup_fn cleanup_fn, + void *policy_cb_data) +{ + struct debug_ref_store *drefs = (struct debug_ref_store *)ref_store; + struct debug_reflog_expiry_should_prune prune = { + .prepare = prepare_fn, + .cleanup = cleanup_fn, + .should_prune = should_prune_fn, + .cb_data = policy_cb_data, + }; + int res = drefs->refs->be->reflog_expire(drefs->refs, refname, + flags, &debug_reflog_expiry_prepare, + &debug_reflog_expiry_should_prune_fn, + &debug_reflog_expiry_cleanup, + &prune); + trace_printf_key(&trace_refs, "reflog_expire: %s: %d\n", refname, res); + return res; +} + +struct ref_storage_be refs_be_debug = { + .name = "debug", + .init = NULL, + .init_db = debug_init_db, + + /* + * None of these should be NULL. If the "files" backend (in + * "struct ref_storage_be refs_be_files" in files-backend.c) + * has a function we should also have a wrapper for it here. + * Test the output with "GIT_TRACE_REFS=1". + */ + .transaction_prepare = debug_transaction_prepare, + .transaction_finish = debug_transaction_finish, + .transaction_abort = debug_transaction_abort, + .initial_transaction_commit = debug_initial_transaction_commit, + + .pack_refs = debug_pack_refs, + .create_symref = debug_create_symref, + .rename_ref = debug_rename_ref, + .copy_ref = debug_copy_ref, + + .iterator_begin = debug_ref_iterator_begin, + .read_raw_ref = debug_read_raw_ref, + .read_symbolic_ref = debug_read_symbolic_ref, + + .reflog_iterator_begin = debug_reflog_iterator_begin, + .for_each_reflog_ent = debug_for_each_reflog_ent, + .for_each_reflog_ent_reverse = debug_for_each_reflog_ent_reverse, + .reflog_exists = debug_reflog_exists, + .create_reflog = debug_create_reflog, + .delete_reflog = debug_delete_reflog, + .reflog_expire = debug_reflog_expire, +}; diff --git a/refs/files-backend.c b/refs/files-backend.c index d60767a..a098d14 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1,23 +1,33 @@ -#include "../cache.h" -#include "../config.h" +#include "../git-compat-util.h" +#include "../copy.h" +#include "../environment.h" +#include "../gettext.h" +#include "../hash.h" +#include "../hex.h" #include "../refs.h" #include "refs-internal.h" #include "ref-cache.h" #include "packed-backend.h" +#include "../ident.h" #include "../iterator.h" #include "../dir-iterator.h" #include "../lockfile.h" #include "../object.h" +#include "../object-file.h" +#include "../path.h" #include "../dir.h" #include "../chdir-notify.h" -#include "worktree.h" +#include "../setup.h" +#include "../wrapper.h" +#include "../write-or-die.h" +#include "../revision.h" +#include <wildmatch.h> /* * This backend uses the following flags in `ref_update::flags` for * internal bookkeeping purposes. Their numerical values must not * conflict with REF_NO_DEREF, REF_FORCE_CREATE_REFLOG, REF_HAVE_NEW, - * REF_HAVE_OLD, or REF_IS_PRUNING, which are also stored in - * `ref_update::flags`. + * or REF_HAVE_OLD, which are also stored in `ref_update::flags`. */ /* @@ -39,23 +49,16 @@ #define REF_NEEDS_COMMIT (1 << 6) /* - * Used as a flag in ref_update::flags when we want to log a ref - * update but not actually perform it. This is used when a symbolic - * ref update is split up. - */ -#define REF_LOG_ONLY (1 << 7) - -/* * Used as a flag in ref_update::flags when the ref_update was via an * update to HEAD. */ #define REF_UPDATE_VIA_HEAD (1 << 8) /* - * Used as a flag in ref_update::flags when the loose reference has - * been deleted. + * Used as a flag in ref_update::flags when a reference has been + * deleted and the ref's parent directories may need cleanup. */ -#define REF_DELETED_LOOSE (1 << 9) +#define REF_DELETED_RMDIR (1 << 9) struct ref_lock { char *ref_name; @@ -67,7 +70,6 @@ struct files_ref_store { struct ref_store base; unsigned int store_flags; - char *gitdir; char *gitcommondir; struct ref_cache *loose; @@ -87,25 +89,22 @@ static void clear_loose_ref_cache(struct files_ref_store *refs) * Create a new submodule ref cache and add it to the internal * set of caches. */ -static struct ref_store *files_ref_store_create(const char *gitdir, +static struct ref_store *files_ref_store_create(struct repository *repo, + const char *gitdir, unsigned int flags) { struct files_ref_store *refs = xcalloc(1, sizeof(*refs)); struct ref_store *ref_store = (struct ref_store *)refs; struct strbuf sb = STRBUF_INIT; - base_ref_store_init(ref_store, &refs_be_files); + base_ref_store_init(ref_store, repo, gitdir, &refs_be_files); refs->store_flags = flags; - - refs->gitdir = xstrdup(gitdir); get_common_dir_noenv(&sb, gitdir); refs->gitcommondir = strbuf_detach(&sb, NULL); - strbuf_addf(&sb, "%s/packed-refs", refs->gitcommondir); - refs->packed_ref_store = packed_ref_store_create(sb.buf, flags); - strbuf_release(&sb); + refs->packed_ref_store = + packed_ref_store_create(repo, refs->gitcommondir, flags); - chdir_notify_reparent("files-backend $GIT_DIR", - &refs->gitdir); + chdir_notify_reparent("files-backend $GIT_DIR", &refs->base.gitdir); chdir_notify_reparent("files-backend $GIT_COMMONDIR", &refs->gitcommondir); @@ -150,44 +149,30 @@ static struct files_ref_store *files_downcast(struct ref_store *ref_store, return refs; } -static void files_reflog_path_other_worktrees(struct files_ref_store *refs, - struct strbuf *sb, - const char *refname) -{ - const char *real_ref; - const char *worktree_name; - int length; - - if (parse_worktree_ref(refname, &worktree_name, &length, &real_ref)) - BUG("refname %s is not a other-worktree ref", refname); - - if (worktree_name) - strbuf_addf(sb, "%s/worktrees/%.*s/logs/%s", refs->gitcommondir, - length, worktree_name, real_ref); - else - strbuf_addf(sb, "%s/logs/%s", refs->gitcommondir, - real_ref); -} - static void files_reflog_path(struct files_ref_store *refs, struct strbuf *sb, const char *refname) { - switch (ref_type(refname)) { - case REF_TYPE_PER_WORKTREE: - case REF_TYPE_PSEUDOREF: - strbuf_addf(sb, "%s/logs/%s", refs->gitdir, refname); + const char *bare_refname; + const char *wtname; + int wtname_len; + enum ref_worktree_type wt_type = parse_worktree_ref( + refname, &wtname, &wtname_len, &bare_refname); + + switch (wt_type) { + case REF_WORKTREE_CURRENT: + strbuf_addf(sb, "%s/logs/%s", refs->base.gitdir, refname); break; - case REF_TYPE_OTHER_PSEUDOREF: - case REF_TYPE_MAIN_PSEUDOREF: - files_reflog_path_other_worktrees(refs, sb, refname); + case REF_WORKTREE_SHARED: + case REF_WORKTREE_MAIN: + strbuf_addf(sb, "%s/logs/%s", refs->gitcommondir, bare_refname); break; - case REF_TYPE_NORMAL: - strbuf_addf(sb, "%s/logs/%s", refs->gitcommondir, refname); + case REF_WORKTREE_OTHER: + strbuf_addf(sb, "%s/worktrees/%.*s/logs/%s", refs->gitcommondir, + wtname_len, wtname, bare_refname); break; default: - BUG("unknown ref type %d of ref %s", - ref_type(refname), refname); + BUG("unknown ref type %d of ref %s", wt_type, refname); } } @@ -195,22 +180,25 @@ static void files_ref_path(struct files_ref_store *refs, struct strbuf *sb, const char *refname) { - switch (ref_type(refname)) { - case REF_TYPE_PER_WORKTREE: - case REF_TYPE_PSEUDOREF: - strbuf_addf(sb, "%s/%s", refs->gitdir, refname); + const char *bare_refname; + const char *wtname; + int wtname_len; + enum ref_worktree_type wt_type = parse_worktree_ref( + refname, &wtname, &wtname_len, &bare_refname); + switch (wt_type) { + case REF_WORKTREE_CURRENT: + strbuf_addf(sb, "%s/%s", refs->base.gitdir, refname); + break; + case REF_WORKTREE_OTHER: + strbuf_addf(sb, "%s/worktrees/%.*s/%s", refs->gitcommondir, + wtname_len, wtname, bare_refname); break; - case REF_TYPE_MAIN_PSEUDOREF: - if (!skip_prefix(refname, "main-worktree/", &refname)) - BUG("ref %s is not a main pseudoref", refname); - /* fallthrough */ - case REF_TYPE_OTHER_PSEUDOREF: - case REF_TYPE_NORMAL: - strbuf_addf(sb, "%s/%s", refs->gitcommondir, refname); + case REF_WORKTREE_SHARED: + case REF_WORKTREE_MAIN: + strbuf_addf(sb, "%s/%s", refs->gitcommondir, bare_refname); break; default: - BUG("unknown ref type %d of ref %s", - ref_type(refname), refname); + BUG("unknown ref type %d of ref %s", wt_type, refname); } } @@ -236,11 +224,43 @@ static void add_per_worktree_entries_to_dir(struct ref_dir *dir, const char *dir pos = search_ref_dir(dir, prefix, prefix_len); if (pos >= 0) continue; - child_entry = create_dir_entry(dir->cache, prefix, prefix_len, 1); + child_entry = create_dir_entry(dir->cache, prefix, prefix_len); add_entry_to_dir(dir, child_entry); } } +static void loose_fill_ref_dir_regular_file(struct files_ref_store *refs, + const char *refname, + struct ref_dir *dir) +{ + struct object_id oid; + int flag; + + if (!refs_resolve_ref_unsafe(&refs->base, refname, RESOLVE_REF_READING, + &oid, &flag)) { + oidclr(&oid); + flag |= REF_ISBROKEN; + } else if (is_null_oid(&oid)) { + /* + * It is so astronomically unlikely + * that null_oid is the OID of an + * actual object that we consider its + * appearance in a loose reference + * file to be repo corruption + * (probably due to a software bug). + */ + flag |= REF_ISBROKEN; + } + + if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) { + if (!refname_is_safe(refname)) + die("loose refname is dangerous: %s", refname); + oidclr(&oid); + flag |= REF_BAD_NAME | REF_ISBROKEN; + } + add_entry_to_dir(dir, create_ref_entry(refname, &oid, flag)); +} + /* * Read the loose references from the namespace dirname into dir * (without recursing). dirname must end with '/'. dir must be the @@ -256,10 +276,8 @@ static void loose_fill_ref_dir(struct ref_store *ref_store, int dirnamelen = strlen(dirname); struct strbuf refname; struct strbuf path = STRBUF_INIT; - size_t path_baselen; files_ref_path(refs, &path, dirname); - path_baselen = path.len; d = opendir(path.buf); if (!d) { @@ -271,54 +289,24 @@ static void loose_fill_ref_dir(struct ref_store *ref_store, strbuf_add(&refname, dirname, dirnamelen); while ((de = readdir(d)) != NULL) { - struct object_id oid; - struct stat st; - int flag; + unsigned char dtype; if (de->d_name[0] == '.') continue; if (ends_with(de->d_name, ".lock")) continue; strbuf_addstr(&refname, de->d_name); - strbuf_addstr(&path, de->d_name); - if (stat(path.buf, &st) < 0) { - ; /* silently ignore */ - } else if (S_ISDIR(st.st_mode)) { + + dtype = get_dtype(de, &path, 1); + if (dtype == DT_DIR) { strbuf_addch(&refname, '/'); add_entry_to_dir(dir, create_dir_entry(dir->cache, refname.buf, - refname.len, 1)); - } else { - if (!refs_resolve_ref_unsafe(&refs->base, - refname.buf, - RESOLVE_REF_READING, - &oid, &flag)) { - oidclr(&oid); - flag |= REF_ISBROKEN; - } else if (is_null_oid(&oid)) { - /* - * It is so astronomically unlikely - * that null_oid is the OID of an - * actual object that we consider its - * appearance in a loose reference - * file to be repo corruption - * (probably due to a software bug). - */ - flag |= REF_ISBROKEN; - } - - if (check_refname_format(refname.buf, - REFNAME_ALLOW_ONELEVEL)) { - if (!refname_is_safe(refname.buf)) - die("loose refname is dangerous: %s", refname.buf); - oidclr(&oid); - flag |= REF_BAD_NAME | REF_ISBROKEN; - } - add_entry_to_dir(dir, - create_ref_entry(refname.buf, &oid, flag)); + refname.len)); + } else if (dtype == DT_REG) { + loose_fill_ref_dir_regular_file(refs, refname.buf, dir); } strbuf_setlen(&refname, dirnamelen); - strbuf_setlen(&path, path_baselen); } strbuf_release(&refname); strbuf_release(&path); @@ -327,9 +315,59 @@ static void loose_fill_ref_dir(struct ref_store *ref_store, add_per_worktree_entries_to_dir(dir, dirname); } -static struct ref_cache *get_loose_ref_cache(struct files_ref_store *refs) +/* + * Add pseudorefs to the ref dir by parsing the directory for any files + * which follow the pseudoref syntax. + */ +static void add_pseudoref_and_head_entries(struct ref_store *ref_store, + struct ref_dir *dir, + const char *dirname) +{ + struct files_ref_store *refs = + files_downcast(ref_store, REF_STORE_READ, "fill_ref_dir"); + struct strbuf path = STRBUF_INIT, refname = STRBUF_INIT; + struct dirent *de; + size_t dirnamelen; + DIR *d; + + files_ref_path(refs, &path, dirname); + + d = opendir(path.buf); + if (!d) { + strbuf_release(&path); + return; + } + + strbuf_addstr(&refname, dirname); + dirnamelen = refname.len; + + while ((de = readdir(d)) != NULL) { + unsigned char dtype; + + if (de->d_name[0] == '.') + continue; + if (ends_with(de->d_name, ".lock")) + continue; + strbuf_addstr(&refname, de->d_name); + + dtype = get_dtype(de, &path, 1); + if (dtype == DT_REG && (is_pseudoref(ref_store, de->d_name) || + is_headref(ref_store, de->d_name))) + loose_fill_ref_dir_regular_file(refs, refname.buf, dir); + + strbuf_setlen(&refname, dirnamelen); + } + strbuf_release(&refname); + strbuf_release(&path); + closedir(d); +} + +static struct ref_cache *get_loose_ref_cache(struct files_ref_store *refs, + unsigned int flags) { if (!refs->loose) { + struct ref_dir *dir; + /* * Mark the top-level directory complete because we * are about to read the only subdirectory that can @@ -340,19 +378,24 @@ static struct ref_cache *get_loose_ref_cache(struct files_ref_store *refs) /* We're going to fill the top level ourselves: */ refs->loose->root->flag &= ~REF_INCOMPLETE; + dir = get_ref_dir(refs->loose->root); + + if (flags & DO_FOR_EACH_INCLUDE_ROOT_REFS) + add_pseudoref_and_head_entries(dir->cache->ref_store, dir, + refs->loose->root->name); + /* * Add an incomplete entry for "refs/" (to be filled * lazily): */ - add_entry_to_dir(get_ref_dir(refs->loose->root), - create_dir_entry(refs->loose, "refs/", 5, 1)); + add_entry_to_dir(dir, create_dir_entry(refs->loose, "refs/", 5)); } return refs->loose; } -static int files_read_raw_ref(struct ref_store *ref_store, - const char *refname, struct object_id *oid, - struct strbuf *referent, unsigned int *type) +static int read_ref_internal(struct ref_store *ref_store, const char *refname, + struct object_id *oid, struct strbuf *referent, + unsigned int *type, int *failure_errno, int skip_packed_refs) { struct files_ref_store *refs = files_downcast(ref_store, REF_STORE_READ, "read_raw_ref"); @@ -360,12 +403,11 @@ static int files_read_raw_ref(struct ref_store *ref_store, struct strbuf sb_path = STRBUF_INIT; const char *path; const char *buf; - const char *p; struct stat st; int fd; int ret = -1; - int save_errno; int remaining_retries = 3; + int myerr = 0; *type = 0; strbuf_reset(&sb_path); @@ -392,11 +434,13 @@ stat_ref: goto out; if (lstat(path, &st) < 0) { - if (errno != ENOENT) + int ignore_errno; + myerr = errno; + if (myerr != ENOENT || skip_packed_refs) goto out; - if (refs_read_raw_ref(refs->packed_ref_store, refname, - oid, referent, type)) { - errno = ENOENT; + if (refs_read_raw_ref(refs->packed_ref_store, refname, oid, + referent, type, &ignore_errno)) { + myerr = ENOENT; goto out; } ret = 0; @@ -407,7 +451,8 @@ stat_ref: if (S_ISLNK(st.st_mode)) { strbuf_reset(&sb_contents); if (strbuf_readlink(&sb_contents, path, st.st_size) < 0) { - if (errno == ENOENT || errno == EINVAL) + myerr = errno; + if (myerr == ENOENT || myerr == EINVAL) /* inconsistent with lstat; retry */ goto stat_ref; else @@ -429,14 +474,16 @@ stat_ref: /* Is it a directory? */ if (S_ISDIR(st.st_mode)) { + int ignore_errno; /* * Even though there is a directory where the loose * ref is supposed to be, there could still be a * packed ref: */ - if (refs_read_raw_ref(refs->packed_ref_store, refname, - oid, referent, type)) { - errno = EISDIR; + if (skip_packed_refs || + refs_read_raw_ref(refs->packed_ref_store, refname, oid, + referent, type, &ignore_errno)) { + myerr = EISDIR; goto out; } ret = 0; @@ -449,7 +496,8 @@ stat_ref: */ fd = open(path, O_RDONLY); if (fd < 0) { - if (errno == ENOENT && !S_ISLNK(st.st_mode)) + myerr = errno; + if (myerr == ENOENT && !S_ISLNK(st.st_mode)) /* inconsistent with lstat; retry */ goto stat_ref; else @@ -457,45 +505,73 @@ stat_ref: } strbuf_reset(&sb_contents); if (strbuf_read(&sb_contents, fd, 256) < 0) { - int save_errno = errno; + myerr = errno; close(fd); - errno = save_errno; goto out; } close(fd); strbuf_rtrim(&sb_contents); buf = sb_contents.buf; - if (starts_with(buf, "ref:")) { - buf += 4; + + ret = parse_loose_ref_contents(buf, oid, referent, type, &myerr); + +out: + if (ret && !myerr) + BUG("returning non-zero %d, should have set myerr!", ret); + *failure_errno = myerr; + + strbuf_release(&sb_path); + strbuf_release(&sb_contents); + errno = 0; + return ret; +} + +static int files_read_raw_ref(struct ref_store *ref_store, const char *refname, + struct object_id *oid, struct strbuf *referent, + unsigned int *type, int *failure_errno) +{ + return read_ref_internal(ref_store, refname, oid, referent, type, failure_errno, 0); +} + +static int files_read_symbolic_ref(struct ref_store *ref_store, const char *refname, + struct strbuf *referent) +{ + struct object_id oid; + int failure_errno, ret; + unsigned int type; + + ret = read_ref_internal(ref_store, refname, &oid, referent, &type, &failure_errno, 1); + if (ret) + return ret; + + return !(type & REF_ISSYMREF); +} + +int parse_loose_ref_contents(const char *buf, struct object_id *oid, + struct strbuf *referent, unsigned int *type, + int *failure_errno) +{ + const char *p; + if (skip_prefix(buf, "ref:", &buf)) { while (isspace(*buf)) buf++; strbuf_reset(referent); strbuf_addstr(referent, buf); *type |= REF_ISSYMREF; - ret = 0; - goto out; + return 0; } /* - * Please note that FETCH_HEAD has additional - * data after the sha. + * FETCH_HEAD has additional data after the sha. */ if (parse_oid_hex(buf, oid, &p) || (*p != '\0' && !isspace(*p))) { *type |= REF_ISBROKEN; - errno = EINVAL; - goto out; + *failure_errno = EINVAL; + return -1; } - - ret = 0; - -out: - save_errno = errno; - strbuf_release(&sb_path); - strbuf_release(&sb_contents); - errno = save_errno; - return ret; + return 0; } static void unlock_ref(struct ref_lock *lock) @@ -537,7 +613,6 @@ static void unlock_ref(struct ref_lock *lock) static int lock_raw_ref(struct files_ref_store *refs, const char *refname, int mustexist, const struct string_list *extras, - const struct string_list *skip, struct ref_lock **lock_p, struct strbuf *referent, unsigned int *type, @@ -547,6 +622,7 @@ static int lock_raw_ref(struct files_ref_store *refs, struct strbuf ref_file = STRBUF_INIT; int attempts_remaining = 3; int ret = TRANSACTION_GENERIC_ERROR; + int failure_errno; assert(err); files_assert_main_repository(refs, "lock_raw_ref"); @@ -555,7 +631,7 @@ static int lock_raw_ref(struct files_ref_store *refs, /* First lock the file so it can't change out from under us. */ - *lock_p = lock = xcalloc(1, sizeof(*lock)); + *lock_p = CALLOC_ARRAY(lock, 1); lock->ref_name = xstrdup(refname); files_ref_path(refs, &ref_file, refname); @@ -574,7 +650,7 @@ retry: * reason to expect this error to be transitory. */ if (refs_verify_refname_available(&refs->base, refname, - extras, skip, err)) { + extras, NULL, err)) { if (mustexist) { /* * To the user the relevant error is @@ -617,7 +693,9 @@ retry: if (hold_lock_file_for_update_timeout( &lock->lk, ref_file.buf, LOCK_NO_DEREF, get_files_ref_lock_timeout_ms()) < 0) { - if (errno == ENOENT && --attempts_remaining > 0) { + int myerr = errno; + errno = 0; + if (myerr == ENOENT && --attempts_remaining > 0) { /* * Maybe somebody just deleted one of the * directories leading to ref_file. Try @@ -625,7 +703,7 @@ retry: */ goto retry; } else { - unable_to_lock_message(ref_file.buf, errno, err); + unable_to_lock_message(ref_file.buf, myerr, err); goto error_return; } } @@ -635,9 +713,9 @@ retry: * fear that its value will change. */ - if (files_read_raw_ref(&refs->base, refname, - &lock->old_oid, referent, type)) { - if (errno == ENOENT) { + if (files_read_raw_ref(&refs->base, refname, &lock->old_oid, referent, + type, &failure_errno)) { + if (failure_errno == ENOENT) { if (mustexist) { /* Garden variety missing reference. */ strbuf_addf(err, "unable to resolve reference '%s'", @@ -661,7 +739,7 @@ retry: * reference named "refs/foo/bar/baz". */ } - } else if (errno == EISDIR) { + } else if (failure_errno == EISDIR) { /* * There is a directory in the way. It might have * contained references that have been deleted. If @@ -679,7 +757,7 @@ retry: REMOVE_DIR_EMPTY_ONLY)) { if (refs_verify_refname_available( &refs->base, refname, - extras, skip, err)) { + extras, NULL, err)) { /* * The error message set by * verify_refname_available() is OK. @@ -699,13 +777,13 @@ retry: goto error_return; } } - } else if (errno == EINVAL && (*type & REF_ISBROKEN)) { + } else if (failure_errno == EINVAL && (*type & REF_ISBROKEN)) { strbuf_addf(err, "unable to resolve reference '%s': " "reference broken", refname); goto error_return; } else { strbuf_addf(err, "unable to resolve reference '%s': %s", - refname, strerror(errno)); + refname, strerror(failure_errno)); goto error_return; } @@ -716,7 +794,7 @@ retry: */ if (refs_verify_refname_available( refs->packed_ref_store, refname, - extras, skip, err)) + extras, NULL, err)) goto error_return; } @@ -736,6 +814,7 @@ struct files_ref_iterator { struct ref_iterator base; struct ref_iterator *iter0; + struct repository *repo; unsigned int flags; }; @@ -747,11 +826,18 @@ static int files_ref_iterator_advance(struct ref_iterator *ref_iterator) while ((ok = ref_iterator_advance(iter->iter0)) == ITER_OK) { if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY && - ref_type(iter->iter0->refname) != REF_TYPE_PER_WORKTREE) + parse_worktree_ref(iter->iter0->refname, NULL, NULL, + NULL) != REF_WORKTREE_CURRENT) + continue; + + if ((iter->flags & DO_FOR_EACH_OMIT_DANGLING_SYMREFS) && + (iter->iter0->flags & REF_ISSYMREF) && + (iter->iter0->flags & REF_ISBROKEN)) continue; if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) && !ref_resolves_to_object(iter->iter0->refname, + iter->repo, iter->iter0->oid, iter->iter0->flags)) continue; @@ -792,14 +878,15 @@ static int files_ref_iterator_abort(struct ref_iterator *ref_iterator) } static struct ref_iterator_vtable files_ref_iterator_vtable = { - files_ref_iterator_advance, - files_ref_iterator_peel, - files_ref_iterator_abort + .advance = files_ref_iterator_advance, + .peel = files_ref_iterator_peel, + .abort = files_ref_iterator_abort, }; static struct ref_iterator *files_ref_iterator_begin( struct ref_store *ref_store, - const char *prefix, unsigned int flags) + const char *prefix, const char **exclude_patterns, + unsigned int flags) { struct files_ref_store *refs; struct ref_iterator *loose_iter, *packed_iter, *overlay_iter; @@ -829,8 +916,8 @@ static struct ref_iterator *files_ref_iterator_begin( * disk, and re-reads it if not. */ - loose_iter = cache_ref_iterator_begin(get_loose_ref_cache(refs), - prefix, 1); + loose_iter = cache_ref_iterator_begin(get_loose_ref_cache(refs, flags), + prefix, ref_store->repo, 1); /* * The packed-refs file might contain broken references, for @@ -844,55 +931,128 @@ static struct ref_iterator *files_ref_iterator_begin( * the packed and loose references. */ packed_iter = refs_ref_iterator_begin( - refs->packed_ref_store, prefix, 0, + refs->packed_ref_store, prefix, exclude_patterns, 0, DO_FOR_EACH_INCLUDE_BROKEN); overlay_iter = overlay_ref_iterator_begin(loose_iter, packed_iter); - iter = xcalloc(1, sizeof(*iter)); + CALLOC_ARRAY(iter, 1); ref_iterator = &iter->base; - base_ref_iterator_init(ref_iterator, &files_ref_iterator_vtable, - overlay_iter->ordered); + base_ref_iterator_init(ref_iterator, &files_ref_iterator_vtable); iter->iter0 = overlay_iter; + iter->repo = ref_store->repo; iter->flags = flags; return ref_iterator; } /* - * Verify that the reference locked by lock has the value old_oid - * (unless it is NULL). Fail if the reference doesn't exist and - * mustexist is set. Return 0 on success. On error, write an error - * message to err, set errno, and return a negative value. + * Callback function for raceproof_create_file(). This function is + * expected to do something that makes dirname(path) permanent despite + * the fact that other processes might be cleaning up empty + * directories at the same time. Usually it will create a file named + * path, but alternatively it could create another file in that + * directory, or even chdir() into that directory. The function should + * return 0 if the action was completed successfully. On error, it + * should return a nonzero result and set errno. + * raceproof_create_file() treats two errno values specially: + * + * - ENOENT -- dirname(path) does not exist. In this case, + * raceproof_create_file() tries creating dirname(path) + * (and any parent directories, if necessary) and calls + * the function again. + * + * - EISDIR -- the file already exists and is a directory. In this + * case, raceproof_create_file() removes the directory if + * it is empty (and recursively any empty directories that + * it contains) and calls the function again. + * + * Any other errno causes raceproof_create_file() to fail with the + * callback's return value and errno. + * + * Obviously, this function should be OK with being called again if it + * fails with ENOENT or EISDIR. In other scenarios it will not be + * called again. + */ +typedef int create_file_fn(const char *path, void *cb); + +/* + * Create a file in dirname(path) by calling fn, creating leading + * directories if necessary. Retry a few times in case we are racing + * with another process that is trying to clean up the directory that + * contains path. See the documentation for create_file_fn for more + * details. + * + * Return the value and set the errno that resulted from the most + * recent call of fn. fn is always called at least once, and will be + * called more than once if it returns ENOENT or EISDIR. */ -static int verify_lock(struct ref_store *ref_store, struct ref_lock *lock, - const struct object_id *old_oid, int mustexist, - struct strbuf *err) +static int raceproof_create_file(const char *path, create_file_fn fn, void *cb) { - assert(err); + /* + * The number of times we will try to remove empty directories + * in the way of path. This is only 1 because if another + * process is racily creating directories that conflict with + * us, we don't want to fight against them. + */ + int remove_directories_remaining = 1; - if (refs_read_ref_full(ref_store, lock->ref_name, - mustexist ? RESOLVE_REF_READING : 0, - &lock->old_oid, NULL)) { - if (old_oid) { - int save_errno = errno; - strbuf_addf(err, "can't verify ref '%s'", lock->ref_name); - errno = save_errno; - return -1; - } else { - oidclr(&lock->old_oid); - return 0; - } - } - if (old_oid && !oideq(&lock->old_oid, old_oid)) { - strbuf_addf(err, "ref '%s' is at %s but expected %s", - lock->ref_name, - oid_to_hex(&lock->old_oid), - oid_to_hex(old_oid)); - errno = EBUSY; - return -1; + /* + * The number of times that we will try to create the + * directories containing path. We are willing to attempt this + * more than once, because another process could be trying to + * clean up empty directories at the same time as we are + * trying to create them. + */ + int create_directories_remaining = 3; + + /* A scratch copy of path, filled lazily if we need it: */ + struct strbuf path_copy = STRBUF_INIT; + + int ret, save_errno; + + /* Sanity check: */ + assert(*path); + +retry_fn: + ret = fn(path, cb); + save_errno = errno; + if (!ret) + goto out; + + if (errno == EISDIR && remove_directories_remaining-- > 0) { + /* + * A directory is in the way. Maybe it is empty; try + * to remove it: + */ + if (!path_copy.len) + strbuf_addstr(&path_copy, path); + + if (!remove_dir_recursively(&path_copy, REMOVE_DIR_EMPTY_ONLY)) + goto retry_fn; + } else if (errno == ENOENT && create_directories_remaining-- > 0) { + /* + * Maybe the containing directory didn't exist, or + * maybe it was just deleted by a process that is + * racing with us to clean up empty directories. Try + * to create it: + */ + enum scld_error scld_result; + + if (!path_copy.len) + strbuf_addstr(&path_copy, path); + + do { + scld_result = safe_create_leading_directories(path_copy.buf); + if (scld_result == SCLD_OK) + goto retry_fn; + } while (scld_result == SCLD_VANISHED && create_directories_remaining-- > 0); } - return 0; + +out: + strbuf_release(&path_copy); + errno = save_errno; + return ret; } static int remove_empty_directories(struct strbuf *path) @@ -916,67 +1076,20 @@ static int create_reflock(const char *path, void *cb) /* * Locks a ref returning the lock on success and NULL on failure. - * On failure errno is set to something meaningful. */ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs, const char *refname, - const struct object_id *old_oid, - const struct string_list *extras, - const struct string_list *skip, - unsigned int flags, int *type, struct strbuf *err) { struct strbuf ref_file = STRBUF_INIT; struct ref_lock *lock; - int last_errno = 0; - int mustexist = (old_oid && !is_null_oid(old_oid)); - int resolve_flags = RESOLVE_REF_NO_RECURSE; - int resolved; files_assert_main_repository(refs, "lock_ref_oid_basic"); assert(err); - lock = xcalloc(1, sizeof(struct ref_lock)); - - if (mustexist) - resolve_flags |= RESOLVE_REF_READING; - if (flags & REF_DELETING) - resolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME; + CALLOC_ARRAY(lock, 1); files_ref_path(refs, &ref_file, refname); - resolved = !!refs_resolve_ref_unsafe(&refs->base, - refname, resolve_flags, - &lock->old_oid, type); - if (!resolved && errno == EISDIR) { - /* - * we are trying to lock foo but we used to - * have foo/bar which now does not exist; - * it is normal for the empty directory 'foo' - * to remain. - */ - if (remove_empty_directories(&ref_file)) { - last_errno = errno; - if (!refs_verify_refname_available( - &refs->base, - refname, extras, skip, err)) - strbuf_addf(err, "there are still refs under '%s'", - refname); - goto error_return; - } - resolved = !!refs_resolve_ref_unsafe(&refs->base, - refname, resolve_flags, - &lock->old_oid, type); - } - if (!resolved) { - last_errno = errno; - if (last_errno != ENOTDIR || - !refs_verify_refname_available(&refs->base, refname, - extras, skip, err)) - strbuf_addf(err, "unable to resolve reference '%s': %s", - refname, strerror(last_errno)); - - goto error_return; - } /* * If the ref did not exist and we are creating it, make sure @@ -986,23 +1099,19 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs, */ if (is_null_oid(&lock->old_oid) && refs_verify_refname_available(refs->packed_ref_store, refname, - extras, skip, err)) { - last_errno = ENOTDIR; + NULL, NULL, err)) goto error_return; - } lock->ref_name = xstrdup(refname); if (raceproof_create_file(ref_file.buf, create_reflock, &lock->lk)) { - last_errno = errno; unable_to_lock_message(ref_file.buf, errno, err); goto error_return; } - if (verify_lock(&refs->base, lock, old_oid, mustexist, err)) { - last_errno = errno; - goto error_return; - } + if (!refs_resolve_ref_unsafe(&refs->base, lock->ref_name, 0, + &lock->old_oid, NULL)) + oidclr(&lock->old_oid); goto out; error_return: @@ -1011,7 +1120,6 @@ static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs, out: strbuf_release(&ref_file); - errno = last_errno; return lock; } @@ -1090,7 +1198,7 @@ static void prune_ref(struct files_ref_store *refs, struct ref_to_prune *r) ref_transaction_add_update( transaction, r->name, REF_NO_DEREF | REF_HAVE_NEW | REF_HAVE_OLD | REF_IS_PRUNING, - &null_oid, &r->oid, NULL); + null_oid(), &r->oid, NULL); if (ref_transaction_commit(transaction, &err)) goto cleanup; @@ -1123,14 +1231,13 @@ static void prune_refs(struct files_ref_store *refs, struct ref_to_prune **refs_ */ static int should_pack_ref(const char *refname, const struct object_id *oid, unsigned int ref_flags, - unsigned int pack_flags) + struct pack_refs_opts *opts) { - /* Do not pack per-worktree refs: */ - if (ref_type(refname) != REF_TYPE_NORMAL) - return 0; + struct string_list_item *item; - /* Do not pack non-tags unless PACK_REFS_ALL is set: */ - if (!(pack_flags & PACK_REFS_ALL) && !starts_with(refname, "refs/tags/")) + /* Do not pack per-worktree refs: */ + if (parse_worktree_ref(refname, NULL, NULL, NULL) != + REF_WORKTREE_SHARED) return 0; /* Do not pack symbolic refs: */ @@ -1138,13 +1245,21 @@ static int should_pack_ref(const char *refname, return 0; /* Do not pack broken refs: */ - if (!ref_resolves_to_object(refname, oid, ref_flags)) + if (!ref_resolves_to_object(refname, the_repository, oid, ref_flags)) return 0; - return 1; + if (ref_excluded(opts->exclusions, refname)) + return 0; + + for_each_string_list_item(item, opts->includes) + if (!wildmatch(item->string, refname, 0)) + return 1; + + return 0; } -static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) +static int files_pack_refs(struct ref_store *ref_store, + struct pack_refs_opts *opts) { struct files_ref_store *refs = files_downcast(ref_store, REF_STORE_WRITE | REF_STORE_ODB, @@ -1161,15 +1276,15 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) packed_refs_lock(refs->packed_ref_store, LOCK_DIE_ON_ERROR, &err); - iter = cache_ref_iterator_begin(get_loose_ref_cache(refs), NULL, 0); + iter = cache_ref_iterator_begin(get_loose_ref_cache(refs, 0), NULL, + the_repository, 0); while ((ok = ref_iterator_advance(iter)) == ITER_OK) { /* * If the loose reference can be packed, add an entry * in the packed ref cache. If the reference should be * pruned, also add it to refs_to_prune. */ - if (!should_pack_ref(iter->refname, iter->oid, iter->flags, - flags)) + if (!should_pack_ref(iter->refname, iter->oid, iter->flags, opts)) continue; /* @@ -1183,7 +1298,7 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) iter->refname, err.buf); /* Schedule the loose reference for pruning if requested. */ - if ((flags & PACK_REFS_PRUNE)) { + if ((opts->flags & PACK_REFS_PRUNE)) { struct ref_to_prune *n; FLEX_ALLOC_STR(n, name, iter->refname); oidcpy(&n->oid, iter->oid); @@ -1206,54 +1321,6 @@ static int files_pack_refs(struct ref_store *ref_store, unsigned int flags) return 0; } -static int files_delete_refs(struct ref_store *ref_store, const char *msg, - struct string_list *refnames, unsigned int flags) -{ - struct files_ref_store *refs = - files_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); - struct strbuf err = STRBUF_INIT; - int i, result = 0; - - if (!refnames->nr) - return 0; - - if (packed_refs_lock(refs->packed_ref_store, 0, &err)) - goto error; - - if (refs_delete_refs(refs->packed_ref_store, msg, refnames, flags)) { - packed_refs_unlock(refs->packed_ref_store); - goto error; - } - - packed_refs_unlock(refs->packed_ref_store); - - for (i = 0; i < refnames->nr; i++) { - const char *refname = refnames->items[i].string; - - if (refs_delete_ref(&refs->base, msg, refname, NULL, flags)) - result |= error(_("could not remove reference %s"), refname); - } - - strbuf_release(&err); - return result; - -error: - /* - * If we failed to rewrite the packed-refs file, then it is - * unsafe to try to remove loose refs, because doing so might - * expose an obsolete packed value for a reference that might - * even point at an object that has been garbage collected. - */ - if (refnames->nr == 1) - error(_("could not delete reference %s: %s"), - refnames->items[0].string, err.buf); - else - error(_("could not delete references: %s"), err.buf); - - strbuf_release(&err); - return -1; -} - /* * People using contrib's git-new-workdir have .git/logs/refs -> * /some/other/path/.git/logs/refs, and that may live on another device. @@ -1315,19 +1382,49 @@ static int rename_tmp_log(struct files_ref_store *refs, const char *newrefname) } static int write_ref_to_lockfile(struct ref_lock *lock, - const struct object_id *oid, struct strbuf *err); + const struct object_id *oid, + int skip_oid_verification, struct strbuf *err); static int commit_ref_update(struct files_ref_store *refs, struct ref_lock *lock, const struct object_id *oid, const char *logmsg, struct strbuf *err); +/* + * Emit a better error message than lockfile.c's + * unable_to_lock_message() would in case there is a D/F conflict with + * another existing reference. If there would be a conflict, emit an error + * message and return false; otherwise, return true. + * + * Note that this function is not safe against all races with other + * processes, and that's not its job. We'll emit a more verbose error on D/f + * conflicts if we get past it into lock_ref_oid_basic(). + */ +static int refs_rename_ref_available(struct ref_store *refs, + const char *old_refname, + const char *new_refname) +{ + struct string_list skip = STRING_LIST_INIT_NODUP; + struct strbuf err = STRBUF_INIT; + int ok; + + string_list_insert(&skip, old_refname); + ok = !refs_verify_refname_available(refs, new_refname, + NULL, &skip, &err); + if (!ok) + error("%s", err.buf); + + string_list_clear(&skip, 0); + strbuf_release(&err); + return ok; +} + static int files_copy_or_rename_ref(struct ref_store *ref_store, const char *oldrefname, const char *newrefname, const char *logmsg, int copy) { struct files_ref_store *refs = files_downcast(ref_store, REF_STORE_WRITE, "rename_ref"); - struct object_id oid, orig_oid; + struct object_id orig_oid; int flag = 0, logmoved = 0; struct ref_lock *lock; struct stat loginfo; @@ -1349,7 +1446,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store, if (!refs_resolve_ref_unsafe(&refs->base, oldrefname, RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, - &orig_oid, &flag)) { + &orig_oid, &flag)) { ret = error("refname %s not found", oldrefname); goto out; } @@ -1393,9 +1490,9 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store, * the safety anyway; we want to delete the reference whatever * its current value. */ - if (!copy && !refs_read_ref_full(&refs->base, newrefname, - RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, - &oid, NULL) && + if (!copy && refs_resolve_ref_unsafe(&refs->base, newrefname, + RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, + NULL, NULL) && refs_delete_ref(&refs->base, NULL, newrefname, NULL, REF_NO_DEREF)) { if (errno == EISDIR) { @@ -1421,8 +1518,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store, logmoved = log; - lock = lock_ref_oid_basic(refs, newrefname, NULL, NULL, NULL, - REF_NO_DEREF, NULL, &err); + lock = lock_ref_oid_basic(refs, newrefname, &err); if (!lock) { if (copy) error("unable to copy '%s' to '%s': %s", oldrefname, newrefname, err.buf); @@ -1433,7 +1529,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store, } oidcpy(&lock->old_oid, &orig_oid); - if (write_ref_to_lockfile(lock, &orig_oid, &err) || + if (write_ref_to_lockfile(lock, &orig_oid, 0, &err) || commit_ref_update(refs, lock, &orig_oid, logmsg, &err)) { error("unable to write current sha1 into %s: %s", newrefname, err.buf); strbuf_release(&err); @@ -1444,8 +1540,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store, goto out; rollback: - lock = lock_ref_oid_basic(refs, oldrefname, NULL, NULL, NULL, - REF_NO_DEREF, NULL, &err); + lock = lock_ref_oid_basic(refs, oldrefname, &err); if (!lock) { error("unable to lock %s for rollback: %s", oldrefname, err.buf); strbuf_release(&err); @@ -1454,7 +1549,7 @@ static int files_copy_or_rename_ref(struct ref_store *ref_store, flag = log_all_ref_updates; log_all_ref_updates = LOG_REFS_NONE; - if (write_ref_to_lockfile(lock, &orig_oid, &err) || + if (write_ref_to_lockfile(lock, &orig_oid, 0, &err) || commit_ref_update(refs, lock, &orig_oid, NULL, &err)) { error("unable to write current sha1 into %s: %s", oldrefname, err.buf); strbuf_release(&err); @@ -1575,7 +1670,7 @@ static int log_ref_setup(struct files_ref_store *refs, goto error; } } else { - *logfd = open(logfile, O_APPEND | O_WRONLY, 0666); + *logfd = open(logfile, O_APPEND | O_WRONLY); if (*logfd < 0) { if (errno == ENOENT || errno == EISDIR) { /* @@ -1604,15 +1699,14 @@ error: return -1; } -static int files_create_reflog(struct ref_store *ref_store, - const char *refname, int force_create, +static int files_create_reflog(struct ref_store *ref_store, const char *refname, struct strbuf *err) { struct files_ref_store *refs = files_downcast(ref_store, REF_STORE_WRITE, "create_reflog"); int fd; - if (log_ref_setup(refs, refname, force_create, &fd, err)) + if (log_ref_setup(refs, refname, 1, &fd, err)) return -1; if (fd >= 0) @@ -1629,8 +1723,10 @@ static int log_ref_write_fd(int fd, const struct object_id *old_oid, int ret = 0; strbuf_addf(&sb, "%s %s %s", oid_to_hex(old_oid), oid_to_hex(new_oid), committer); - if (msg && *msg) - copy_reflog_msg(&sb, msg); + if (msg && *msg) { + strbuf_addch(&sb, '\t'); + strbuf_addstr(&sb, msg); + } strbuf_addch(&sb, '\n'); if (write_in_full(fd, sb.buf, sb.len) < 0) ret = -1; @@ -1688,30 +1784,36 @@ static int files_log_ref_write(struct files_ref_store *refs, * errors, rollback the lockfile, fill in *err and return -1. */ static int write_ref_to_lockfile(struct ref_lock *lock, - const struct object_id *oid, struct strbuf *err) + const struct object_id *oid, + int skip_oid_verification, struct strbuf *err) { static char term = '\n'; struct object *o; int fd; - o = parse_object(the_repository, oid); - if (!o) { - strbuf_addf(err, - "trying to write ref '%s' with nonexistent object %s", - lock->ref_name, oid_to_hex(oid)); - unlock_ref(lock); - return -1; - } - if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) { - strbuf_addf(err, - "trying to write non-commit object %s to branch '%s'", - oid_to_hex(oid), lock->ref_name); - unlock_ref(lock); - return -1; + if (!skip_oid_verification) { + o = parse_object(the_repository, oid); + if (!o) { + strbuf_addf( + err, + "trying to write ref '%s' with nonexistent object %s", + lock->ref_name, oid_to_hex(oid)); + unlock_ref(lock); + return -1; + } + if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) { + strbuf_addf( + err, + "trying to write non-commit object %s to branch '%s'", + oid_to_hex(oid), lock->ref_name); + unlock_ref(lock); + return -1; + } } fd = get_lock_file_fd(&lock->lk); if (write_in_full(fd, oid_to_hex(oid), the_hash_algo->hexsz) < 0 || write_in_full(fd, &term, 1) < 0 || + fsync_component(FSYNC_COMPONENT_REFERENCE, get_lock_file_fd(&lock->lk)) < 0 || close_ref_gently(lock) < 0) { strbuf_addf(err, "couldn't write '%s'", get_lock_file_path(&lock->lk)); @@ -1807,9 +1909,10 @@ static void update_symref_reflog(struct files_ref_store *refs, { struct strbuf err = STRBUF_INIT; struct object_id new_oid; + if (logmsg && - !refs_read_ref_full(&refs->base, target, - RESOLVE_REF_READING, &new_oid, NULL) && + refs_resolve_ref_unsafe(&refs->base, target, + RESOLVE_REF_READING, &new_oid, NULL) && files_log_ref_write(refs, refname, &lock->old_oid, &new_oid, logmsg, 0, &err)) { error("%s", err.buf); @@ -1828,12 +1931,12 @@ static int create_symref_locked(struct files_ref_store *refs, if (!fdopen_lock_file(&lock->lk, "w")) return error("unable to fdopen %s: %s", - lock->lk.tempfile->filename.buf, strerror(errno)); + get_lock_file_path(&lock->lk), strerror(errno)); update_symref_reflog(refs, lock, refname, target, logmsg); /* no error check; commit_ref will check ferror */ - fprintf(lock->lk.tempfile->fp, "ref: %s\n", target); + fprintf(get_lock_file_fp(&lock->lk), "ref: %s\n", target); if (commit_ref(lock) < 0) return error("unable to write symref for %s: %s", refname, strerror(errno)); @@ -1850,9 +1953,7 @@ static int files_create_symref(struct ref_store *ref_store, struct ref_lock *lock; int ret; - lock = lock_ref_oid_basic(refs, refname, NULL, - NULL, NULL, REF_NO_DEREF, NULL, - &err); + lock = lock_ref_oid_basic(refs, refname, &err); if (!lock) { error("%s", err.buf); strbuf_release(&err); @@ -2073,10 +2174,8 @@ static int files_for_each_reflog_ent(struct ref_store *ref_store, struct files_reflog_iterator { struct ref_iterator base; - struct ref_store *ref_store; struct dir_iterator *dir_iterator; - struct object_id oid; }; static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator) @@ -2087,25 +2186,13 @@ static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator) int ok; while ((ok = dir_iterator_advance(diter)) == ITER_OK) { - int flags; - if (!S_ISREG(diter->st.st_mode)) continue; - if (diter->basename[0] == '.') + if (check_refname_format(diter->basename, + REFNAME_ALLOW_ONELEVEL)) continue; - if (ends_with(diter->basename, ".lock")) - continue; - - if (refs_read_ref_full(iter->ref_store, - diter->relative_path, 0, - &iter->oid, &flags)) { - error("bad ref for %s", diter->path.buf); - continue; - } iter->base.refname = diter->relative_path; - iter->base.oid = &iter->oid; - iter->base.flags = flags; return ITER_OK; } @@ -2115,8 +2202,8 @@ static int files_reflog_iterator_advance(struct ref_iterator *ref_iterator) return ok; } -static int files_reflog_iterator_peel(struct ref_iterator *ref_iterator, - struct object_id *peeled) +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"); } @@ -2135,9 +2222,9 @@ static int files_reflog_iterator_abort(struct ref_iterator *ref_iterator) } static struct ref_iterator_vtable files_reflog_iterator_vtable = { - files_reflog_iterator_advance, - files_reflog_iterator_peel, - files_reflog_iterator_abort + .advance = files_reflog_iterator_advance, + .peel = files_reflog_iterator_peel, + .abort = files_reflog_iterator_abort, }; static struct ref_iterator *reflog_iterator_begin(struct ref_store *ref_store, @@ -2150,16 +2237,16 @@ static struct ref_iterator *reflog_iterator_begin(struct ref_store *ref_store, strbuf_addf(&sb, "%s/logs", gitdir); - diter = dir_iterator_begin(sb.buf, 0); + diter = dir_iterator_begin(sb.buf, DIR_ITERATOR_SORTED); if (!diter) { strbuf_release(&sb); return empty_ref_iterator_begin(); } - iter = xcalloc(1, sizeof(*iter)); + CALLOC_ARRAY(iter, 1); ref_iterator = &iter->base; - base_ref_iterator_init(ref_iterator, &files_reflog_iterator_vtable, 0); + base_ref_iterator_init(ref_iterator, &files_reflog_iterator_vtable); iter->dir_iterator = diter; iter->ref_store = ref_store; strbuf_release(&sb); @@ -2167,45 +2254,19 @@ static struct ref_iterator *reflog_iterator_begin(struct ref_store *ref_store, return ref_iterator; } -static enum iterator_selection reflog_iterator_select( - struct ref_iterator *iter_worktree, - struct ref_iterator *iter_common, - void *cb_data) -{ - if (iter_worktree) { - /* - * We're a bit loose here. We probably should ignore - * common refs if they are accidentally added as - * per-worktree refs. - */ - return ITER_SELECT_0; - } else if (iter_common) { - if (ref_type(iter_common->refname) == REF_TYPE_NORMAL) - return ITER_SELECT_1; - - /* - * The main ref store may contain main worktree's - * per-worktree refs, which should be ignored - */ - return ITER_SKIP_1; - } else - return ITER_DONE; -} - static struct ref_iterator *files_reflog_iterator_begin(struct ref_store *ref_store) { struct files_ref_store *refs = files_downcast(ref_store, REF_STORE_READ, "reflog_iterator_begin"); - if (!strcmp(refs->gitdir, refs->gitcommondir)) { + if (!strcmp(refs->base.gitdir, refs->gitcommondir)) { return reflog_iterator_begin(ref_store, refs->gitcommondir); } else { return merge_ref_iterator_begin( - 0, - reflog_iterator_begin(ref_store, refs->gitdir), + reflog_iterator_begin(ref_store, refs->base.gitdir), reflog_iterator_begin(ref_store, refs->gitcommondir), - reflog_iterator_select, refs); + ref_iterator_select, refs); } } @@ -2421,7 +2482,7 @@ static int lock_ref_for_update(struct files_ref_store *refs, } ret = lock_raw_ref(refs, update->refname, mustexist, - affected_refnames, NULL, + affected_refnames, &lock, &referent, &update->type, err); if (ret) { @@ -2443,9 +2504,9 @@ static int lock_ref_for_update(struct files_ref_store *refs, * the transaction, so we have to read it here * to record and possibly check old_oid: */ - if (refs_read_ref_full(&refs->base, - referent.buf, 0, - &lock->old_oid, NULL)) { + if (!refs_resolve_ref_unsafe(&refs->base, + referent.buf, 0, + &lock->old_oid, NULL)) { if (update->flags & REF_HAVE_OLD) { strbuf_addf(err, "cannot lock ref '%s': " "error reading reference", @@ -2501,8 +2562,10 @@ static int lock_ref_for_update(struct files_ref_store *refs, * The reference already has the desired * value, so we don't need to write it. */ - } else if (write_ref_to_lockfile(lock, &update->new_oid, - err)) { + } else if (write_ref_to_lockfile( + lock, &update->new_oid, + update->flags & REF_SKIP_OID_VERIFICATION, + err)) { char *write_err = strbuf_detach(err, NULL); /* @@ -2566,16 +2629,18 @@ static void files_transaction_cleanup(struct files_ref_store *refs, } } - if (backend_data->packed_transaction && - ref_transaction_abort(backend_data->packed_transaction, &err)) { - error("error aborting transaction: %s", err.buf); - strbuf_release(&err); - } + if (backend_data) { + if (backend_data->packed_transaction && + ref_transaction_abort(backend_data->packed_transaction, &err)) { + error("error aborting transaction: %s", err.buf); + strbuf_release(&err); + } - if (backend_data->packed_refs_locked) - packed_refs_unlock(refs->packed_ref_store); + if (backend_data->packed_refs_locked) + packed_refs_unlock(refs->packed_ref_store); - free(backend_data); + free(backend_data); + } transaction->state = REF_TRANSACTION_CLOSED; } @@ -2600,7 +2665,7 @@ static int files_transaction_prepare(struct ref_store *ref_store, if (!transaction->nr) goto cleanup; - backend_data = xcalloc(1, sizeof(*backend_data)); + CALLOC_ARRAY(backend_data, 1); transaction->backend_data = backend_data; /* @@ -2855,6 +2920,7 @@ static int files_transaction_finish(struct ref_store *ref_store, if (update->flags & REF_DELETING && !(update->flags & REF_LOG_ONLY)) { + update->flags |= REF_DELETED_RMDIR; if (!(update->type & REF_ISPACKED) || update->type & REF_ISSYMREF) { /* It is a loose reference. */ @@ -2864,7 +2930,6 @@ static int files_transaction_finish(struct ref_store *ref_store, ret = TRANSACTION_GENERIC_ERROR; goto cleanup; } - update->flags |= REF_DELETED_LOOSE; } } } @@ -2877,9 +2942,9 @@ cleanup: for (i = 0; i < transaction->nr; i++) { struct ref_update *update = transaction->updates[i]; - if (update->flags & REF_DELETED_LOOSE) { + if (update->flags & REF_DELETED_RMDIR) { /* - * The loose reference was deleted. Delete any + * The reference was deleted. Delete any * empty parent directories. (Note that this * can only work because we have already * removed the lockfile.) @@ -2895,7 +2960,7 @@ cleanup: static int files_transaction_abort(struct ref_store *ref_store, struct ref_transaction *transaction, - struct strbuf *err) + struct strbuf *err UNUSED) { struct files_ref_store *refs = files_downcast(ref_store, 0, "ref_transaction_abort"); @@ -2905,7 +2970,9 @@ static int files_transaction_abort(struct ref_store *ref_store, } static int ref_present(const char *refname, - const struct object_id *oid, int flags, void *cb_data) + const struct object_id *oid UNUSED, + int flags UNUSED, + void *cb_data) { struct string_list *affected_refnames = cb_data; @@ -3003,11 +3070,12 @@ cleanup: } struct expire_reflog_cb { - unsigned int flags; reflog_expiry_should_prune_fn *should_prune_fn; void *policy_cb; FILE *newlog; struct object_id last_kept_oid; + unsigned int rewrite:1, + dry_run:1; }; static int expire_reflog_ent(struct object_id *ooid, struct object_id *noid, @@ -3015,33 +3083,27 @@ static int expire_reflog_ent(struct object_id *ooid, struct object_id *noid, const char *message, void *cb_data) { struct expire_reflog_cb *cb = cb_data; - struct expire_reflog_policy_cb *policy_cb = cb->policy_cb; + reflog_expiry_should_prune_fn *fn = cb->should_prune_fn; - if (cb->flags & EXPIRE_REFLOGS_REWRITE) + if (cb->rewrite) ooid = &cb->last_kept_oid; - if ((*cb->should_prune_fn)(ooid, noid, email, timestamp, tz, - message, policy_cb)) { - if (!cb->newlog) - printf("would prune %s", message); - else if (cb->flags & EXPIRE_REFLOGS_VERBOSE) - printf("prune %s", message); - } else { - if (cb->newlog) { - fprintf(cb->newlog, "%s %s %s %"PRItime" %+05d\t%s", - oid_to_hex(ooid), oid_to_hex(noid), - email, timestamp, tz, message); - oidcpy(&cb->last_kept_oid, noid); - } - if (cb->flags & EXPIRE_REFLOGS_VERBOSE) - printf("keep %s", message); - } + if (fn(ooid, noid, email, timestamp, tz, message, cb->policy_cb)) + return 0; + + if (cb->dry_run) + return 0; /* --dry-run */ + + fprintf(cb->newlog, "%s %s %s %"PRItime" %+05d\t%s", oid_to_hex(ooid), + oid_to_hex(noid), email, timestamp, tz, message); + oidcpy(&cb->last_kept_oid, noid); + return 0; } static int files_reflog_expire(struct ref_store *ref_store, - const char *refname, const struct object_id *oid, - unsigned int flags, + const char *refname, + unsigned int expire_flags, reflog_expiry_prepare_fn prepare_fn, reflog_expiry_should_prune_fn should_prune_fn, reflog_expiry_cleanup_fn cleanup_fn, @@ -3055,11 +3117,12 @@ static int files_reflog_expire(struct ref_store *ref_store, struct strbuf log_file_sb = STRBUF_INIT; char *log_file; int status = 0; - int type; struct strbuf err = STRBUF_INIT; + const struct object_id *oid; memset(&cb, 0, sizeof(cb)); - cb.flags = flags; + cb.rewrite = !!(expire_flags & EXPIRE_REFLOGS_REWRITE); + cb.dry_run = !!(expire_flags & EXPIRE_REFLOGS_DRY_RUN); cb.policy_cb = policy_cb_data; cb.should_prune_fn = should_prune_fn; @@ -3068,14 +3131,26 @@ static int files_reflog_expire(struct ref_store *ref_store, * reference itself, plus we might need to update the * reference if --updateref was specified: */ - lock = lock_ref_oid_basic(refs, refname, oid, - NULL, NULL, REF_NO_DEREF, - &type, &err); + lock = lock_ref_oid_basic(refs, refname, &err); if (!lock) { error("cannot lock ref '%s': %s", refname, err.buf); strbuf_release(&err); return -1; } + oid = &lock->old_oid; + + /* + * When refs are deleted, their reflog is deleted before the + * ref itself is deleted. This is because there is no separate + * lock for reflog; instead we take a lock on the ref with + * lock_ref_oid_basic(). + * + * If a race happens and the reflog doesn't exist after we've + * acquired the lock that's OK. We've got nothing more to do; + * We were asked to delete the reflog, but someone else + * deleted it! The caller doesn't care that we deleted it, + * just that it is deleted. So we can return successfully. + */ if (!refs_reflog_exists(ref_store, refname)) { unlock_ref(lock); return 0; @@ -3083,7 +3158,7 @@ static int files_reflog_expire(struct ref_store *ref_store, files_reflog_path(refs, &log_file_sb, refname); log_file = strbuf_detach(&log_file_sb, NULL); - if (!(flags & EXPIRE_REFLOGS_DRY_RUN)) { + if (!cb.dry_run) { /* * Even though holding $GIT_DIR/logs/$reflog.lock has * no locking implications, we use the lock_file @@ -3110,7 +3185,7 @@ static int files_reflog_expire(struct ref_store *ref_store, refs_for_each_reflog_ent(ref_store, refname, expire_reflog_ent, &cb); (*cleanup_fn)(cb.policy_cb); - if (!(flags & EXPIRE_REFLOGS_DRY_RUN)) { + if (!cb.dry_run) { /* * It doesn't make sense to adjust a reference pointed * to by a symbolic ref based on expiring entries in @@ -3118,9 +3193,18 @@ static int files_reflog_expire(struct ref_store *ref_store, * a reference if there are no remaining reflog * entries. */ - int update = (flags & EXPIRE_REFLOGS_UPDATE_REF) && - !(type & REF_ISSYMREF) && - !is_null_oid(&cb.last_kept_oid); + int update = 0; + + if ((expire_flags & EXPIRE_REFLOGS_UPDATE_REF) && + !is_null_oid(&cb.last_kept_oid)) { + int type; + const char *ref; + + ref = refs_resolve_ref_unsafe(&refs->base, refname, + RESOLVE_REF_NO_RECURSE, + NULL, &type); + update = !!(ref && !(type & REF_ISSYMREF)); + } if (close_lock_file_gently(&reflog_lock)) { status |= error("couldn't write %s: %s", log_file, @@ -3152,50 +3236,74 @@ static int files_reflog_expire(struct ref_store *ref_store, return -1; } -static int files_init_db(struct ref_store *ref_store, struct strbuf *err) +static int files_init_db(struct ref_store *ref_store, + int flags, + struct strbuf *err UNUSED) { struct files_ref_store *refs = files_downcast(ref_store, REF_STORE_WRITE, "init_db"); struct strbuf sb = STRBUF_INIT; /* - * Create .git/refs/{heads,tags} + * We need to create a "refs" dir in any case so that older versions of + * Git can tell that this is a repository. This serves two main purposes: + * + * - Clients will know to stop walking the parent-directory chain when + * detecting the Git repository. Otherwise they may end up detecting + * a Git repository in a parent directory instead. + * + * - Instead of failing to detect a repository with unknown reference + * format altogether, old clients will print an error saying that + * they do not understand the reference format extension. */ - files_ref_path(refs, &sb, "refs/heads"); + strbuf_addf(&sb, "%s/refs", ref_store->gitdir); safe_create_dir(sb.buf, 1); + adjust_shared_perm(sb.buf); - strbuf_reset(&sb); - files_ref_path(refs, &sb, "refs/tags"); - safe_create_dir(sb.buf, 1); + /* + * There is no need to create directories for common refs when creating + * a worktree ref store. + */ + if (!(flags & REFS_INIT_DB_IS_WORKTREE)) { + /* + * Create .git/refs/{heads,tags} + */ + strbuf_reset(&sb); + files_ref_path(refs, &sb, "refs/heads"); + safe_create_dir(sb.buf, 1); + + strbuf_reset(&sb); + files_ref_path(refs, &sb, "refs/tags"); + safe_create_dir(sb.buf, 1); + } strbuf_release(&sb); return 0; } struct ref_storage_be refs_be_files = { - NULL, - "files", - files_ref_store_create, - files_init_db, - files_transaction_prepare, - files_transaction_finish, - files_transaction_abort, - files_initial_transaction_commit, - - files_pack_refs, - files_create_symref, - files_delete_refs, - files_rename_ref, - files_copy_ref, - - files_ref_iterator_begin, - files_read_raw_ref, - - files_reflog_iterator_begin, - files_for_each_reflog_ent, - files_for_each_reflog_ent_reverse, - files_reflog_exists, - files_create_reflog, - files_delete_reflog, - files_reflog_expire + .name = "files", + .init = files_ref_store_create, + .init_db = files_init_db, + .transaction_prepare = files_transaction_prepare, + .transaction_finish = files_transaction_finish, + .transaction_abort = files_transaction_abort, + .initial_transaction_commit = files_initial_transaction_commit, + + .pack_refs = files_pack_refs, + .create_symref = files_create_symref, + .rename_ref = files_rename_ref, + .copy_ref = files_copy_ref, + + .iterator_begin = files_ref_iterator_begin, + .read_raw_ref = files_read_raw_ref, + .read_symbolic_ref = files_read_symbolic_ref, + + .reflog_iterator_begin = files_reflog_iterator_begin, + .for_each_reflog_ent = files_for_each_reflog_ent, + .for_each_reflog_ent_reverse = files_for_each_reflog_ent_reverse, + .reflog_exists = files_reflog_exists, + .create_reflog = files_create_reflog, + .delete_reflog = files_delete_reflog, + .reflog_expire = files_reflog_expire }; diff --git a/refs/iterator.c b/refs/iterator.c index 629e00a..9db8b05 100644 --- a/refs/iterator.c +++ b/refs/iterator.c @@ -3,7 +3,7 @@ * documentation about the design and use of reference iterators. */ -#include "cache.h" +#include "git-compat-util.h" #include "refs.h" #include "refs/refs-internal.h" #include "iterator.h" @@ -25,11 +25,9 @@ int ref_iterator_abort(struct ref_iterator *ref_iterator) } void base_ref_iterator_init(struct ref_iterator *iter, - struct ref_iterator_vtable *vtable, - int ordered) + struct ref_iterator_vtable *vtable) { iter->vtable = vtable; - iter->ordered = !!ordered; iter->refname = NULL; iter->oid = NULL; iter->flags = 0; @@ -51,8 +49,8 @@ static int empty_ref_iterator_advance(struct ref_iterator *ref_iterator) return ref_iterator_abort(ref_iterator); } -static int empty_ref_iterator_peel(struct ref_iterator *ref_iterator, - struct object_id *peeled) +static int empty_ref_iterator_peel(struct ref_iterator *ref_iterator UNUSED, + struct object_id *peeled UNUSED) { BUG("peel called for empty iterator"); } @@ -64,9 +62,9 @@ static int empty_ref_iterator_abort(struct ref_iterator *ref_iterator) } static struct ref_iterator_vtable empty_ref_iterator_vtable = { - empty_ref_iterator_advance, - empty_ref_iterator_peel, - empty_ref_iterator_abort + .advance = empty_ref_iterator_advance, + .peel = empty_ref_iterator_peel, + .abort = empty_ref_iterator_abort, }; struct ref_iterator *empty_ref_iterator_begin(void) @@ -74,7 +72,7 @@ struct ref_iterator *empty_ref_iterator_begin(void) struct empty_ref_iterator *iter = xcalloc(1, sizeof(*iter)); struct ref_iterator *ref_iterator = &iter->base; - base_ref_iterator_init(ref_iterator, &empty_ref_iterator_vtable, 1); + base_ref_iterator_init(ref_iterator, &empty_ref_iterator_vtable); return ref_iterator; } @@ -98,6 +96,49 @@ struct merge_ref_iterator { struct ref_iterator **current; }; +enum iterator_selection ref_iterator_select(struct ref_iterator *iter_worktree, + struct ref_iterator *iter_common, + void *cb_data UNUSED) +{ + if (iter_worktree && !iter_common) { + /* + * Return the worktree ref if there are no more common refs. + */ + return ITER_SELECT_0; + } else if (iter_common) { + /* + * In case we have pending worktree and common refs we need to + * yield them based on their lexicographical order. Worktree + * refs that have the same name as common refs shadow the + * latter. + */ + if (iter_worktree) { + int cmp = strcmp(iter_worktree->refname, + iter_common->refname); + if (cmp < 0) + return ITER_SELECT_0; + else if (!cmp) + return ITER_SELECT_0_SKIP_1; + } + + /* + * We now know that the lexicographically-next ref is a common + * ref. When the common ref is a shared one we return it. + */ + if (parse_worktree_ref(iter_common->refname, NULL, NULL, + NULL) == REF_WORKTREE_SHARED) + return ITER_SELECT_1; + + /* + * Otherwise, if the common ref is a per-worktree ref we skip + * it because it would belong to the main worktree, not ours. + */ + return ITER_SKIP_1; + } else { + return ITER_DONE; + } +} + static int merge_ref_iterator_advance(struct ref_iterator *ref_iterator) { struct merge_ref_iterator *iter = @@ -201,13 +242,12 @@ static int merge_ref_iterator_abort(struct ref_iterator *ref_iterator) } static struct ref_iterator_vtable merge_ref_iterator_vtable = { - merge_ref_iterator_advance, - merge_ref_iterator_peel, - merge_ref_iterator_abort + .advance = merge_ref_iterator_advance, + .peel = merge_ref_iterator_peel, + .abort = merge_ref_iterator_abort, }; struct ref_iterator *merge_ref_iterator_begin( - int ordered, struct ref_iterator *iter0, struct ref_iterator *iter1, ref_iterator_select_fn *select, void *cb_data) { @@ -222,7 +262,7 @@ struct ref_iterator *merge_ref_iterator_begin( * references through only if they exist in both iterators. */ - base_ref_iterator_init(ref_iterator, &merge_ref_iterator_vtable, ordered); + base_ref_iterator_init(ref_iterator, &merge_ref_iterator_vtable); iter->iter0 = iter0; iter->iter1 = iter1; iter->select = select; @@ -238,7 +278,7 @@ struct ref_iterator *merge_ref_iterator_begin( */ static enum iterator_selection overlay_iterator_select( struct ref_iterator *front, struct ref_iterator *back, - void *cb_data) + void *cb_data UNUSED) { int cmp; @@ -271,12 +311,9 @@ struct ref_iterator *overlay_ref_iterator_begin( } else if (is_empty_ref_iterator(back)) { ref_iterator_abort(back); return front; - } else if (!front->ordered || !back->ordered) { - BUG("overlay_ref_iterator requires ordered inputs"); } - return merge_ref_iterator_begin(1, front, back, - overlay_iterator_select, NULL); + return merge_ref_iterator_begin(front, back, overlay_iterator_select, NULL); } struct prefix_ref_iterator { @@ -315,16 +352,12 @@ static int prefix_ref_iterator_advance(struct ref_iterator *ref_iterator) if (cmp > 0) { /* - * If the source iterator is ordered, then we + * As the source iterator is ordered, we * can stop the iteration as soon as we see a * refname that comes after the prefix: */ - if (iter->iter0->ordered) { - ok = ref_iterator_abort(iter->iter0); - break; - } else { - continue; - } + ok = ref_iterator_abort(iter->iter0); + break; } if (iter->trim) { @@ -378,9 +411,9 @@ static int prefix_ref_iterator_abort(struct ref_iterator *ref_iterator) } static struct ref_iterator_vtable prefix_ref_iterator_vtable = { - prefix_ref_iterator_advance, - prefix_ref_iterator_peel, - prefix_ref_iterator_abort + .advance = prefix_ref_iterator_advance, + .peel = prefix_ref_iterator_peel, + .abort = prefix_ref_iterator_abort, }; struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0, @@ -393,10 +426,10 @@ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0, if (!*prefix && !trim) return iter0; /* optimization: no need to wrap iterator */ - iter = xcalloc(1, sizeof(*iter)); + CALLOC_ARRAY(iter, 1); ref_iterator = &iter->base; - base_ref_iterator_init(ref_iterator, &prefix_ref_iterator_vtable, iter0->ordered); + base_ref_iterator_init(ref_iterator, &prefix_ref_iterator_vtable); iter->iter0 = iter0; iter->prefix = xstrdup(prefix); diff --git a/refs/packed-backend.c b/refs/packed-backend.c index 4458a0f..4e826c0 100644 --- a/refs/packed-backend.c +++ b/refs/packed-backend.c @@ -1,11 +1,18 @@ -#include "../cache.h" +#include "../git-compat-util.h" #include "../config.h" +#include "../gettext.h" +#include "../hash.h" +#include "../hex.h" #include "../refs.h" #include "refs-internal.h" #include "packed-backend.h" #include "../iterator.h" #include "../lockfile.h" #include "../chdir-notify.h" +#include "../statinfo.h" +#include "../wrapper.h" +#include "../write-or-die.h" +#include "../trace2.h" enum mmap_strategy { /* @@ -193,18 +200,20 @@ static int release_snapshot(struct snapshot *snapshot) } } -struct ref_store *packed_ref_store_create(const char *path, +struct ref_store *packed_ref_store_create(struct repository *repo, + const char *gitdir, unsigned int store_flags) { struct packed_ref_store *refs = xcalloc(1, sizeof(*refs)); struct ref_store *ref_store = (struct ref_store *)refs; + struct strbuf sb = STRBUF_INIT; - base_ref_store_init(ref_store, &refs_be_packed); + base_ref_store_init(ref_store, repo, gitdir, &refs_be_packed); refs->store_flags = store_flags; - refs->path = xstrdup(path); + strbuf_addf(&sb, "%s/packed-refs", gitdir); + refs->path = strbuf_detach(&sb, NULL); chdir_notify_reparent("packed-refs", &refs->path); - return ref_store; } @@ -295,7 +304,8 @@ static int cmp_packed_ref_records(const void *v1, const void *v2) * Compare a snapshot record at `rec` to the specified NUL-terminated * refname. */ -static int cmp_record_to_refname(const char *rec, const char *refname) +static int cmp_record_to_refname(const char *rec, const char *refname, + int start) { const char *r1 = rec + the_hash_algo->hexsz + 1; const char *r2 = refname; @@ -304,7 +314,7 @@ static int cmp_record_to_refname(const char *rec, const char *refname) if (*r1 == '\n') return *r2 ? -1 : 0; if (!*r2) - return 1; + return start ? 1 : -1; if (*r1 != *r2) return (unsigned char)*r1 < (unsigned char)*r2 ? -1 : +1; r1++; @@ -519,22 +529,9 @@ static int load_contents(struct snapshot *snapshot) return 1; } -/* - * Find the place in `snapshot->buf` where the start of the record for - * `refname` starts. If `mustexist` is true and the reference doesn't - * exist, then return NULL. If `mustexist` is false and the reference - * doesn't exist, then return the point where that reference would be - * inserted, or `snapshot->eof` (which might be NULL) if it would be - * inserted at the end of the file. In the latter mode, `refname` - * doesn't have to be a proper reference name; for example, one could - * search for "refs/replace/" to find the start of any replace - * references. - * - * The record is sought using a binary search, so `snapshot->buf` must - * be sorted. - */ -static const char *find_reference_location(struct snapshot *snapshot, - const char *refname, int mustexist) +static const char *find_reference_location_1(struct snapshot *snapshot, + const char *refname, int mustexist, + int start) { /* * This is not *quite* a garden-variety binary search, because @@ -564,7 +561,7 @@ static const char *find_reference_location(struct snapshot *snapshot, mid = lo + (hi - lo) / 2; rec = find_start_of_record(lo, mid); - cmp = cmp_record_to_refname(rec, refname); + cmp = cmp_record_to_refname(rec, refname, start); if (cmp < 0) { lo = find_end_of_record(mid, hi); } else if (cmp > 0) { @@ -581,6 +578,41 @@ static const char *find_reference_location(struct snapshot *snapshot, } /* + * Find the place in `snapshot->buf` where the start of the record for + * `refname` starts. If `mustexist` is true and the reference doesn't + * exist, then return NULL. If `mustexist` is false and the reference + * doesn't exist, then return the point where that reference would be + * inserted, or `snapshot->eof` (which might be NULL) if it would be + * inserted at the end of the file. In the latter mode, `refname` + * doesn't have to be a proper reference name; for example, one could + * search for "refs/replace/" to find the start of any replace + * references. + * + * The record is sought using a binary search, so `snapshot->buf` must + * be sorted. + */ +static const char *find_reference_location(struct snapshot *snapshot, + const char *refname, int mustexist) +{ + return find_reference_location_1(snapshot, refname, mustexist, 1); +} + +/* + * Find the place in `snapshot->buf` after the end of the record for + * `refname`. In other words, find the location of first thing *after* + * `refname`. + * + * Other semantics are identical to the ones in + * `find_reference_location()`. + */ +static const char *find_reference_location_end(struct snapshot *snapshot, + const char *refname, + int mustexist) +{ + return find_reference_location_1(snapshot, refname, mustexist, 0); +} + +/* * Create a newly-allocated `snapshot` of the `packed-refs` file in * its current state and return it. The return value will already have * its reference count incremented. @@ -644,7 +676,7 @@ static struct snapshot *create_snapshot(struct packed_ref_store *refs) snapshot->buf, snapshot->eof - snapshot->buf); - string_list_split_in_place(&traits, p, ' ', -1); + string_list_split_in_place(&traits, p, " ", -1); if (unsorted_string_list_has_string(&traits, "fully-peeled")) snapshot->peeled = PEELED_FULLY; @@ -723,9 +755,9 @@ static struct snapshot *get_snapshot(struct packed_ref_store *refs) return refs->snapshot; } -static int packed_read_raw_ref(struct ref_store *ref_store, - const char *refname, struct object_id *oid, - struct strbuf *referent, unsigned int *type) +static int packed_read_raw_ref(struct ref_store *ref_store, const char *refname, + struct object_id *oid, struct strbuf *referent UNUSED, + unsigned int *type, int *failure_errno) { struct packed_ref_store *refs = packed_downcast(ref_store, REF_STORE_READ, "read_raw_ref"); @@ -738,7 +770,7 @@ static int packed_read_raw_ref(struct ref_store *ref_store, if (!rec) { /* refname is not a packed reference. */ - errno = ENOENT; + *failure_errno = ENOENT; return -1; } @@ -771,10 +803,18 @@ struct packed_ref_iterator { /* The end of the part of the buffer that will be iterated over: */ const char *eof; + struct jump_list_entry { + const char *start; + const char *end; + } *jump; + size_t jump_nr, jump_alloc; + size_t jump_cur; + /* Scratch space for current values: */ struct object_id oid, peeled; struct strbuf refname_buf; + struct repository *repo; unsigned int flags; }; @@ -787,14 +827,36 @@ struct packed_ref_iterator { */ static int next_record(struct packed_ref_iterator *iter) { - const char *p = iter->pos, *eol; + const char *p, *eol; strbuf_reset(&iter->refname_buf); + /* + * If iter->pos is contained within a skipped region, jump past + * it. + * + * Note that each skipped region is considered at most once, + * since they are ordered based on their starting position. + */ + while (iter->jump_cur < iter->jump_nr) { + struct jump_list_entry *curr = &iter->jump[iter->jump_cur]; + if (iter->pos < curr->start) + break; /* not to the next jump yet */ + + iter->jump_cur++; + if (iter->pos < curr->end) { + iter->pos = curr->end; + trace2_counter_add(TRACE2_COUNTER_ID_PACKED_REFS_JUMPS, 1); + /* jumps are coalesced, so only one jump is necessary */ + break; + } + } + if (iter->pos == iter->eof) return ITER_DONE; iter->base.flags = REF_ISPACKED; + p = iter->pos; if (iter->eof - p < the_hash_algo->hexsz + 2 || parse_oid_hex(p, &iter->oid, &p) || @@ -859,12 +921,12 @@ static int packed_ref_iterator_advance(struct ref_iterator *ref_iterator) while ((ok = next_record(iter)) == ITER_OK) { if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY && - ref_type(iter->base.refname) != REF_TYPE_PER_WORKTREE) + !is_per_worktree_ref(iter->base.refname)) continue; if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) && - !ref_resolves_to_object(iter->base.refname, &iter->oid, - iter->flags)) + !ref_resolves_to_object(iter->base.refname, iter->repo, + &iter->oid, iter->flags)) continue; return ITER_OK; @@ -882,13 +944,16 @@ static int packed_ref_iterator_peel(struct ref_iterator *ref_iterator, struct packed_ref_iterator *iter = (struct packed_ref_iterator *)ref_iterator; + if (iter->repo != the_repository) + BUG("peeling for non-the_repository is not supported"); + if ((iter->base.flags & REF_KNOWS_PEELED)) { oidcpy(peeled, &iter->peeled); return is_null_oid(&iter->peeled) ? -1 : 0; } else if ((iter->base.flags & (REF_ISBROKEN | REF_ISSYMREF))) { return -1; } else { - return !!peel_object(&iter->oid, peeled); + return peel_object(&iter->oid, peeled) ? -1 : 0; } } @@ -899,20 +964,124 @@ static int packed_ref_iterator_abort(struct ref_iterator *ref_iterator) int ok = ITER_DONE; strbuf_release(&iter->refname_buf); + free(iter->jump); release_snapshot(iter->snapshot); base_ref_iterator_free(ref_iterator); return ok; } static struct ref_iterator_vtable packed_ref_iterator_vtable = { - packed_ref_iterator_advance, - packed_ref_iterator_peel, - packed_ref_iterator_abort + .advance = packed_ref_iterator_advance, + .peel = packed_ref_iterator_peel, + .abort = packed_ref_iterator_abort }; +static int jump_list_entry_cmp(const void *va, const void *vb) +{ + const struct jump_list_entry *a = va; + const struct jump_list_entry *b = vb; + + if (a->start < b->start) + return -1; + if (a->start > b->start) + return 1; + return 0; +} + +static int has_glob_special(const char *str) +{ + const char *p; + for (p = str; *p; p++) { + if (is_glob_special(*p)) + return 1; + } + return 0; +} + +static void populate_excluded_jump_list(struct packed_ref_iterator *iter, + struct snapshot *snapshot, + const char **excluded_patterns) +{ + size_t i, j; + const char **pattern; + struct jump_list_entry *last_disjoint; + + if (!excluded_patterns) + return; + + for (pattern = excluded_patterns; *pattern; pattern++) { + struct jump_list_entry *e; + const char *start, *end; + + /* + * We can't feed any excludes with globs in them to the + * refs machinery. It only understands prefix matching. + * We likewise can't even feed the string leading up to + * the first meta-character, as something like "foo[a]" + * should not exclude "foobar" (but the prefix "foo" + * would match that and mark it for exclusion). + */ + if (has_glob_special(*pattern)) + continue; + + start = find_reference_location(snapshot, *pattern, 0); + end = find_reference_location_end(snapshot, *pattern, 0); + + if (start == end) + continue; /* nothing to jump over */ + + ALLOC_GROW(iter->jump, iter->jump_nr + 1, iter->jump_alloc); + + e = &iter->jump[iter->jump_nr++]; + e->start = start; + e->end = end; + } + + if (!iter->jump_nr) { + /* + * Every entry in exclude_patterns has a meta-character, + * nothing to do here. + */ + return; + } + + QSORT(iter->jump, iter->jump_nr, jump_list_entry_cmp); + + /* + * As an optimization, merge adjacent entries in the jump list + * to jump forwards as far as possible when entering a skipped + * region. + * + * For example, if we have two skipped regions: + * + * [[A, B], [B, C]] + * + * we want to combine that into a single entry jumping from A to + * C. + */ + last_disjoint = iter->jump; + + for (i = 1, j = 1; i < iter->jump_nr; i++) { + struct jump_list_entry *ours = &iter->jump[i]; + if (ours->start <= last_disjoint->end) { + /* overlapping regions extend the previous one */ + last_disjoint->end = last_disjoint->end > ours->end + ? last_disjoint->end : ours->end; + } else { + /* otherwise, insert a new region */ + iter->jump[j++] = *ours; + last_disjoint = ours; + } + } + + iter->jump_nr = j; + iter->jump_cur = 0; +} + static struct ref_iterator *packed_ref_iterator_begin( struct ref_store *ref_store, - const char *prefix, unsigned int flags) + const char *prefix, const char **exclude_patterns, + unsigned int flags) { struct packed_ref_store *refs; struct snapshot *snapshot; @@ -940,9 +1109,12 @@ static struct ref_iterator *packed_ref_iterator_begin( if (start == snapshot->eof) return empty_ref_iterator_begin(); - iter = xcalloc(1, sizeof(*iter)); + CALLOC_ARRAY(iter, 1); ref_iterator = &iter->base; - base_ref_iterator_init(ref_iterator, &packed_ref_iterator_vtable, 1); + base_ref_iterator_init(ref_iterator, &packed_ref_iterator_vtable); + + if (exclude_patterns) + populate_excluded_jump_list(iter, snapshot, exclude_patterns); iter->snapshot = snapshot; acquire_snapshot(snapshot); @@ -953,6 +1125,7 @@ static struct ref_iterator *packed_ref_iterator_begin( iter->base.oid = &iter->oid; + iter->repo = ref_store->repo; iter->flags = flags; if (prefix && *prefix) @@ -1071,7 +1244,9 @@ int packed_refs_is_locked(struct ref_store *ref_store) static const char PACKED_REFS_HEADER[] = "# pack-refs with: peeled fully-peeled sorted \n"; -static int packed_init_db(struct ref_store *ref_store, struct strbuf *err) +static int packed_init_db(struct ref_store *ref_store UNUSED, + int flags UNUSED, + struct strbuf *err UNUSED) { /* Nothing to do. */ return 0; @@ -1135,7 +1310,7 @@ static int write_with_updates(struct packed_ref_store *refs, * list of refs is exhausted, set iter to NULL. When the list * of updates is exhausted, leave i set to updates->nr. */ - iter = packed_ref_iterator_begin(&refs->base, "", + iter = packed_ref_iterator_begin(&refs->base, "", NULL, DO_FOR_EACH_INCLUDE_BROKEN); if ((ok = ref_iterator_advance(iter)) != ITER_OK) iter = NULL; @@ -1255,7 +1430,9 @@ static int write_with_updates(struct packed_ref_store *refs, goto error; } - if (close_tempfile_gently(refs->tempfile)) { + if (fflush(out) || + fsync_component(FSYNC_COMPONENT_REFERENCE, get_tempfile_fd(refs->tempfile)) || + close_tempfile_gently(refs->tempfile)) { strbuf_addf(err, "error closing file %s: %s", get_tempfile_path(refs->tempfile), strerror(errno)); @@ -1346,6 +1523,7 @@ int is_packed_transaction_needed(struct ref_store *ref_store, ret = 0; for (i = 0; i < transaction->nr; i++) { struct ref_update *update = transaction->updates[i]; + int failure_errno; unsigned int type; struct object_id oid; @@ -1356,9 +1534,9 @@ int is_packed_transaction_needed(struct ref_store *ref_store, */ continue; - if (!refs_read_raw_ref(ref_store, update->refname, - &oid, &referent, &type) || - errno != ENOENT) { + if (!refs_read_raw_ref(ref_store, update->refname, &oid, + &referent, &type, &failure_errno) || + failure_errno != ENOENT) { /* * We have to actually delete that reference * -> this transaction is needed. @@ -1423,8 +1601,8 @@ static int packed_transaction_prepare(struct ref_store *ref_store, * do so itself. */ - data = xcalloc(1, sizeof(*data)); - string_list_init(&data->updates, 0); + CALLOC_ARRAY(data, 1); + string_list_init_nodup(&data->updates); transaction->backend_data = data; @@ -1464,7 +1642,7 @@ failure: static int packed_transaction_abort(struct ref_store *ref_store, struct ref_transaction *transaction, - struct strbuf *err) + struct strbuf *err UNUSED) { struct packed_ref_store *refs = packed_downcast( ref_store, @@ -1503,63 +1681,15 @@ cleanup: return ret; } -static int packed_initial_transaction_commit(struct ref_store *ref_store, +static int packed_initial_transaction_commit(struct ref_store *ref_store UNUSED, struct ref_transaction *transaction, struct strbuf *err) { return ref_transaction_commit(transaction, err); } -static int packed_delete_refs(struct ref_store *ref_store, const char *msg, - struct string_list *refnames, unsigned int flags) -{ - struct packed_ref_store *refs = - packed_downcast(ref_store, REF_STORE_WRITE, "delete_refs"); - struct strbuf err = STRBUF_INIT; - struct ref_transaction *transaction; - struct string_list_item *item; - int ret; - - (void)refs; /* We need the check above, but don't use the variable */ - - if (!refnames->nr) - return 0; - - /* - * Since we don't check the references' old_oids, the - * individual updates can't fail, so we can pack all of the - * updates into a single transaction. - */ - - transaction = ref_store_transaction_begin(ref_store, &err); - if (!transaction) - return -1; - - for_each_string_list_item(item, refnames) { - if (ref_transaction_delete(transaction, item->string, NULL, - flags, msg, &err)) { - warning(_("could not delete reference %s: %s"), - item->string, err.buf); - strbuf_reset(&err); - } - } - - ret = ref_transaction_commit(transaction, &err); - - if (ret) { - if (refnames->nr == 1) - error(_("could not delete reference %s: %s"), - refnames->items[0].string, err.buf); - else - error(_("could not delete references: %s"), err.buf); - } - - ref_transaction_free(transaction); - strbuf_release(&err); - return ret; -} - -static int packed_pack_refs(struct ref_store *ref_store, unsigned int flags) +static int packed_pack_refs(struct ref_store *ref_store UNUSED, + struct pack_refs_opts *pack_opts UNUSED) { /* * Packed refs are already packed. It might be that loose refs @@ -1569,101 +1699,34 @@ static int packed_pack_refs(struct ref_store *ref_store, unsigned int flags) return 0; } -static int packed_create_symref(struct ref_store *ref_store, - const char *refname, const char *target, - const char *logmsg) -{ - BUG("packed reference store does not support symrefs"); -} - -static int packed_rename_ref(struct ref_store *ref_store, - const char *oldrefname, const char *newrefname, - const char *logmsg) -{ - BUG("packed reference store does not support renaming references"); -} - -static int packed_copy_ref(struct ref_store *ref_store, - const char *oldrefname, const char *newrefname, - const char *logmsg) -{ - BUG("packed reference store does not support copying references"); -} - -static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_store) +static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_store UNUSED) { return empty_ref_iterator_begin(); } -static int packed_for_each_reflog_ent(struct ref_store *ref_store, - const char *refname, - each_reflog_ent_fn fn, void *cb_data) -{ - return 0; -} - -static int packed_for_each_reflog_ent_reverse(struct ref_store *ref_store, - const char *refname, - each_reflog_ent_fn fn, - void *cb_data) -{ - return 0; -} - -static int packed_reflog_exists(struct ref_store *ref_store, - const char *refname) -{ - return 0; -} - -static int packed_create_reflog(struct ref_store *ref_store, - const char *refname, int force_create, - struct strbuf *err) -{ - BUG("packed reference store does not support reflogs"); -} - -static int packed_delete_reflog(struct ref_store *ref_store, - const char *refname) -{ - return 0; -} - -static int packed_reflog_expire(struct ref_store *ref_store, - const char *refname, const struct object_id *oid, - unsigned int flags, - reflog_expiry_prepare_fn prepare_fn, - reflog_expiry_should_prune_fn should_prune_fn, - reflog_expiry_cleanup_fn cleanup_fn, - void *policy_cb_data) -{ - return 0; -} - struct ref_storage_be refs_be_packed = { - NULL, - "packed", - packed_ref_store_create, - packed_init_db, - packed_transaction_prepare, - packed_transaction_finish, - packed_transaction_abort, - packed_initial_transaction_commit, - - packed_pack_refs, - packed_create_symref, - packed_delete_refs, - packed_rename_ref, - packed_copy_ref, - - packed_ref_iterator_begin, - packed_read_raw_ref, - - packed_reflog_iterator_begin, - packed_for_each_reflog_ent, - packed_for_each_reflog_ent_reverse, - packed_reflog_exists, - packed_create_reflog, - packed_delete_reflog, - packed_reflog_expire + .name = "packed", + .init = packed_ref_store_create, + .init_db = packed_init_db, + .transaction_prepare = packed_transaction_prepare, + .transaction_finish = packed_transaction_finish, + .transaction_abort = packed_transaction_abort, + .initial_transaction_commit = packed_initial_transaction_commit, + + .pack_refs = packed_pack_refs, + .create_symref = NULL, + .rename_ref = NULL, + .copy_ref = NULL, + + .iterator_begin = packed_ref_iterator_begin, + .read_raw_ref = packed_read_raw_ref, + .read_symbolic_ref = NULL, + + .reflog_iterator_begin = packed_reflog_iterator_begin, + .for_each_reflog_ent = NULL, + .for_each_reflog_ent_reverse = NULL, + .reflog_exists = NULL, + .create_reflog = NULL, + .delete_reflog = NULL, + .reflog_expire = NULL, }; diff --git a/refs/packed-backend.h b/refs/packed-backend.h index a01a0af..9dd8a34 100644 --- a/refs/packed-backend.h +++ b/refs/packed-backend.h @@ -1,6 +1,7 @@ #ifndef REFS_PACKED_BACKEND_H #define REFS_PACKED_BACKEND_H +struct repository; struct ref_transaction; /* @@ -12,7 +13,8 @@ struct ref_transaction; * even among packed refs. */ -struct ref_store *packed_ref_store_create(const char *path, +struct ref_store *packed_ref_store_create(struct repository *repo, + const char *gitdir, unsigned int store_flags); /* diff --git a/refs/ref-cache.c b/refs/ref-cache.c index b7052f7..9f97972 100644 --- a/refs/ref-cache.c +++ b/refs/ref-cache.c @@ -1,5 +1,7 @@ -#include "../cache.h" +#include "../git-compat-util.h" +#include "../hash.h" #include "../refs.h" +#include "../repository.h" #include "refs-internal.h" #include "ref-cache.h" #include "../iterator.h" @@ -49,7 +51,7 @@ struct ref_cache *create_ref_cache(struct ref_store *refs, ret->ref_store = refs; ret->fill_ref_dir = fill_ref_dir; - ret->root = create_dir_entry(ret, "", 0, 1); + ret->root = create_dir_entry(ret, "", 0); return ret; } @@ -86,14 +88,13 @@ static void clear_ref_dir(struct ref_dir *dir) } struct ref_entry *create_dir_entry(struct ref_cache *cache, - const char *dirname, size_t len, - int incomplete) + const char *dirname, size_t len) { struct ref_entry *direntry; FLEX_ALLOC_MEM(direntry, name, dirname, len); direntry->u.subdir.cache = cache; - direntry->flag = REF_DIR | (incomplete ? REF_INCOMPLETE : 0); + direntry->flag = REF_DIR | REF_INCOMPLETE; return direntry; } @@ -135,7 +136,7 @@ int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len) r = bsearch(&key, dir->entries, dir->nr, sizeof(*dir->entries), ref_entry_cmp_sslice); - if (r == NULL) + if (!r) return -1; return r - dir->entries; @@ -144,30 +145,19 @@ int search_ref_dir(struct ref_dir *dir, const char *refname, size_t len) /* * Search for a directory entry directly within dir (without * recursing). Sort dir if necessary. subdirname must be a directory - * name (i.e., end in '/'). If mkdir is set, then create the - * directory if it is missing; otherwise, return NULL if the desired + * name (i.e., end in '/'). Returns NULL if the desired * directory cannot be found. dir must already be complete. */ static struct ref_dir *search_for_subdir(struct ref_dir *dir, - const char *subdirname, size_t len, - int mkdir) + const char *subdirname, size_t len) { int entry_index = search_ref_dir(dir, subdirname, len); struct ref_entry *entry; - if (entry_index == -1) { - if (!mkdir) - return NULL; - /* - * Since dir is complete, the absence of a subdir - * means that the subdir really doesn't exist; - * therefore, create an empty record for it but mark - * the record complete. - */ - entry = create_dir_entry(dir->cache, subdirname, len, 0); - add_entry_to_dir(dir, entry); - } else { - entry = dir->entries[entry_index]; - } + + if (entry_index == -1) + return NULL; + + entry = dir->entries[entry_index]; return get_ref_dir(entry); } @@ -176,18 +166,17 @@ static struct ref_dir *search_for_subdir(struct ref_dir *dir, * tree that should hold refname. If refname is a directory name * (i.e., it ends in '/'), then return that ref_dir itself. dir must * represent the top-level directory and must already be complete. - * Sort ref_dirs and recurse into subdirectories as necessary. If - * mkdir is set, then create any missing directories; otherwise, + * Sort ref_dirs and recurse into subdirectories as necessary. Will * return NULL if the desired directory cannot be found. */ static struct ref_dir *find_containing_dir(struct ref_dir *dir, - const char *refname, int mkdir) + const char *refname) { const char *slash; for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) { size_t dirnamelen = slash - refname + 1; struct ref_dir *subdir; - subdir = search_for_subdir(dir, refname, dirnamelen, mkdir); + subdir = search_for_subdir(dir, refname, dirnamelen); if (!subdir) { dir = NULL; break; @@ -202,7 +191,7 @@ struct ref_entry *find_ref_entry(struct ref_dir *dir, const char *refname) { int entry_index; struct ref_entry *entry; - dir = find_containing_dir(dir, refname, 0); + dir = find_containing_dir(dir, refname); if (!dir) return NULL; entry_index = search_ref_dir(dir, refname, strlen(refname)); @@ -212,50 +201,6 @@ struct ref_entry *find_ref_entry(struct ref_dir *dir, const char *refname) return (entry->flag & REF_DIR) ? NULL : entry; } -int remove_entry_from_dir(struct ref_dir *dir, const char *refname) -{ - int refname_len = strlen(refname); - int entry_index; - struct ref_entry *entry; - int is_dir = refname[refname_len - 1] == '/'; - if (is_dir) { - /* - * refname represents a reference directory. Remove - * the trailing slash; otherwise we will get the - * directory *representing* refname rather than the - * one *containing* it. - */ - char *dirname = xmemdupz(refname, refname_len - 1); - dir = find_containing_dir(dir, dirname, 0); - free(dirname); - } else { - dir = find_containing_dir(dir, refname, 0); - } - if (!dir) - return -1; - entry_index = search_ref_dir(dir, refname, refname_len); - if (entry_index == -1) - return -1; - entry = dir->entries[entry_index]; - - MOVE_ARRAY(&dir->entries[entry_index], - &dir->entries[entry_index + 1], dir->nr - entry_index - 1); - dir->nr--; - if (dir->sorted > entry_index) - dir->sorted--; - free_ref_entry(entry); - return dir->nr; -} - -int add_ref_entry(struct ref_dir *dir, struct ref_entry *ref) -{ - dir = find_containing_dir(dir, ref->name, 1); - if (!dir) - return -1; - add_entry_to_dir(dir, ref); - return 0; -} - /* * Emit a warning and return true iff ref1 and ref2 have the same name * and the same oid. Die if they have the same name but different @@ -435,6 +380,8 @@ struct cache_ref_iterator { * on from there.) */ struct cache_ref_iterator_level *levels; + + struct repository *repo; }; static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator) @@ -464,7 +411,8 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator) if (level->prefix_state == PREFIX_WITHIN_DIR) { entry_prefix_state = overlaps_prefix(entry->name, iter->prefix); - if (entry_prefix_state == PREFIX_EXCLUDES_DIR) + if (entry_prefix_state == PREFIX_EXCLUDES_DIR || + (entry_prefix_state == PREFIX_WITHIN_DIR && !(entry->flag & REF_DIR))) continue; } else { entry_prefix_state = level->prefix_state; @@ -491,7 +439,12 @@ static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator) static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator, struct object_id *peeled) { - return peel_object(ref_iterator->oid, peeled); + struct cache_ref_iterator *iter = + (struct cache_ref_iterator *)ref_iterator; + + if (iter->repo != the_repository) + BUG("peeling for non-the_repository is not supported"); + return peel_object(ref_iterator->oid, peeled) ? -1 : 0; } static int cache_ref_iterator_abort(struct ref_iterator *ref_iterator) @@ -506,13 +459,14 @@ static int cache_ref_iterator_abort(struct ref_iterator *ref_iterator) } static struct ref_iterator_vtable cache_ref_iterator_vtable = { - cache_ref_iterator_advance, - cache_ref_iterator_peel, - cache_ref_iterator_abort + .advance = cache_ref_iterator_advance, + .peel = cache_ref_iterator_peel, + .abort = cache_ref_iterator_abort }; struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache, const char *prefix, + struct repository *repo, int prime_dir) { struct ref_dir *dir; @@ -522,7 +476,7 @@ struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache, dir = get_ref_dir(cache->root); if (prefix && *prefix) - dir = find_containing_dir(dir, prefix, 0); + dir = find_containing_dir(dir, prefix); if (!dir) /* There's nothing to iterate over. */ return empty_ref_iterator_begin(); @@ -530,9 +484,9 @@ struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache, if (prime_dir) prime_ref_dir(dir, prefix); - iter = xcalloc(1, sizeof(*iter)); + CALLOC_ARRAY(iter, 1); ref_iterator = &iter->base; - base_ref_iterator_init(ref_iterator, &cache_ref_iterator_vtable, 1); + base_ref_iterator_init(ref_iterator, &cache_ref_iterator_vtable); ALLOC_GROW(iter->levels, 10, iter->levels_alloc); iter->levels_nr = 1; @@ -547,5 +501,7 @@ struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache, level->prefix_state = PREFIX_CONTAINS_DIR; } + iter->repo = repo; + return ref_iterator; } diff --git a/refs/ref-cache.h b/refs/ref-cache.h index 3bfb89d..95c76e2 100644 --- a/refs/ref-cache.h +++ b/refs/ref-cache.h @@ -1,10 +1,11 @@ #ifndef REFS_REF_CACHE_H #define REFS_REF_CACHE_H -#include "cache.h" +#include "hash-ll.h" struct ref_dir; struct ref_store; +struct repository; /* * If this ref_cache is filled lazily, this function is used to load @@ -169,8 +170,7 @@ struct ref_dir *get_ref_dir(struct ref_entry *entry); * "refs/heads/") or "" for the top-level directory. */ struct ref_entry *create_dir_entry(struct ref_cache *cache, - const char *dirname, size_t len, - int incomplete); + const char *dirname, size_t len); struct ref_entry *create_ref_entry(const char *refname, const struct object_id *oid, int flag); @@ -200,29 +200,6 @@ void free_ref_cache(struct ref_cache *cache); void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry); /* - * Remove the entry with the given name from dir, recursing into - * subdirectories as necessary. If refname is the name of a directory - * (i.e., ends with '/'), then remove the directory and its contents. - * If the removal was successful, return the number of entries - * remaining in the directory entry that contained the deleted entry. - * If the name was not found, return -1. Please note that this - * function only deletes the entry from the cache; it does not delete - * it from the filesystem or ensure that other cache entries (which - * might be symbolic references to the removed entry) are updated. - * Nor does it remove any containing dir entries that might be made - * empty by the removal. dir must represent the top-level directory - * and must already be complete. - */ -int remove_entry_from_dir(struct ref_dir *dir, const char *refname); - -/* - * Add a ref_entry to the ref_dir (unsorted), recursing into - * subdirectories as necessary. dir must represent the top-level - * directory. Return 0 on success. - */ -int add_ref_entry(struct ref_dir *dir, struct ref_entry *ref); - -/* * Find the value entry with the given name in dir, sorting ref_dirs * and recursing into subdirectories as necessary. If the name is not * found or it corresponds to a directory entry, return NULL. @@ -238,6 +215,7 @@ struct ref_entry *find_ref_entry(struct ref_dir *dir, const char *refname); */ struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache, const char *prefix, + struct repository *repo, int prime_dir); #endif /* REFS_REF_CACHE_H */ diff --git a/refs/refs-internal.h b/refs/refs-internal.h index f2d8c01..56641aa 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -1,7 +1,6 @@ #ifndef REFS_REFS_INTERNAL_H #define REFS_REFS_INTERNAL_H -#include "cache.h" #include "refs.h" #include "iterator.h" @@ -32,6 +31,13 @@ struct ref_transaction; #define REF_HAVE_OLD (1 << 3) /* + * Used as a flag in ref_update::flags when we want to log a ref + * update but not actually perform it. This is used when a symbolic + * ref update is split up. + */ +#define REF_LOG_ONLY (1 << 7) + +/* * Return the length of time to retry acquiring a loose reference lock * before giving up, in milliseconds: */ @@ -59,6 +65,7 @@ int refname_is_safe(const char *refname); * referred-to object does not exist, emit a warning and return false. */ int ref_resolves_to_object(const char *refname, + struct repository *repo, const struct object_id *oid, unsigned int flags); @@ -96,12 +103,6 @@ enum peel_status { */ enum peel_status peel_object(const struct object_id *name, struct object_id *oid); -/* - * Copy the reflog message msg to sb while cleaning up the whitespaces. - * Especially, convert LF to space, because reflog file is one line per entry. - */ -void copy_reflog_msg(struct strbuf *sb, const char *msg); - /** * Information needed for a single ref update. Set new_oid to the new * value or to null_oid to delete the ref. To check the old value @@ -148,9 +149,9 @@ struct ref_update { const char refname[FLEX_ARRAY]; }; -int refs_read_raw_ref(struct ref_store *ref_store, - const char *refname, struct object_id *oid, - struct strbuf *referent, unsigned int *type); +int refs_read_raw_ref(struct ref_store *ref_store, const char *refname, + struct object_id *oid, struct strbuf *referent, + unsigned int *type, int *failure_errno); /* * Write an error to `err` and return a nonzero value iff the same @@ -227,25 +228,45 @@ const char *find_descendant_ref(const char *dirname, const struct string_list *extras, const struct string_list *skip); +/* We allow "recursive" symbolic refs. Only within reason, though */ +#define SYMREF_MAXDEPTH 5 + /* - * Check whether an attempt to rename old_refname to new_refname would - * cause a D/F conflict with any existing reference (other than - * possibly old_refname). If there would be a conflict, emit an error - * message and return false; otherwise, return true. - * - * Note that this function is not safe against all races with other - * processes (though rename_ref() catches some races that might get by - * this check). + * These flags are passed to refs_ref_iterator_begin() (and do_for_each_ref(), + * which feeds it). */ -int refs_rename_ref_available(struct ref_store *refs, - const char *old_refname, - const char *new_refname); +enum do_for_each_ref_flags { + /* + * Include broken references in a do_for_each_ref*() iteration, which + * would normally be omitted. This includes both refs that point to + * missing objects (a true repository corruption), ones with illegal + * names (which we prefer not to expose to callers), as well as + * dangling symbolic refs (i.e., those that point to a non-existent + * ref; this is not a corruption, but as they have no valid oid, we + * omit them from normal iteration results). + */ + DO_FOR_EACH_INCLUDE_BROKEN = (1 << 0), -/* We allow "recursive" symbolic refs. Only within reason, though */ -#define SYMREF_MAXDEPTH 5 + /* + * Only include per-worktree refs in a do_for_each_ref*() iteration. + * Normally this will be used with a files ref_store, since that's + * where all reference backends will presumably store their + * per-worktree refs. + */ + DO_FOR_EACH_PER_WORKTREE_ONLY = (1 << 1), -/* Include broken references in a do_for_each_ref*() iteration: */ -#define DO_FOR_EACH_INCLUDE_BROKEN 0x01 + /* + * Omit dangling symrefs from output; this only has an effect with + * INCLUDE_BROKEN, since they are otherwise not included at all. + */ + DO_FOR_EACH_OMIT_DANGLING_SYMREFS = (1 << 2), + + /* + * Include root refs i.e. HEAD and pseudorefs along with the regular + * refs. + */ + DO_FOR_EACH_INCLUDE_ROOT_REFS = (1 << 3), +}; /* * Reference iterators @@ -262,7 +283,7 @@ int refs_rename_ref_available(struct ref_store *refs, * after calling ref_iterator_advance() again or calling * ref_iterator_abort(), you must make a copy. When the iteration has * been exhausted, ref_iterator_advance() releases any resources - * assocated with the iteration, frees the ref_iterator object, and + * 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 * any associated resources. If there was an internal error advancing @@ -297,13 +318,6 @@ int refs_rename_ref_available(struct ref_store *refs, */ struct ref_iterator { struct ref_iterator_vtable *vtable; - - /* - * Does this `ref_iterator` iterate over references in order - * by refname? - */ - unsigned int ordered : 1; - const char *refname; const struct object_id *oid; unsigned int flags; @@ -347,13 +361,13 @@ int is_empty_ref_iterator(struct ref_iterator *ref_iterator); /* * Return an iterator that goes over each reference in `refs` for * which the refname begins with prefix. If trim is non-zero, then - * trim that many characters off the beginning of each refname. flags - * can be DO_FOR_EACH_INCLUDE_BROKEN to include broken references in - * the iteration. The output is ordered by refname. + * trim that many characters off the beginning of each refname. + * The output is ordered by refname. */ struct ref_iterator *refs_ref_iterator_begin( struct ref_store *refs, - const char *prefix, int trim, int flags); + const char *prefix, const char **exclude_patterns, + int trim, enum do_for_each_ref_flags flags); /* * A callback function used to instruct merge_ref_iterator how to @@ -372,14 +386,21 @@ typedef enum iterator_selection ref_iterator_select_fn( void *cb_data); /* + * An implementation of ref_iterator_select_fn that merges worktree and common + * refs. Per-worktree refs from the common iterator are ignored, worktree refs + * override common refs. Refs are selected lexicographically. + */ +enum iterator_selection ref_iterator_select(struct ref_iterator *iter_worktree, + struct ref_iterator *iter_common, + void *cb_data); + +/* * Iterate over the entries from iter0 and iter1, with the values * interleaved as directed by the select function. The iterator takes * ownership of iter0 and iter1 and frees them when the iteration is - * over. A derived class should set `ordered` to 1 or 0 based on - * whether it generates its output in order by reference name. + * over. */ struct ref_iterator *merge_ref_iterator_begin( - int ordered, struct ref_iterator *iter0, struct ref_iterator *iter1, ref_iterator_select_fn *select, void *cb_data); @@ -408,8 +429,6 @@ struct ref_iterator *overlay_ref_iterator_begin( * As an convenience to callers, if prefix is the empty string and * trim is zero, this function returns iter0 directly, without * wrapping it. - * - * The resulting ref_iterator is ordered if iter0 is. */ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0, const char *prefix, @@ -420,14 +439,11 @@ struct ref_iterator *prefix_ref_iterator_begin(struct ref_iterator *iter0, /* * Base class constructor for ref_iterators. Initialize the * ref_iterator part of iter, setting its vtable pointer as specified. - * `ordered` should be set to 1 if the iterator will iterate over - * references in order by refname; otherwise it should be set to 0. * This is meant to be called only by the initializers of derived * classes. */ void base_ref_iterator_init(struct ref_iterator *iter, - struct ref_iterator_vtable *vtable, - int ordered); + struct ref_iterator_vtable *vtable); /* * Base class destructor for ref_iterators. Destroy the ref_iterator @@ -438,8 +454,17 @@ void base_ref_iterator_free(struct ref_iterator *iter); /* Virtual function declarations for ref_iterators: */ +/* + * backend-specific implementation of ref_iterator_advance. For symrefs, the + * function should set REF_ISSYMREF, and it should also dereference the symref + * to provide the OID referent. It should respect do_for_each_ref_flags + * that were passed to refs_ref_iterator_begin(). + */ typedef int ref_iterator_advance_fn(struct ref_iterator *ref_iterator); +/* + * Peels the current ref, returning 0 for success or -1 for failure. + */ typedef int ref_iterator_peel_fn(struct ref_iterator *ref_iterator, struct object_id *peeled); @@ -482,14 +507,6 @@ int do_for_each_repo_ref_iterator(struct repository *r, struct ref_iterator *iter, each_repo_ref_fn fn, void *cb_data); -/* - * Only include per-worktree refs in a do_for_each_ref*() iteration. - * Normally this will be used with a files ref_store, since that's - * where all reference backends will presumably store their - * per-worktree refs. - */ -#define DO_FOR_EACH_PER_WORKTREE_ONLY 0x02 - struct ref_store; /* refs backends */ @@ -509,10 +526,13 @@ struct ref_store; * should call base_ref_store_init() to initialize the shared part of * the ref_store and to record the ref_store for later lookup. */ -typedef struct ref_store *ref_store_init_fn(const char *gitdir, +typedef struct ref_store *ref_store_init_fn(struct repository *repo, + const char *gitdir, unsigned int flags); -typedef int ref_init_db_fn(struct ref_store *refs, struct strbuf *err); +typedef int ref_init_db_fn(struct ref_store *refs, + int flags, + struct strbuf *err); typedef int ref_transaction_prepare_fn(struct ref_store *refs, struct ref_transaction *transaction, @@ -530,13 +550,12 @@ typedef int ref_transaction_commit_fn(struct ref_store *refs, struct ref_transaction *transaction, struct strbuf *err); -typedef int pack_refs_fn(struct ref_store *ref_store, unsigned int flags); +typedef int pack_refs_fn(struct ref_store *ref_store, + struct pack_refs_opts *opts); typedef int create_symref_fn(struct ref_store *ref_store, const char *ref_target, const char *refs_heads_master, const char *logmsg); -typedef int delete_refs_fn(struct ref_store *ref_store, const char *msg, - struct string_list *refnames, unsigned int flags); typedef int rename_ref_fn(struct ref_store *ref_store, const char *oldref, const char *newref, const char *logmsg); @@ -553,7 +572,8 @@ typedef int copy_ref_fn(struct ref_store *ref_store, */ typedef struct ref_iterator *ref_iterator_begin_fn( struct ref_store *ref_store, - const char *prefix, unsigned int flags); + const char *prefix, const char **exclude_patterns, + unsigned int flags); /* reflog functions */ @@ -574,10 +594,10 @@ typedef int for_each_reflog_ent_reverse_fn(struct ref_store *ref_store, void *cb_data); typedef int reflog_exists_fn(struct ref_store *ref_store, const char *refname); typedef int create_reflog_fn(struct ref_store *ref_store, const char *refname, - int force_create, struct strbuf *err); + struct strbuf *err); typedef int delete_reflog_fn(struct ref_store *ref_store, const char *refname); typedef int reflog_expire_fn(struct ref_store *ref_store, - const char *refname, const struct object_id *oid, + const char *refname, unsigned int flags, reflog_expiry_prepare_fn prepare_fn, reflog_expiry_should_prune_fn should_prune_fn, @@ -604,11 +624,15 @@ typedef int reflog_expire_fn(struct ref_store *ref_store, * properly-formatted or even safe reference name. NEITHER INPUT NOR * OUTPUT REFERENCE NAMES ARE VALIDATED WITHIN THIS FUNCTION. * - * Return 0 on success. If the ref doesn't exist, set errno to ENOENT - * and return -1. If the ref exists but is neither a symbolic ref nor - * an object ID, it is broken; set REF_ISBROKEN in type, set errno to - * EINVAL, and return -1. If there is another error reading the ref, - * set errno appropriately and return -1. + * Return 0 on success, or -1 on failure. If the ref exists but is neither a + * symbolic ref nor an object ID, it is broken. In this case set REF_ISBROKEN in + * type, and return -1 (failure_errno should not be ENOENT) + * + * failure_errno provides errno codes that are interpreted beyond error + * reporting. The following error codes have special meaning: + * * ENOENT: the ref doesn't exist + * * EISDIR: ref name is a directory + * * ENOTDIR: ref prefix is not a directory * * Backend-specific flags might be set in type as well, regardless of * outcome. @@ -622,12 +646,26 @@ typedef int reflog_expire_fn(struct ref_store *ref_store, * - in all other cases, referent will be untouched, and therefore * refname will still be valid and unchanged. */ -typedef int read_raw_ref_fn(struct ref_store *ref_store, - const char *refname, struct object_id *oid, - struct strbuf *referent, unsigned int *type); +typedef int read_raw_ref_fn(struct ref_store *ref_store, const char *refname, + struct object_id *oid, struct strbuf *referent, + unsigned int *type, int *failure_errno); + +/* + * Read a symbolic reference from the specified reference store. This function + * is optional: if not implemented by a backend, then `read_raw_ref_fn` is used + * to read the symbolcic reference instead. It is intended to be implemented + * only in case the backend can optimize the reading of symbolic references. + * + * Return 0 on success, or -1 on failure. `referent` will be set to the target + * of the symbolic reference on success. This function explicitly does not + * distinguish between error cases and the reference not being a symbolic + * reference to allow backends to optimize this operation in case symbolic and + * non-symbolic references are treated differently. + */ +typedef int read_symbolic_ref_fn(struct ref_store *ref_store, const char *refname, + struct strbuf *referent); struct ref_storage_be { - struct ref_storage_be *next; const char *name; ref_store_init_fn *init; ref_init_db_fn *init_db; @@ -639,12 +677,12 @@ struct ref_storage_be { pack_refs_fn *pack_refs; create_symref_fn *create_symref; - delete_refs_fn *delete_refs; rename_ref_fn *rename_ref; copy_ref_fn *copy_ref; ref_iterator_begin_fn *iterator_begin; read_raw_ref_fn *read_raw_ref; + read_symbolic_ref_fn *read_symbolic_ref; reflog_iterator_begin_fn *reflog_iterator_begin; for_each_reflog_ent_fn *for_each_reflog_ent; @@ -656,23 +694,45 @@ struct ref_storage_be { }; extern struct ref_storage_be refs_be_files; +extern struct ref_storage_be refs_be_reftable; extern struct ref_storage_be refs_be_packed; /* * A representation of the reference store for the main repository or * a submodule. The ref_store instances for submodules are kept in a - * linked list. + * hash map; see get_submodule_ref_store() for more info. */ struct ref_store { /* The backend describing this ref_store's storage scheme: */ const struct ref_storage_be *be; + + struct repository *repo; + + /* + * The gitdir that this ref_store applies to. Note that this is not + * necessarily repo->gitdir if the repo has multiple worktrees. + */ + char *gitdir; }; /* + * Parse contents of a loose ref file. *failure_errno maybe be set to EINVAL for + * invalid contents. + */ +int parse_loose_ref_contents(const char *buf, struct object_id *oid, + struct strbuf *referent, unsigned int *type, + int *failure_errno); + +/* * Fill in the generic part of refs and add it to our collection of * reference stores. */ -void base_ref_store_init(struct ref_store *refs, - const struct ref_storage_be *be); +void base_ref_store_init(struct ref_store *refs, struct repository *repo, + const char *path, const struct ref_storage_be *be); + +/* + * Support GIT_TRACE_REFS by optionally wrapping the given ref_store instance. + */ +struct ref_store *maybe_debug_wrap_ref_store(const char *gitdir, struct ref_store *store); #endif /* REFS_REFS_INTERNAL_H */ diff --git a/refs/reftable-backend.c b/refs/reftable-backend.c new file mode 100644 index 0000000..1cda48c --- /dev/null +++ b/refs/reftable-backend.c @@ -0,0 +1,2242 @@ +#include "../git-compat-util.h" +#include "../abspath.h" +#include "../chdir-notify.h" +#include "../environment.h" +#include "../gettext.h" +#include "../hash.h" +#include "../hex.h" +#include "../iterator.h" +#include "../ident.h" +#include "../lockfile.h" +#include "../object.h" +#include "../path.h" +#include "../refs.h" +#include "../reftable/reftable-stack.h" +#include "../reftable/reftable-record.h" +#include "../reftable/reftable-error.h" +#include "../reftable/reftable-iterator.h" +#include "../reftable/reftable-merged.h" +#include "../setup.h" +#include "../strmap.h" +#include "parse.h" +#include "refs-internal.h" + +/* + * Used as a flag in ref_update::flags when the ref_update was via an + * update to HEAD. + */ +#define REF_UPDATE_VIA_HEAD (1 << 8) + +struct reftable_ref_store { + struct ref_store base; + + /* + * The main stack refers to the common dir and thus contains common + * refs as well as refs of the main repository. + */ + struct reftable_stack *main_stack; + /* + * The worktree stack refers to the gitdir in case the refdb is opened + * via a worktree. It thus contains the per-worktree refs. + */ + struct reftable_stack *worktree_stack; + /* + * Map of worktree stacks by their respective worktree names. The map + * is populated lazily when we try to resolve `worktrees/$worktree` refs. + */ + struct strmap worktree_stacks; + struct reftable_write_options write_options; + + unsigned int store_flags; + int err; +}; + +/* + * Downcast ref_store to reftable_ref_store. Die if ref_store is not a + * reftable_ref_store. required_flags is compared with ref_store's store_flags + * to ensure the ref_store has all required capabilities. "caller" is used in + * any necessary error messages. + */ +static struct reftable_ref_store *reftable_be_downcast(struct ref_store *ref_store, + unsigned int required_flags, + const char *caller) +{ + struct reftable_ref_store *refs; + + if (ref_store->be != &refs_be_reftable) + BUG("ref_store is type \"%s\" not \"reftables\" in %s", + ref_store->be->name, caller); + + refs = (struct reftable_ref_store *)ref_store; + + if ((refs->store_flags & required_flags) != required_flags) + BUG("operation %s requires abilities 0x%x, but only have 0x%x", + caller, required_flags, refs->store_flags); + + return refs; +} + +/* + * Some refs are global to the repository (refs/heads/{*}), while others are + * local to the worktree (eg. HEAD, refs/bisect/{*}). We solve this by having + * multiple separate databases (ie. multiple reftable/ directories), one for + * the shared refs, one for the current worktree refs, and one for each + * additional worktree. For reading, we merge the view of both the shared and + * the current worktree's refs, when necessary. + * + * This function also optionally assigns the rewritten reference name that is + * local to the stack. This translation is required when using worktree refs + * like `worktrees/$worktree/refs/heads/foo` as worktree stacks will store + * those references in their normalized form. + */ +static struct reftable_stack *stack_for(struct reftable_ref_store *store, + const char *refname, + const char **rewritten_ref) +{ + const char *wtname; + int wtname_len; + + if (!refname) + return store->main_stack; + + switch (parse_worktree_ref(refname, &wtname, &wtname_len, rewritten_ref)) { + case REF_WORKTREE_OTHER: { + static struct strbuf wtname_buf = STRBUF_INIT; + struct strbuf wt_dir = STRBUF_INIT; + struct reftable_stack *stack; + + /* + * We're using a static buffer here so that we don't need to + * allocate the worktree name whenever we look up a reference. + * This could be avoided if the strmap interface knew how to + * handle keys with a length. + */ + strbuf_reset(&wtname_buf); + strbuf_add(&wtname_buf, wtname, wtname_len); + + /* + * There is an edge case here: when the worktree references the + * current worktree, then we set up the stack once via + * `worktree_stacks` and once via `worktree_stack`. This is + * wasteful, but in the reading case it shouldn't matter. And + * in the writing case we would notice that the stack is locked + * already and error out when trying to write a reference via + * both stacks. + */ + stack = strmap_get(&store->worktree_stacks, wtname_buf.buf); + if (!stack) { + strbuf_addf(&wt_dir, "%s/worktrees/%s/reftable", + store->base.repo->commondir, wtname_buf.buf); + + store->err = reftable_new_stack(&stack, wt_dir.buf, + store->write_options); + assert(store->err != REFTABLE_API_ERROR); + strmap_put(&store->worktree_stacks, wtname_buf.buf, stack); + } + + strbuf_release(&wt_dir); + return stack; + } + case REF_WORKTREE_CURRENT: + /* + * If there is no worktree stack then we're currently in the + * main worktree. We thus return the main stack in that case. + */ + if (!store->worktree_stack) + return store->main_stack; + return store->worktree_stack; + case REF_WORKTREE_MAIN: + case REF_WORKTREE_SHARED: + return store->main_stack; + default: + BUG("unhandled worktree reference type"); + } +} + +static int should_write_log(struct ref_store *refs, const char *refname) +{ + if (log_all_ref_updates == LOG_REFS_UNSET) + log_all_ref_updates = is_bare_repository() ? LOG_REFS_NONE : LOG_REFS_NORMAL; + + switch (log_all_ref_updates) { + case LOG_REFS_NONE: + return refs_reflog_exists(refs, refname); + case LOG_REFS_ALWAYS: + return 1; + case LOG_REFS_NORMAL: + if (should_autocreate_reflog(refname)) + return 1; + return refs_reflog_exists(refs, refname); + default: + BUG("unhandled core.logAllRefUpdates value %d", log_all_ref_updates); + } +} + +static void fill_reftable_log_record(struct reftable_log_record *log) +{ + const char *info = git_committer_info(0); + struct ident_split split = {0}; + int sign = 1; + + if (split_ident_line(&split, info, strlen(info))) + BUG("failed splitting committer info"); + + reftable_log_record_release(log); + log->value_type = REFTABLE_LOG_UPDATE; + log->value.update.name = + xstrndup(split.name_begin, split.name_end - split.name_begin); + log->value.update.email = + xstrndup(split.mail_begin, split.mail_end - split.mail_begin); + log->value.update.time = atol(split.date_begin); + if (*split.tz_begin == '-') { + sign = -1; + split.tz_begin++; + } + if (*split.tz_begin == '+') { + sign = 1; + split.tz_begin++; + } + + log->value.update.tz_offset = sign * atoi(split.tz_begin); +} + +static int read_ref_without_reload(struct reftable_stack *stack, + const char *refname, + struct object_id *oid, + struct strbuf *referent, + unsigned int *type) +{ + struct reftable_ref_record ref = {0}; + int ret; + + ret = reftable_stack_read_ref(stack, refname, &ref); + if (ret) + goto done; + + if (ref.value_type == REFTABLE_REF_SYMREF) { + strbuf_reset(referent); + strbuf_addstr(referent, ref.value.symref); + *type |= REF_ISSYMREF; + } else if (reftable_ref_record_val1(&ref)) { + oidread(oid, reftable_ref_record_val1(&ref)); + } else { + /* We got a tombstone, which should not happen. */ + BUG("unhandled reference value type %d", ref.value_type); + } + +done: + assert(ret != REFTABLE_API_ERROR); + reftable_ref_record_release(&ref); + return ret; +} + +static struct ref_store *reftable_be_init(struct repository *repo, + const char *gitdir, + unsigned int store_flags) +{ + struct reftable_ref_store *refs = xcalloc(1, sizeof(*refs)); + struct strbuf path = STRBUF_INIT; + int is_worktree; + mode_t mask; + + mask = umask(0); + umask(mask); + + base_ref_store_init(&refs->base, repo, gitdir, &refs_be_reftable); + strmap_init(&refs->worktree_stacks); + refs->store_flags = store_flags; + refs->write_options.block_size = 4096; + refs->write_options.hash_id = repo->hash_algo->format_id; + refs->write_options.default_permissions = calc_shared_perm(0666 & ~mask); + refs->write_options.disable_auto_compact = + !git_env_bool("GIT_TEST_REFTABLE_AUTOCOMPACTION", 1); + + /* + * Set up the main reftable stack that is hosted in GIT_COMMON_DIR. + * This stack contains both the shared and the main worktree refs. + * + * Note that we don't try to resolve the path in case we have a + * worktree because `get_common_dir_noenv()` already does it for us. + */ + is_worktree = get_common_dir_noenv(&path, gitdir); + if (!is_worktree) { + strbuf_reset(&path); + strbuf_realpath(&path, gitdir, 0); + } + strbuf_addstr(&path, "/reftable"); + refs->err = reftable_new_stack(&refs->main_stack, path.buf, + refs->write_options); + if (refs->err) + goto done; + + /* + * If we're in a worktree we also need to set up the worktree reftable + * stack that is contained in the per-worktree GIT_DIR. + * + * Ideally, we would also add the stack to our worktree stack map. But + * we have no way to figure out the worktree name here and thus can't + * do it efficiently. + */ + if (is_worktree) { + strbuf_reset(&path); + strbuf_addf(&path, "%s/reftable", gitdir); + + refs->err = reftable_new_stack(&refs->worktree_stack, path.buf, + refs->write_options); + if (refs->err) + goto done; + } + + chdir_notify_reparent("reftables-backend $GIT_DIR", &refs->base.gitdir); + +done: + assert(refs->err != REFTABLE_API_ERROR); + strbuf_release(&path); + return &refs->base; +} + +static int reftable_be_init_db(struct ref_store *ref_store, + int flags UNUSED, + struct strbuf *err UNUSED) +{ + struct reftable_ref_store *refs = + reftable_be_downcast(ref_store, REF_STORE_WRITE, "init_db"); + struct strbuf sb = STRBUF_INIT; + + strbuf_addf(&sb, "%s/reftable", refs->base.gitdir); + safe_create_dir(sb.buf, 1); + strbuf_reset(&sb); + + strbuf_addf(&sb, "%s/HEAD", refs->base.gitdir); + write_file(sb.buf, "ref: refs/heads/.invalid"); + adjust_shared_perm(sb.buf); + strbuf_reset(&sb); + + strbuf_addf(&sb, "%s/refs", refs->base.gitdir); + safe_create_dir(sb.buf, 1); + strbuf_reset(&sb); + + strbuf_addf(&sb, "%s/refs/heads", refs->base.gitdir); + write_file(sb.buf, "this repository uses the reftable format"); + adjust_shared_perm(sb.buf); + + strbuf_release(&sb); + return 0; +} + +struct reftable_ref_iterator { + struct ref_iterator base; + struct reftable_ref_store *refs; + struct reftable_iterator iter; + struct reftable_ref_record ref; + struct object_id oid; + + const char *prefix; + size_t prefix_len; + unsigned int flags; + int err; +}; + +static int reftable_ref_iterator_advance(struct ref_iterator *ref_iterator) +{ + struct reftable_ref_iterator *iter = + (struct reftable_ref_iterator *)ref_iterator; + struct reftable_ref_store *refs = iter->refs; + + while (!iter->err) { + int flags = 0; + + iter->err = reftable_iterator_next_ref(&iter->iter, &iter->ref); + if (iter->err) + break; + + /* + * The files backend only lists references contained in "refs/" unless + * the root refs are to be included. We emulate the same behaviour here. + */ + if (!starts_with(iter->ref.refname, "refs/") && + !(iter->flags & DO_FOR_EACH_INCLUDE_ROOT_REFS && + (is_pseudoref(&iter->refs->base, iter->ref.refname) || + is_headref(&iter->refs->base, iter->ref.refname)))) { + continue; + } + + if (iter->prefix_len && + strncmp(iter->prefix, iter->ref.refname, iter->prefix_len)) { + iter->err = 1; + break; + } + + if (iter->flags & DO_FOR_EACH_PER_WORKTREE_ONLY && + parse_worktree_ref(iter->ref.refname, NULL, NULL, NULL) != + REF_WORKTREE_CURRENT) + continue; + + switch (iter->ref.value_type) { + case REFTABLE_REF_VAL1: + oidread(&iter->oid, iter->ref.value.val1); + break; + case REFTABLE_REF_VAL2: + oidread(&iter->oid, iter->ref.value.val2.value); + break; + case REFTABLE_REF_SYMREF: + if (!refs_resolve_ref_unsafe(&iter->refs->base, iter->ref.refname, + RESOLVE_REF_READING, &iter->oid, &flags)) + oidclr(&iter->oid); + break; + default: + BUG("unhandled reference value type %d", iter->ref.value_type); + } + + if (is_null_oid(&iter->oid)) + flags |= REF_ISBROKEN; + + if (check_refname_format(iter->ref.refname, REFNAME_ALLOW_ONELEVEL)) { + if (!refname_is_safe(iter->ref.refname)) + die(_("refname is dangerous: %s"), iter->ref.refname); + oidclr(&iter->oid); + flags |= REF_BAD_NAME | REF_ISBROKEN; + } + + if (iter->flags & DO_FOR_EACH_OMIT_DANGLING_SYMREFS && + flags & REF_ISSYMREF && + flags & REF_ISBROKEN) + continue; + + if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) && + !ref_resolves_to_object(iter->ref.refname, refs->base.repo, + &iter->oid, flags)) + continue; + + iter->base.refname = iter->ref.refname; + iter->base.oid = &iter->oid; + iter->base.flags = flags; + + break; + } + + if (iter->err > 0) { + if (ref_iterator_abort(ref_iterator) != ITER_DONE) + return ITER_ERROR; + return ITER_DONE; + } + + if (iter->err < 0) { + ref_iterator_abort(ref_iterator); + return ITER_ERROR; + } + + return ITER_OK; +} + +static int reftable_ref_iterator_peel(struct ref_iterator *ref_iterator, + struct object_id *peeled) +{ + struct reftable_ref_iterator *iter = + (struct reftable_ref_iterator *)ref_iterator; + + if (iter->ref.value_type == REFTABLE_REF_VAL2) { + oidread(peeled, iter->ref.value.val2.target_value); + return 0; + } + + return -1; +} + +static int reftable_ref_iterator_abort(struct ref_iterator *ref_iterator) +{ + struct reftable_ref_iterator *iter = + (struct reftable_ref_iterator *)ref_iterator; + reftable_ref_record_release(&iter->ref); + reftable_iterator_destroy(&iter->iter); + free(iter); + return ITER_DONE; +} + +static struct ref_iterator_vtable reftable_ref_iterator_vtable = { + .advance = reftable_ref_iterator_advance, + .peel = reftable_ref_iterator_peel, + .abort = reftable_ref_iterator_abort +}; + +static struct reftable_ref_iterator *ref_iterator_for_stack(struct reftable_ref_store *refs, + struct reftable_stack *stack, + const char *prefix, + int flags) +{ + struct reftable_merged_table *merged_table; + struct reftable_ref_iterator *iter; + int ret; + + 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; + + ret = refs->err; + if (ret) + goto done; + + ret = reftable_stack_reload(stack); + if (ret) + goto done; + + merged_table = reftable_stack_merged_table(stack); + + ret = reftable_merged_table_seek_ref(merged_table, &iter->iter, prefix); + if (ret) + goto done; + +done: + iter->err = ret; + return iter; +} + +static struct ref_iterator *reftable_be_iterator_begin(struct ref_store *ref_store, + const char *prefix, + const char **exclude_patterns, + unsigned int flags) +{ + struct reftable_ref_iterator *main_iter, *worktree_iter; + struct reftable_ref_store *refs; + unsigned int required_flags = REF_STORE_READ; + + if (!(flags & DO_FOR_EACH_INCLUDE_BROKEN)) + required_flags |= REF_STORE_ODB; + refs = reftable_be_downcast(ref_store, required_flags, "ref_iterator_begin"); + + main_iter = ref_iterator_for_stack(refs, refs->main_stack, prefix, flags); + + /* + * The worktree stack is only set when we're in an actual worktree + * right now. If we aren't, then we return the common reftable + * iterator, only. + */ + if (!refs->worktree_stack) + return &main_iter->base; + + /* + * Otherwise we merge both the common and the per-worktree refs into a + * single iterator. + */ + worktree_iter = ref_iterator_for_stack(refs, refs->worktree_stack, prefix, flags); + return merge_ref_iterator_begin(&worktree_iter->base, &main_iter->base, + ref_iterator_select, NULL); +} + +static int reftable_be_read_raw_ref(struct ref_store *ref_store, + const char *refname, + struct object_id *oid, + struct strbuf *referent, + unsigned int *type, + int *failure_errno) +{ + struct reftable_ref_store *refs = + reftable_be_downcast(ref_store, REF_STORE_READ, "read_raw_ref"); + struct reftable_stack *stack = stack_for(refs, refname, &refname); + int ret; + + if (refs->err < 0) + return refs->err; + + ret = reftable_stack_reload(stack); + if (ret) + return ret; + + ret = read_ref_without_reload(stack, refname, oid, referent, type); + if (ret < 0) + return ret; + if (ret > 0) { + *failure_errno = ENOENT; + return -1; + } + + return 0; +} + +static int reftable_be_read_symbolic_ref(struct ref_store *ref_store, + const char *refname, + struct strbuf *referent) +{ + struct reftable_ref_store *refs = + reftable_be_downcast(ref_store, REF_STORE_READ, "read_symbolic_ref"); + struct reftable_stack *stack = stack_for(refs, refname, &refname); + struct reftable_ref_record ref = {0}; + int ret; + + ret = reftable_stack_reload(stack); + if (ret) + return ret; + + ret = reftable_stack_read_ref(stack, refname, &ref); + if (ret == 0 && ref.value_type == REFTABLE_REF_SYMREF) + strbuf_addstr(referent, ref.value.symref); + else + ret = -1; + + reftable_ref_record_release(&ref); + return ret; +} + +/* + * Return the refname under which update was originally requested. + */ +static const char *original_update_refname(struct ref_update *update) +{ + while (update->parent_update) + update = update->parent_update; + return update->refname; +} + +struct reftable_transaction_update { + struct ref_update *update; + struct object_id current_oid; +}; + +struct write_transaction_table_arg { + struct reftable_ref_store *refs; + struct reftable_stack *stack; + struct reftable_addition *addition; + struct reftable_transaction_update *updates; + size_t updates_nr; + size_t updates_alloc; + size_t updates_expected; +}; + +struct reftable_transaction_data { + struct write_transaction_table_arg *args; + size_t args_nr, args_alloc; +}; + +static void free_transaction_data(struct reftable_transaction_data *tx_data) +{ + if (!tx_data) + return; + for (size_t i = 0; i < tx_data->args_nr; i++) { + reftable_addition_destroy(tx_data->args[i].addition); + free(tx_data->args[i].updates); + } + free(tx_data->args); + free(tx_data); +} + +/* + * Prepare transaction update for the given reference update. This will cause + * us to lock the corresponding reftable stack for concurrent modification. + */ +static int prepare_transaction_update(struct write_transaction_table_arg **out, + struct reftable_ref_store *refs, + struct reftable_transaction_data *tx_data, + struct ref_update *update, + struct strbuf *err) +{ + struct reftable_stack *stack = stack_for(refs, update->refname, NULL); + struct write_transaction_table_arg *arg = NULL; + size_t i; + int ret; + + /* + * Search for a preexisting stack update. If there is one then we add + * the update to it, otherwise we set up a new stack update. + */ + for (i = 0; !arg && i < tx_data->args_nr; i++) + if (tx_data->args[i].stack == stack) + arg = &tx_data->args[i]; + + if (!arg) { + struct reftable_addition *addition; + + ret = reftable_stack_reload(stack); + if (ret) + return ret; + + ret = reftable_stack_new_addition(&addition, stack); + if (ret) { + if (ret == REFTABLE_LOCK_ERROR) + strbuf_addstr(err, "cannot lock references"); + return ret; + } + + ALLOC_GROW(tx_data->args, tx_data->args_nr + 1, + tx_data->args_alloc); + arg = &tx_data->args[tx_data->args_nr++]; + arg->refs = refs; + arg->stack = stack; + arg->addition = addition; + arg->updates = NULL; + arg->updates_nr = 0; + arg->updates_alloc = 0; + arg->updates_expected = 0; + } + + arg->updates_expected++; + + if (out) + *out = arg; + + return 0; +} + +/* + * Queue a reference update for the correct stack. We potentially need to + * handle multiple stack updates in a single transaction when it spans across + * multiple worktrees. + */ +static int queue_transaction_update(struct reftable_ref_store *refs, + struct reftable_transaction_data *tx_data, + struct ref_update *update, + struct object_id *current_oid, + struct strbuf *err) +{ + struct write_transaction_table_arg *arg = NULL; + int ret; + + if (update->backend_data) + BUG("reference update queued more than once"); + + ret = prepare_transaction_update(&arg, refs, tx_data, update, err); + if (ret < 0) + return ret; + + ALLOC_GROW(arg->updates, arg->updates_nr + 1, + arg->updates_alloc); + arg->updates[arg->updates_nr].update = update; + oidcpy(&arg->updates[arg->updates_nr].current_oid, current_oid); + update->backend_data = &arg->updates[arg->updates_nr++]; + + return 0; +} + +static int reftable_be_transaction_prepare(struct ref_store *ref_store, + struct ref_transaction *transaction, + struct strbuf *err) +{ + struct reftable_ref_store *refs = + 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 reftable_transaction_data *tx_data = NULL; + struct object_id head_oid; + unsigned int head_type = 0; + size_t i; + int ret; + + ret = refs->err; + if (ret < 0) + goto done; + + tx_data = xcalloc(1, sizeof(*tx_data)); + + /* + * Preprocess all updates. For one we check that there are no duplicate + * reference updates in this transaction. Second, we lock all stacks + * that will be modified during the transaction. + */ + for (i = 0; i < transaction->nr; i++) { + ret = prepare_transaction_update(NULL, refs, tx_data, + transaction->updates[i], err); + if (ret) + goto done; + + string_list_append(&affected_refnames, + transaction->updates[i]->refname); + } + + /* + * Now that we have counted updates per stack we can preallocate their + * arrays. This avoids having to reallocate many times. + */ + for (i = 0; i < tx_data->args_nr; i++) { + CALLOC_ARRAY(tx_data->args[i].updates, 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; + } + + ret = read_ref_without_reload(stack_for(refs, "HEAD", NULL), "HEAD", &head_oid, + &head_referent, &head_type); + if (ret < 0) + goto done; + ret = 0; + + for (i = 0; i < transaction->nr; i++) { + struct ref_update *u = transaction->updates[i]; + struct object_id current_oid = {0}; + struct reftable_stack *stack; + const char *rewritten_ref; + + stack = stack_for(refs, u->refname, &rewritten_ref); + + /* 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, u->msg); + string_list_insert(&affected_refnames, new_update->refname); + } + + ret = read_ref_without_reload(stack, rewritten_ref, + ¤t_oid, &referent, &u->type); + if (ret < 0) + goto done; + if (ret > 0 && (!(u->flags & REF_HAVE_OLD) || is_null_oid(&u->old_oid))) { + /* + * 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. + */ + ret = refs_verify_refname_available(ref_store, u->refname, + &affected_refnames, NULL, err); + if (ret < 0) + goto done; + + /* + * There is no need to write the reference deletion + * when the reference in question doesn't exist. + */ + if (u->flags & REF_HAVE_NEW && !is_null_oid(&u->new_oid)) { + 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'"), + 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_oid, &u->old_oid, 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->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"), + original_update_refname(u)); + else if (is_null_oid(¤t_oid)) + strbuf_addf(err, _("cannot lock ref '%s': " + "reference is missing but expected %s"), + original_update_refname(u), + oid_to_hex(&u->old_oid)); + else + strbuf_addf(err, _("cannot lock ref '%s': " + "is at %s but expected %s"), + original_update_refname(u), + oid_to_hex(¤t_oid), + oid_to_hex(&u->old_oid)); + ret = -1; + 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; + } + } + + transaction->backend_data = tx_data; + transaction->state = REF_TRANSACTION_PREPARED; + +done: + assert(ret != REFTABLE_API_ERROR); + if (ret < 0) { + free_transaction_data(tx_data); + transaction->state = REF_TRANSACTION_CLOSED; + if (!err->len) + strbuf_addf(err, _("reftable: transaction prepare: %s"), + reftable_error_str(ret)); + } + string_list_clear(&affected_refnames, 0); + strbuf_release(&referent); + strbuf_release(&head_referent); + + return ret; +} + +static int reftable_be_transaction_abort(struct ref_store *ref_store, + struct ref_transaction *transaction, + struct strbuf *err) +{ + struct reftable_transaction_data *tx_data = transaction->backend_data; + free_transaction_data(tx_data); + transaction->state = REF_TRANSACTION_CLOSED; + return 0; +} + +static int transaction_update_cmp(const void *a, const void *b) +{ + return strcmp(((struct reftable_transaction_update *)a)->update->refname, + ((struct reftable_transaction_update *)b)->update->refname); +} + +static int write_transaction_table(struct reftable_writer *writer, void *cb_data) +{ + struct write_transaction_table_arg *arg = cb_data; + struct reftable_merged_table *mt = + reftable_stack_merged_table(arg->stack); + uint64_t ts = reftable_stack_next_update_index(arg->stack); + struct reftable_log_record *logs = NULL; + size_t logs_nr = 0, logs_alloc = 0, i; + int ret = 0; + + QSORT(arg->updates, arg->updates_nr, transaction_update_cmp); + + reftable_writer_set_limits(writer, ts, ts); + + for (i = 0; i < arg->updates_nr; i++) { + struct reftable_transaction_update *tx_update = &arg->updates[i]; + struct ref_update *u = tx_update->update; + + /* + * Write a reflog entry when updating a ref to point to + * something new in either of the following cases: + * + * - The reference is about to be deleted. We always want to + * delete the reflog in that case. + * - REF_FORCE_CREATE_REFLOG is set, asking us to always create + * the reflog entry. + * - `core.logAllRefUpdates` tells us to create the reflog for + * the given ref. + */ + if (u->flags & REF_HAVE_NEW && !(u->type & REF_ISSYMREF) && is_null_oid(&u->new_oid)) { + struct reftable_log_record log = {0}; + struct reftable_iterator it = {0}; + + /* + * When deleting refs we also delete all reflog entries + * with them. While it is not strictly required to + * delete reflogs together with their refs, this + * matches the behaviour of the files backend. + * + * Unfortunately, we have no better way than to delete + * all reflog entries one by one. + */ + ret = reftable_merged_table_seek_log(mt, &it, u->refname); + while (ret == 0) { + struct reftable_log_record *tombstone; + + ret = reftable_iterator_next_log(&it, &log); + if (ret < 0) + break; + if (ret > 0 || strcmp(log.refname, u->refname)) { + ret = 0; + break; + } + + ALLOC_GROW(logs, logs_nr + 1, logs_alloc); + tombstone = &logs[logs_nr++]; + tombstone->refname = xstrdup(u->refname); + tombstone->value_type = REFTABLE_LOG_DELETION; + tombstone->update_index = log.update_index; + } + + reftable_log_record_release(&log); + reftable_iterator_destroy(&it); + + if (ret) + goto done; + } else if (u->flags & REF_HAVE_NEW && + (u->flags & REF_FORCE_CREATE_REFLOG || + should_write_log(&arg->refs->base, u->refname))) { + struct reftable_log_record *log; + + ALLOC_GROW(logs, logs_nr + 1, logs_alloc); + log = &logs[logs_nr++]; + memset(log, 0, sizeof(*log)); + + fill_reftable_log_record(log); + log->update_index = ts; + log->refname = xstrdup(u->refname); + memcpy(log->value.update.new_hash, u->new_oid.hash, GIT_MAX_RAWSZ); + memcpy(log->value.update.old_hash, tx_update->current_oid.hash, GIT_MAX_RAWSZ); + log->value.update.message = + xstrndup(u->msg, arg->refs->write_options.block_size / 2); + } + + if (u->flags & REF_LOG_ONLY) + continue; + + if (u->flags & REF_HAVE_NEW && is_null_oid(&u->new_oid)) { + struct reftable_ref_record ref = { + .refname = (char *)u->refname, + .update_index = ts, + .value_type = REFTABLE_REF_DELETION, + }; + + ret = reftable_writer_add_ref(writer, &ref); + if (ret < 0) + goto done; + } else if (u->flags & REF_HAVE_NEW) { + struct reftable_ref_record ref = {0}; + struct object_id peeled; + int peel_error; + + ref.refname = (char *)u->refname; + ref.update_index = ts; + + peel_error = peel_object(&u->new_oid, &peeled); + if (!peel_error) { + ref.value_type = REFTABLE_REF_VAL2; + memcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ); + memcpy(ref.value.val2.value, u->new_oid.hash, GIT_MAX_RAWSZ); + } else if (!is_null_oid(&u->new_oid)) { + ref.value_type = REFTABLE_REF_VAL1; + memcpy(ref.value.val1, u->new_oid.hash, GIT_MAX_RAWSZ); + } + + ret = reftable_writer_add_ref(writer, &ref); + if (ret < 0) + goto done; + } + } + + /* + * Logs are written at the end so that we do not have intermixed ref + * and log blocks. + */ + if (logs) { + ret = reftable_writer_add_logs(writer, logs, logs_nr); + if (ret < 0) + goto done; + } + +done: + assert(ret != REFTABLE_API_ERROR); + for (i = 0; i < logs_nr; i++) + reftable_log_record_release(&logs[i]); + free(logs); + return ret; +} + +static int reftable_be_transaction_finish(struct ref_store *ref_store, + struct ref_transaction *transaction, + struct strbuf *err) +{ + struct reftable_transaction_data *tx_data = transaction->backend_data; + int ret = 0; + + for (size_t i = 0; i < tx_data->args_nr; i++) { + ret = reftable_addition_add(tx_data->args[i].addition, + write_transaction_table, &tx_data->args[i]); + if (ret < 0) + goto done; + + ret = reftable_addition_commit(tx_data->args[i].addition); + if (ret < 0) + goto done; + } + +done: + assert(ret != REFTABLE_API_ERROR); + free_transaction_data(tx_data); + transaction->state = REF_TRANSACTION_CLOSED; + + if (ret) { + strbuf_addf(err, _("reftable: transaction failure: %s"), + reftable_error_str(ret)); + return -1; + } + return ret; +} + +static int reftable_be_initial_transaction_commit(struct ref_store *ref_store UNUSED, + struct ref_transaction *transaction, + struct strbuf *err) +{ + return ref_transaction_commit(transaction, err); +} + +static int reftable_be_pack_refs(struct ref_store *ref_store, + struct pack_refs_opts *opts) +{ + struct reftable_ref_store *refs = + reftable_be_downcast(ref_store, REF_STORE_WRITE | REF_STORE_ODB, "pack_refs"); + struct reftable_stack *stack; + int ret; + + if (refs->err) + return refs->err; + + stack = refs->worktree_stack; + if (!stack) + stack = refs->main_stack; + + if (opts->flags & PACK_REFS_AUTO) + ret = reftable_stack_auto_compact(stack); + else + ret = reftable_stack_compact_all(stack, NULL); + if (ret < 0) { + ret = error(_("unable to compact stack: %s"), + reftable_error_str(ret)); + goto out; + } + + ret = reftable_stack_clean(stack); + if (ret) + goto out; + +out: + return ret; +} + +struct write_create_symref_arg { + struct reftable_ref_store *refs; + struct reftable_stack *stack; + const char *refname; + const char *target; + const char *logmsg; +}; + +static int write_create_symref_table(struct reftable_writer *writer, void *cb_data) +{ + struct write_create_symref_arg *create = cb_data; + uint64_t ts = reftable_stack_next_update_index(create->stack); + struct reftable_ref_record ref = { + .refname = (char *)create->refname, + .value_type = REFTABLE_REF_SYMREF, + .value.symref = (char *)create->target, + .update_index = ts, + }; + struct reftable_log_record log = {0}; + struct object_id new_oid; + struct object_id old_oid; + int ret; + + reftable_writer_set_limits(writer, ts, ts); + + ret = reftable_writer_add_ref(writer, &ref); + if (ret) + return ret; + + /* + * Note that it is important to try and resolve the reference before we + * write the log entry. This is because `should_write_log()` will munge + * `core.logAllRefUpdates`, which is undesirable when we create a new + * repository because it would be written into the config. As HEAD will + * not resolve for new repositories this ordering will ensure that this + * never happens. + */ + if (!create->logmsg || + !refs_resolve_ref_unsafe(&create->refs->base, create->target, + RESOLVE_REF_READING, &new_oid, NULL) || + !should_write_log(&create->refs->base, create->refname)) + return 0; + + fill_reftable_log_record(&log); + log.refname = xstrdup(create->refname); + log.update_index = ts; + log.value.update.message = xstrndup(create->logmsg, + create->refs->write_options.block_size / 2); + memcpy(log.value.update.new_hash, new_oid.hash, GIT_MAX_RAWSZ); + if (refs_resolve_ref_unsafe(&create->refs->base, create->refname, + RESOLVE_REF_READING, &old_oid, NULL)) + memcpy(log.value.update.old_hash, old_oid.hash, GIT_MAX_RAWSZ); + + ret = reftable_writer_add_log(writer, &log); + reftable_log_record_release(&log); + return ret; +} + +static int reftable_be_create_symref(struct ref_store *ref_store, + const char *refname, + const char *target, + const char *logmsg) +{ + struct reftable_ref_store *refs = + reftable_be_downcast(ref_store, REF_STORE_WRITE, "create_symref"); + struct reftable_stack *stack = stack_for(refs, refname, &refname); + struct write_create_symref_arg arg = { + .refs = refs, + .stack = stack, + .refname = refname, + .target = target, + .logmsg = logmsg, + }; + int ret; + + ret = refs->err; + if (ret < 0) + goto done; + + ret = reftable_stack_reload(stack); + if (ret) + goto done; + + ret = reftable_stack_add(stack, &write_create_symref_table, &arg); + +done: + assert(ret != REFTABLE_API_ERROR); + if (ret) + error("unable to write symref for %s: %s", refname, + reftable_error_str(ret)); + return ret; +} + +struct write_copy_arg { + struct reftable_ref_store *refs; + struct reftable_stack *stack; + const char *oldname; + const char *newname; + const char *logmsg; + int delete_old; +}; + +static int write_copy_table(struct reftable_writer *writer, void *cb_data) +{ + struct write_copy_arg *arg = cb_data; + uint64_t deletion_ts, creation_ts; + struct reftable_merged_table *mt = reftable_stack_merged_table(arg->stack); + struct reftable_ref_record old_ref = {0}, refs[2] = {0}; + struct reftable_log_record old_log = {0}, *logs = NULL; + struct reftable_iterator it = {0}; + struct string_list skip = STRING_LIST_INIT_NODUP; + struct strbuf errbuf = STRBUF_INIT; + size_t logs_nr = 0, logs_alloc = 0, i; + int ret; + + if (reftable_stack_read_ref(arg->stack, arg->oldname, &old_ref)) { + ret = error(_("refname %s not found"), arg->oldname); + goto done; + } + if (old_ref.value_type == REFTABLE_REF_SYMREF) { + ret = error(_("refname %s is a symbolic ref, copying it is not supported"), + arg->oldname); + goto done; + } + + /* + * There's nothing to do in case the old and new name are the same, so + * we exit early in that case. + */ + if (!strcmp(arg->oldname, arg->newname)) { + ret = 0; + goto done; + } + + /* + * Verify that the new refname is available. + */ + string_list_insert(&skip, arg->oldname); + ret = refs_verify_refname_available(&arg->refs->base, arg->newname, + NULL, &skip, &errbuf); + if (ret < 0) { + error("%s", errbuf.buf); + goto done; + } + + /* + * When deleting the old reference we have to use two update indices: + * once to delete the old ref and its reflog, and once to create the + * new ref and its reflog. They need to be staged with two separate + * indices because the new reflog needs to encode both the deletion of + * the old branch and the creation of the new branch, and we cannot do + * two changes to a reflog in a single update. + */ + deletion_ts = creation_ts = reftable_stack_next_update_index(arg->stack); + if (arg->delete_old) + creation_ts++; + reftable_writer_set_limits(writer, deletion_ts, creation_ts); + + /* + * Add the new reference. If this is a rename then we also delete the + * old reference. + */ + refs[0] = old_ref; + refs[0].refname = (char *)arg->newname; + refs[0].update_index = creation_ts; + if (arg->delete_old) { + refs[1].refname = (char *)arg->oldname; + refs[1].value_type = REFTABLE_REF_DELETION; + refs[1].update_index = deletion_ts; + } + ret = reftable_writer_add_refs(writer, refs, arg->delete_old ? 2 : 1); + if (ret < 0) + goto done; + + /* + * When deleting the old branch we need to create a reflog entry on the + * new branch name that indicates that the old branch has been deleted + * and then recreated. This is a tad weird, but matches what the files + * backend does. + */ + if (arg->delete_old) { + struct strbuf head_referent = STRBUF_INIT; + struct object_id head_oid; + int append_head_reflog; + unsigned head_type = 0; + + ALLOC_GROW(logs, logs_nr + 1, logs_alloc); + memset(&logs[logs_nr], 0, sizeof(logs[logs_nr])); + fill_reftable_log_record(&logs[logs_nr]); + logs[logs_nr].refname = (char *)arg->newname; + logs[logs_nr].update_index = deletion_ts; + logs[logs_nr].value.update.message = + xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2); + memcpy(logs[logs_nr].value.update.old_hash, old_ref.value.val1, GIT_MAX_RAWSZ); + logs_nr++; + + ret = read_ref_without_reload(arg->stack, "HEAD", &head_oid, &head_referent, &head_type); + if (ret < 0) + goto done; + append_head_reflog = (head_type & REF_ISSYMREF) && !strcmp(head_referent.buf, arg->oldname); + strbuf_release(&head_referent); + + /* + * The files backend uses `refs_delete_ref()` to delete the old + * branch name, which will append a reflog entry for HEAD in + * case it points to the old branch. + */ + if (append_head_reflog) { + ALLOC_GROW(logs, logs_nr + 1, logs_alloc); + logs[logs_nr] = logs[logs_nr - 1]; + logs[logs_nr].refname = "HEAD"; + logs_nr++; + } + } + + /* + * Create the reflog entry for the newly created branch. + */ + ALLOC_GROW(logs, logs_nr + 1, logs_alloc); + memset(&logs[logs_nr], 0, sizeof(logs[logs_nr])); + fill_reftable_log_record(&logs[logs_nr]); + logs[logs_nr].refname = (char *)arg->newname; + logs[logs_nr].update_index = creation_ts; + logs[logs_nr].value.update.message = + xstrndup(arg->logmsg, arg->refs->write_options.block_size / 2); + memcpy(logs[logs_nr].value.update.new_hash, old_ref.value.val1, GIT_MAX_RAWSZ); + logs_nr++; + + /* + * In addition to writing the reflog entry for the new branch, we also + * copy over all log entries from the old reflog. Last but not least, + * when renaming we also have to delete all the old reflog entries. + */ + ret = reftable_merged_table_seek_log(mt, &it, arg->oldname); + if (ret < 0) + goto done; + + while (1) { + ret = reftable_iterator_next_log(&it, &old_log); + if (ret < 0) + goto done; + if (ret > 0 || strcmp(old_log.refname, arg->oldname)) { + ret = 0; + break; + } + + free(old_log.refname); + + /* + * Copy over the old reflog entry with the new refname. + */ + ALLOC_GROW(logs, logs_nr + 1, logs_alloc); + logs[logs_nr] = old_log; + logs[logs_nr].refname = (char *)arg->newname; + logs_nr++; + + /* + * Delete the old reflog entry in case we are renaming. + */ + if (arg->delete_old) { + ALLOC_GROW(logs, logs_nr + 1, logs_alloc); + memset(&logs[logs_nr], 0, sizeof(logs[logs_nr])); + logs[logs_nr].refname = (char *)arg->oldname; + logs[logs_nr].value_type = REFTABLE_LOG_DELETION; + logs[logs_nr].update_index = old_log.update_index; + logs_nr++; + } + + /* + * Transfer ownership of the log record we're iterating over to + * the array of log records. Otherwise, the pointers would get + * free'd or reallocated by the iterator. + */ + memset(&old_log, 0, sizeof(old_log)); + } + + ret = reftable_writer_add_logs(writer, logs, logs_nr); + if (ret < 0) + goto done; + +done: + assert(ret != REFTABLE_API_ERROR); + reftable_iterator_destroy(&it); + string_list_clear(&skip, 0); + strbuf_release(&errbuf); + for (i = 0; i < logs_nr; i++) { + if (!strcmp(logs[i].refname, "HEAD")) + continue; + logs[i].refname = NULL; + reftable_log_record_release(&logs[i]); + } + free(logs); + reftable_ref_record_release(&old_ref); + reftable_log_record_release(&old_log); + return ret; +} + +static int reftable_be_rename_ref(struct ref_store *ref_store, + const char *oldrefname, + const char *newrefname, + const char *logmsg) +{ + struct reftable_ref_store *refs = + reftable_be_downcast(ref_store, REF_STORE_WRITE, "rename_ref"); + struct reftable_stack *stack = stack_for(refs, newrefname, &newrefname); + struct write_copy_arg arg = { + .refs = refs, + .stack = stack, + .oldname = oldrefname, + .newname = newrefname, + .logmsg = logmsg, + .delete_old = 1, + }; + int ret; + + ret = refs->err; + if (ret < 0) + goto done; + + ret = reftable_stack_reload(stack); + if (ret) + goto done; + ret = reftable_stack_add(stack, &write_copy_table, &arg); + +done: + assert(ret != REFTABLE_API_ERROR); + return ret; +} + +static int reftable_be_copy_ref(struct ref_store *ref_store, + const char *oldrefname, + const char *newrefname, + const char *logmsg) +{ + struct reftable_ref_store *refs = + reftable_be_downcast(ref_store, REF_STORE_WRITE, "copy_ref"); + struct reftable_stack *stack = stack_for(refs, newrefname, &newrefname); + struct write_copy_arg arg = { + .refs = refs, + .stack = stack, + .oldname = oldrefname, + .newname = newrefname, + .logmsg = logmsg, + }; + int ret; + + ret = refs->err; + if (ret < 0) + goto done; + + ret = reftable_stack_reload(stack); + if (ret) + goto done; + ret = reftable_stack_add(stack, &write_copy_table, &arg); + +done: + assert(ret != REFTABLE_API_ERROR); + return ret; +} + +struct reftable_reflog_iterator { + struct ref_iterator base; + struct reftable_ref_store *refs; + struct reftable_iterator iter; + struct reftable_log_record log; + struct strbuf last_name; + int err; +}; + +static int reftable_reflog_iterator_advance(struct ref_iterator *ref_iterator) +{ + struct reftable_reflog_iterator *iter = + (struct reftable_reflog_iterator *)ref_iterator; + + while (!iter->err) { + iter->err = reftable_iterator_next_log(&iter->iter, &iter->log); + if (iter->err) + break; + + /* + * We want the refnames that we have reflogs for, so we skip if + * we've already produced this name. This could be faster by + * seeking directly to reflog@update_index==0. + */ + if (!strcmp(iter->log.refname, iter->last_name.buf)) + continue; + + if (check_refname_format(iter->log.refname, + REFNAME_ALLOW_ONELEVEL)) + continue; + + strbuf_reset(&iter->last_name); + strbuf_addstr(&iter->last_name, iter->log.refname); + iter->base.refname = iter->log.refname; + + break; + } + + if (iter->err > 0) { + if (ref_iterator_abort(ref_iterator) != ITER_DONE) + return ITER_ERROR; + return ITER_DONE; + } + + if (iter->err < 0) { + ref_iterator_abort(ref_iterator); + return ITER_ERROR; + } + + return ITER_OK; +} + +static int reftable_reflog_iterator_peel(struct ref_iterator *ref_iterator, + struct object_id *peeled) +{ + BUG("reftable reflog iterator cannot be peeled"); + return -1; +} + +static int reftable_reflog_iterator_abort(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, + .peel = reftable_reflog_iterator_peel, + .abort = reftable_reflog_iterator_abort +}; + +static struct reftable_reflog_iterator *reflog_iterator_for_stack(struct reftable_ref_store *refs, + struct reftable_stack *stack) +{ + struct reftable_merged_table *merged_table; + struct reftable_reflog_iterator *iter; + int ret; + + iter = xcalloc(1, sizeof(*iter)); + base_ref_iterator_init(&iter->base, &reftable_reflog_iterator_vtable); + strbuf_init(&iter->last_name, 0); + iter->refs = refs; + + ret = refs->err; + if (ret) + goto done; + + ret = reftable_stack_reload(stack); + if (ret < 0) + goto done; + + merged_table = reftable_stack_merged_table(stack); + + ret = reftable_merged_table_seek_log(merged_table, &iter->iter, ""); + if (ret < 0) + goto done; + +done: + iter->err = ret; + return iter; +} + +static struct ref_iterator *reftable_be_reflog_iterator_begin(struct ref_store *ref_store) +{ + struct reftable_ref_store *refs = + reftable_be_downcast(ref_store, REF_STORE_READ, "reflog_iterator_begin"); + struct reftable_reflog_iterator *main_iter, *worktree_iter; + + main_iter = reflog_iterator_for_stack(refs, refs->main_stack); + if (!refs->worktree_stack) + return &main_iter->base; + + worktree_iter = reflog_iterator_for_stack(refs, refs->worktree_stack); + + return merge_ref_iterator_begin(&worktree_iter->base, &main_iter->base, + ref_iterator_select, NULL); +} + +static int yield_log_record(struct reftable_log_record *log, + each_reflog_ent_fn fn, + void *cb_data) +{ + struct object_id old_oid, new_oid; + const char *full_committer; + + oidread(&old_oid, log->value.update.old_hash); + oidread(&new_oid, log->value.update.new_hash); + + /* + * When both the old object ID and the new object ID are null + * then this is the reflog existence marker. The caller must + * not be aware of it. + */ + if (is_null_oid(&old_oid) && is_null_oid(&new_oid)) + return 0; + + full_committer = fmt_ident(log->value.update.name, log->value.update.email, + WANT_COMMITTER_IDENT, NULL, IDENT_NO_DATE); + return fn(&old_oid, &new_oid, full_committer, + log->value.update.time, log->value.update.tz_offset, + log->value.update.message, cb_data); +} + +static int reftable_be_for_each_reflog_ent_reverse(struct ref_store *ref_store, + const char *refname, + each_reflog_ent_fn fn, + void *cb_data) +{ + struct reftable_ref_store *refs = + reftable_be_downcast(ref_store, REF_STORE_READ, "for_each_reflog_ent_reverse"); + struct reftable_stack *stack = stack_for(refs, refname, &refname); + struct reftable_merged_table *mt = NULL; + struct reftable_log_record log = {0}; + struct reftable_iterator it = {0}; + int ret; + + if (refs->err < 0) + return refs->err; + + mt = reftable_stack_merged_table(stack); + ret = reftable_merged_table_seek_log(mt, &it, refname); + while (!ret) { + ret = reftable_iterator_next_log(&it, &log); + if (ret < 0) + break; + if (ret > 0 || strcmp(log.refname, refname)) { + ret = 0; + break; + } + + ret = yield_log_record(&log, fn, cb_data); + if (ret) + break; + } + + reftable_log_record_release(&log); + reftable_iterator_destroy(&it); + return ret; +} + +static int reftable_be_for_each_reflog_ent(struct ref_store *ref_store, + const char *refname, + each_reflog_ent_fn fn, + void *cb_data) +{ + struct reftable_ref_store *refs = + reftable_be_downcast(ref_store, REF_STORE_READ, "for_each_reflog_ent"); + struct reftable_stack *stack = stack_for(refs, refname, &refname); + struct reftable_merged_table *mt = NULL; + struct reftable_log_record *logs = NULL; + struct reftable_iterator it = {0}; + size_t logs_alloc = 0, logs_nr = 0, i; + int ret; + + if (refs->err < 0) + return refs->err; + + mt = reftable_stack_merged_table(stack); + ret = reftable_merged_table_seek_log(mt, &it, refname); + while (!ret) { + struct reftable_log_record log = {0}; + + ret = reftable_iterator_next_log(&it, &log); + if (ret < 0) + goto done; + if (ret > 0 || strcmp(log.refname, refname)) { + reftable_log_record_release(&log); + ret = 0; + break; + } + + ALLOC_GROW(logs, logs_nr + 1, logs_alloc); + logs[logs_nr++] = log; + } + + for (i = logs_nr; i--;) { + ret = yield_log_record(&logs[i], fn, cb_data); + if (ret) + goto done; + } + +done: + reftable_iterator_destroy(&it); + for (i = 0; i < logs_nr; i++) + reftable_log_record_release(&logs[i]); + free(logs); + return ret; +} + +static int reftable_be_reflog_exists(struct ref_store *ref_store, + const char *refname) +{ + struct reftable_ref_store *refs = + reftable_be_downcast(ref_store, REF_STORE_READ, "reflog_exists"); + struct reftable_stack *stack = stack_for(refs, refname, &refname); + struct reftable_merged_table *mt = reftable_stack_merged_table(stack); + struct reftable_log_record log = {0}; + struct reftable_iterator it = {0}; + int ret; + + ret = refs->err; + if (ret < 0) + goto done; + + ret = reftable_stack_reload(stack); + if (ret < 0) + goto done; + + ret = reftable_merged_table_seek_log(mt, &it, refname); + if (ret < 0) + goto done; + + /* + * Check whether we get at least one log record for the given ref name. + * If so, the reflog exists, otherwise it doesn't. + */ + ret = reftable_iterator_next_log(&it, &log); + if (ret < 0) + goto done; + if (ret > 0) { + ret = 0; + goto done; + } + + ret = strcmp(log.refname, refname) == 0; + +done: + reftable_iterator_destroy(&it); + reftable_log_record_release(&log); + if (ret < 0) + ret = 0; + return ret; +} + +struct write_reflog_existence_arg { + struct reftable_ref_store *refs; + const char *refname; + struct reftable_stack *stack; +}; + +static int write_reflog_existence_table(struct reftable_writer *writer, + void *cb_data) +{ + struct write_reflog_existence_arg *arg = cb_data; + uint64_t ts = reftable_stack_next_update_index(arg->stack); + struct reftable_log_record log = {0}; + int ret; + + ret = reftable_stack_read_log(arg->stack, arg->refname, &log); + if (ret <= 0) + goto done; + + reftable_writer_set_limits(writer, ts, ts); + + /* + * The existence entry has both old and new object ID set to the the + * null object ID. Our iterators are aware of this and will not present + * them to their callers. + */ + log.refname = xstrdup(arg->refname); + log.update_index = ts; + log.value_type = REFTABLE_LOG_UPDATE; + ret = reftable_writer_add_log(writer, &log); + +done: + assert(ret != REFTABLE_API_ERROR); + reftable_log_record_release(&log); + return ret; +} + +static int reftable_be_create_reflog(struct ref_store *ref_store, + const char *refname, + struct strbuf *errmsg) +{ + struct reftable_ref_store *refs = + reftable_be_downcast(ref_store, REF_STORE_WRITE, "create_reflog"); + struct reftable_stack *stack = stack_for(refs, refname, &refname); + struct write_reflog_existence_arg arg = { + .refs = refs, + .stack = stack, + .refname = refname, + }; + int ret; + + ret = refs->err; + if (ret < 0) + goto done; + + ret = reftable_stack_reload(stack); + if (ret) + goto done; + + ret = reftable_stack_add(stack, &write_reflog_existence_table, &arg); + +done: + return ret; +} + +struct write_reflog_delete_arg { + struct reftable_stack *stack; + const char *refname; +}; + +static int write_reflog_delete_table(struct reftable_writer *writer, void *cb_data) +{ + struct write_reflog_delete_arg *arg = cb_data; + struct reftable_merged_table *mt = + reftable_stack_merged_table(arg->stack); + struct reftable_log_record log = {0}, tombstone = {0}; + struct reftable_iterator it = {0}; + uint64_t ts = reftable_stack_next_update_index(arg->stack); + int ret; + + reftable_writer_set_limits(writer, ts, ts); + + /* + * In order to delete a table we need to delete all reflog entries one + * by one. This is inefficient, but the reftable format does not have a + * better marker right now. + */ + ret = reftable_merged_table_seek_log(mt, &it, arg->refname); + while (ret == 0) { + ret = reftable_iterator_next_log(&it, &log); + if (ret < 0) + break; + if (ret > 0 || strcmp(log.refname, arg->refname)) { + ret = 0; + break; + } + + tombstone.refname = (char *)arg->refname; + tombstone.value_type = REFTABLE_LOG_DELETION; + tombstone.update_index = log.update_index; + + ret = reftable_writer_add_log(writer, &tombstone); + } + + reftable_log_record_release(&log); + reftable_iterator_destroy(&it); + return ret; +} + +static int reftable_be_delete_reflog(struct ref_store *ref_store, + const char *refname) +{ + struct reftable_ref_store *refs = + reftable_be_downcast(ref_store, REF_STORE_WRITE, "delete_reflog"); + struct reftable_stack *stack = stack_for(refs, refname, &refname); + struct write_reflog_delete_arg arg = { + .stack = stack, + .refname = refname, + }; + int ret; + + ret = reftable_stack_reload(stack); + if (ret) + return ret; + ret = reftable_stack_add(stack, &write_reflog_delete_table, &arg); + + assert(ret != REFTABLE_API_ERROR); + return ret; +} + +struct reflog_expiry_arg { + struct reftable_stack *stack; + struct reftable_log_record *records; + struct object_id update_oid; + const char *refname; + size_t len; +}; + +static int write_reflog_expiry_table(struct reftable_writer *writer, void *cb_data) +{ + struct reflog_expiry_arg *arg = cb_data; + uint64_t ts = reftable_stack_next_update_index(arg->stack); + uint64_t live_records = 0; + size_t i; + int ret; + + for (i = 0; i < arg->len; i++) + if (arg->records[i].value_type == REFTABLE_LOG_UPDATE) + live_records++; + + reftable_writer_set_limits(writer, ts, ts); + + if (!is_null_oid(&arg->update_oid)) { + struct reftable_ref_record ref = {0}; + struct object_id peeled; + + ref.refname = (char *)arg->refname; + ref.update_index = ts; + + if (!peel_object(&arg->update_oid, &peeled)) { + ref.value_type = REFTABLE_REF_VAL2; + memcpy(ref.value.val2.target_value, peeled.hash, GIT_MAX_RAWSZ); + memcpy(ref.value.val2.value, arg->update_oid.hash, GIT_MAX_RAWSZ); + } else { + ref.value_type = REFTABLE_REF_VAL1; + memcpy(ref.value.val1, arg->update_oid.hash, GIT_MAX_RAWSZ); + } + + ret = reftable_writer_add_ref(writer, &ref); + if (ret < 0) + return ret; + } + + /* + * When there are no more entries left in the reflog we empty it + * completely, but write a placeholder reflog entry that indicates that + * the reflog still exists. + */ + if (!live_records) { + struct reftable_log_record log = { + .refname = (char *)arg->refname, + .value_type = REFTABLE_LOG_UPDATE, + .update_index = ts, + }; + + ret = reftable_writer_add_log(writer, &log); + if (ret) + return ret; + } + + for (i = 0; i < arg->len; i++) { + ret = reftable_writer_add_log(writer, &arg->records[i]); + if (ret) + return ret; + } + + return 0; +} + +static int reftable_be_reflog_expire(struct ref_store *ref_store, + const char *refname, + unsigned int flags, + reflog_expiry_prepare_fn prepare_fn, + reflog_expiry_should_prune_fn should_prune_fn, + reflog_expiry_cleanup_fn cleanup_fn, + void *policy_cb_data) +{ + /* + * For log expiry, we write tombstones for every single reflog entry + * that is to be expired. This means that the entries are still + * retrievable by delving into the stack, and expiring entries + * paradoxically takes extra memory. This memory is only reclaimed when + * compacting the reftable stack. + * + * It would be better if the refs backend supported an API that sets a + * criterion for all refs, passing the criterion to pack_refs(). + * + * On the plus side, because we do the expiration per ref, we can easily + * insert the reflog existence dummies. + */ + struct reftable_ref_store *refs = + reftable_be_downcast(ref_store, REF_STORE_WRITE, "reflog_expire"); + struct reftable_stack *stack = stack_for(refs, refname, &refname); + struct reftable_merged_table *mt = reftable_stack_merged_table(stack); + struct reftable_log_record *logs = NULL; + struct reftable_log_record *rewritten = NULL; + struct reftable_ref_record ref_record = {0}; + struct reftable_iterator it = {0}; + struct reftable_addition *add = NULL; + struct reflog_expiry_arg arg = {0}; + struct object_id oid = {0}; + uint8_t *last_hash = NULL; + size_t logs_nr = 0, logs_alloc = 0, i; + int ret; + + if (refs->err < 0) + return refs->err; + + ret = reftable_stack_reload(stack); + if (ret < 0) + goto done; + + ret = reftable_merged_table_seek_log(mt, &it, refname); + if (ret < 0) + goto done; + + ret = reftable_stack_new_addition(&add, stack); + if (ret < 0) + goto done; + + ret = reftable_stack_read_ref(stack, refname, &ref_record); + if (ret < 0) + goto done; + if (reftable_ref_record_val1(&ref_record)) + oidread(&oid, reftable_ref_record_val1(&ref_record)); + prepare_fn(refname, &oid, policy_cb_data); + + while (1) { + struct reftable_log_record log = {0}; + struct object_id old_oid, new_oid; + + ret = reftable_iterator_next_log(&it, &log); + if (ret < 0) + goto done; + if (ret > 0 || strcmp(log.refname, refname)) { + reftable_log_record_release(&log); + break; + } + + oidread(&old_oid, log.value.update.old_hash); + oidread(&new_oid, log.value.update.new_hash); + + /* + * Skip over the reflog existence marker. We will add it back + * in when there are no live reflog records. + */ + if (is_null_oid(&old_oid) && is_null_oid(&new_oid)) { + reftable_log_record_release(&log); + continue; + } + + ALLOC_GROW(logs, logs_nr + 1, logs_alloc); + logs[logs_nr++] = log; + } + + /* + * We need to rewrite all reflog entries according to the pruning + * callback function: + * + * - If a reflog entry shall be pruned we mark the record for + * deletion. + * + * - Otherwise we may have to rewrite the chain of reflog entries so + * that gaps created by just-deleted records get backfilled. + */ + CALLOC_ARRAY(rewritten, logs_nr); + for (i = logs_nr; i--;) { + struct reftable_log_record *dest = &rewritten[i]; + struct object_id old_oid, new_oid; + + *dest = logs[i]; + oidread(&old_oid, logs[i].value.update.old_hash); + oidread(&new_oid, logs[i].value.update.new_hash); + + if (should_prune_fn(&old_oid, &new_oid, logs[i].value.update.email, + (timestamp_t)logs[i].value.update.time, + logs[i].value.update.tz_offset, + logs[i].value.update.message, + policy_cb_data)) { + dest->value_type = REFTABLE_LOG_DELETION; + } else { + if ((flags & EXPIRE_REFLOGS_REWRITE) && last_hash) + memcpy(dest->value.update.old_hash, last_hash, GIT_MAX_RAWSZ); + last_hash = logs[i].value.update.new_hash; + } + } + + if (flags & EXPIRE_REFLOGS_UPDATE_REF && last_hash && + reftable_ref_record_val1(&ref_record)) + oidread(&arg.update_oid, last_hash); + + arg.records = rewritten; + arg.len = logs_nr; + arg.stack = stack, + arg.refname = refname, + + ret = reftable_addition_add(add, &write_reflog_expiry_table, &arg); + if (ret < 0) + goto done; + + /* + * Future improvement: we could skip writing records that were + * not changed. + */ + if (!(flags & EXPIRE_REFLOGS_DRY_RUN)) + ret = reftable_addition_commit(add); + +done: + if (add) + cleanup_fn(policy_cb_data); + assert(ret != REFTABLE_API_ERROR); + + reftable_ref_record_release(&ref_record); + reftable_iterator_destroy(&it); + reftable_addition_destroy(add); + for (i = 0; i < logs_nr; i++) + reftable_log_record_release(&logs[i]); + free(logs); + free(rewritten); + return ret; +} + +struct ref_storage_be refs_be_reftable = { + .name = "reftable", + .init = reftable_be_init, + .init_db = reftable_be_init_db, + .transaction_prepare = reftable_be_transaction_prepare, + .transaction_finish = reftable_be_transaction_finish, + .transaction_abort = reftable_be_transaction_abort, + .initial_transaction_commit = reftable_be_initial_transaction_commit, + + .pack_refs = reftable_be_pack_refs, + .create_symref = reftable_be_create_symref, + .rename_ref = reftable_be_rename_ref, + .copy_ref = reftable_be_copy_ref, + + .iterator_begin = reftable_be_iterator_begin, + .read_raw_ref = reftable_be_read_raw_ref, + .read_symbolic_ref = reftable_be_read_symbolic_ref, + + .reflog_iterator_begin = reftable_be_reflog_iterator_begin, + .for_each_reflog_ent = reftable_be_for_each_reflog_ent, + .for_each_reflog_ent_reverse = reftable_be_for_each_reflog_ent_reverse, + .reflog_exists = reftable_be_reflog_exists, + .create_reflog = reftable_be_create_reflog, + .delete_reflog = reftable_be_delete_reflog, + .reflog_expire = reftable_be_reflog_expire, +}; |