--- Makefile | 1 + refs/namespaced-backend.c | 619 ++++++++++++++++++++++++++++++++++++++++++++++ refs/refs-internal.h | 1 + 3 files changed, 621 insertions(+) create mode 100644 refs/namespaced-backend.c diff --git a/Makefile b/Makefile index 461c845..0c417c3 100644 --- a/Makefile +++ b/Makefile @@ -842,6 +842,7 @@ LIB_OBJS += reflog-walk.o LIB_OBJS += refs.o LIB_OBJS += refs/files-backend.o LIB_OBJS += refs/iterator.o +LIB_OBJS += refs/namespaced-backend.o LIB_OBJS += refs/ref-cache.o LIB_OBJS += ref-filter.o LIB_OBJS += remote.o diff --git a/refs/namespaced-backend.c b/refs/namespaced-backend.c new file mode 100644 index 0000000..bcea2ca --- /dev/null +++ b/refs/namespaced-backend.c @@ -0,0 +1,619 @@ + +#include "../cache.h" +#include "../config.h" +#include "../refs.h" +#include "refs-internal.h" +#include "../repository.h" +#include "../iterator.h" + +/* Namespace backend intended to stack on top of existing ref store */ + +extern struct ref_storage_be refs_be_namespaced; + +struct namespaced_ref_store { + struct ref_store base; + struct ref_store *lower; + char *prefix; +}; + +static struct namespaced_ref_store *namespaced_downcast( + struct ref_store *ref_store, const char *caller) +{ + struct namespaced_ref_store *refs; + + if (ref_store->be != &refs_be_namespaced) + die("BUG: ref_store is type \"%s\" not \"namespaced\" in %s", + ref_store->be->name, caller); + + refs = (struct namespaced_ref_store *)ref_store; + + return refs; +} + +int namespaced_ref_store_create(const char *gitdir, + struct ref_store **lower) +{ + struct namespaced_ref_store *refs = NULL; + struct ref_store *ref_store; + char *config = NULL, *prefix = NULL; + int ret; + struct config_options opts; + struct config_set cs; + struct strbuf sb = STRBUF_INIT; + + ret = get_common_dir(&sb, gitdir); + if (ret < 0) { + goto cleanup; + } + + opts.respect_includes = 1; + opts.commondir = sb.buf; + opts.git_dir = gitdir; + memset(&cs, 0, sizeof(cs)); + git_configset_init(&cs); + + ret = git_configset_add_standard(&cs, &opts); + if (ret < 0) { + goto cleanup; + } + + ret = git_configset_get_string(&cs, "core.namespace", &config); + if (ret != 0) { + goto cleanup; + } + + prefix = expand_namespace(config); + assert(prefix); + + refs = xcalloc(1, sizeof(*refs)); + ref_store = &refs->base; + refs->prefix = prefix; + prefix = NULL; + + base_ref_store_init(ref_store, &refs_be_namespaced); + + refs->lower = *lower; + *lower = ref_store; + refs = NULL; + +cleanup: + free(refs); + git_configset_clear(&cs); + free(prefix); + free(config); + strbuf_release(&sb); + + return ret; +} + +static void prepend_prefix(struct ref_transaction *transaction, + const char *prefix) +{ + struct strbuf sb = STRBUF_INIT; + size_t prefixlen; + int i; + + strbuf_addstr(&sb, prefix); + prefixlen = sb.len; + + for (i = 0; i < transaction->nr; i++) { + struct ref_update *oldupdate, *newupdate; + + oldupdate = transaction->updates[i]; + + if (ref_type(oldupdate->refname) == REF_TYPE_PSEUDOREF) + continue; + + strbuf_addstr(&sb, oldupdate->refname); + FLEX_ALLOC_STR(newupdate, refname, sb.buf); + memcpy(newupdate, oldupdate, + ((char*)&newupdate->refname) - (char*)newupdate); + transaction->updates[i] = newupdate; + free(oldupdate); + strbuf_setlen(&sb, prefixlen); + } + strbuf_release(&sb); +} + +static char *add_namespace( + struct strbuf *sb, struct namespaced_ref_store *refs, + const char *refname) +{ + if (ref_type(refname) != REF_TYPE_PSEUDOREF) + strbuf_addstr(sb, refs->prefix); + if (refname) + strbuf_addstr(sb, refname); + return sb->buf; +} + +static int namespaced_init_db(struct ref_store *ref_store, struct strbuf *err) +{ + struct namespaced_ref_store *refs = namespaced_downcast( + ref_store, "init_db"); + int ret; + + ret = refs->lower->be->init_db(refs->lower, err); + if (ret != 0) + return ret; + + /* TODO: Needs to add an un-namespaced HEAD symlink, + is_git_directory assumes it's not a repo without it. + Can't change is_git_directory to resolve ref via backend + since it would need to make the backend and can't free it. */ + ret = refs->lower->be->create_symref( + refs->lower, "HEAD", "refs/heads/master", NULL); + if (ret != 0) + return ret; + + ret = refs_create_symref(ref_store, "HEAD", "refs/heads/master", NULL); + + return ret; +} + +static int namespaced_transaction_prepare(struct ref_store *ref_store, + struct ref_transaction *transaction, + struct strbuf *err) +{ + struct namespaced_ref_store *refs = namespaced_downcast( + ref_store, "transaction_prepare"); + int ret; + + prepend_prefix(transaction, refs->prefix); + + ret = refs->lower->be->transaction_prepare(refs->lower, transaction, + err); + + /* TODO: Fix missing ref update for namespaced HEAD */ + + return ret; +} +static int namespaced_transaction_finish(struct ref_store *ref_store, + struct ref_transaction *transaction, + struct strbuf *err) +{ + struct namespaced_ref_store *refs = namespaced_downcast( + ref_store, "transaction_finish"); + return refs->lower->be->transaction_finish(refs->lower, transaction, + err); +} +static int namespaced_transaction_abort(struct ref_store *ref_store, + struct ref_transaction *transaction, + struct strbuf *err) +{ + struct namespaced_ref_store *refs = namespaced_downcast( + ref_store, "transaction_abort"); + return refs->lower->be->transaction_abort(refs->lower, transaction, + err); +} +static int namespaced_initial_transaction_commit( + struct ref_store *ref_store, + struct ref_transaction *transaction, struct strbuf *err) +{ + struct namespaced_ref_store *refs = namespaced_downcast( + ref_store, "initial_transaction_commit"); + int ret; + + prepend_prefix(transaction, refs->prefix); + + ret = refs->lower->be->initial_transaction_commit( + refs->lower, transaction, err); + + /* TODO: Fix missing ref update for namespaced HEAD */ + + return ret; +} +static int namespaced_pack_refs(struct ref_store *ref_store, unsigned int flags) +{ + struct namespaced_ref_store *refs = namespaced_downcast( + ref_store, "pack_refs"); + return refs->lower->be->pack_refs(refs->lower, flags); +} +static int namespaced_peel_ref(struct ref_store *ref_store, const char *refname, + unsigned char *sha1) +{ + struct namespaced_ref_store *refs = namespaced_downcast( + ref_store, "peel_ref"); + struct strbuf sb = STRBUF_INIT; + int ret; + + ret = refs->lower->be->peel_ref( + refs->lower, add_namespace(&sb, refs, refname), sha1); + + strbuf_release(&sb); + + return ret; +} +static int namespaced_create_symref(struct ref_store *ref_store, + const char *refname, + const char *target, + const char *logmsg) +{ + struct namespaced_ref_store *refs = namespaced_downcast( + ref_store, "create_symref"); + struct strbuf rsb = STRBUF_INIT, tsb = STRBUF_INIT; + int ret; + + ret = refs->lower->be->create_symref( + refs->lower, add_namespace(&rsb, refs, refname), + add_namespace(&tsb, refs, target), logmsg); + + strbuf_release(&rsb); + strbuf_release(&tsb); + + return ret; +} +static int namespaced_delete_refs(struct ref_store *ref_store, const char *msg, + struct string_list *refnames, + unsigned int flags) +{ + struct namespaced_ref_store *refs = namespaced_downcast( + ref_store, "delete_refs"); + struct string_list prefixed = STRING_LIST_INIT_DUP; + struct string_list_item *it; + int ret; + + for_each_string_list_item(it, refnames) { + struct strbuf sb = STRBUF_INIT; + + /* TODO: Pseudorefs aren't namespaced, + so add_namespace may do nothing but strdup */ + add_namespace(&sb, refs, it->string); + string_list_append_nodup(&prefixed, strbuf_detach(&sb, NULL)); + + strbuf_release(&sb); + } + + ret = refs->lower->be->delete_refs(refs->lower, msg, &prefixed, flags); + + string_list_clear(&prefixed, 1); + + return ret; +} +static int namespaced_rename_ref(struct ref_store *ref_store, + const char *oldref, const char *newref, + const char *logmsg) +{ + struct namespaced_ref_store *refs = namespaced_downcast( + ref_store, "rename_ref"); + struct strbuf osb = STRBUF_INIT, nsb = STRBUF_INIT; + int ret; + + ret = refs->lower->be->rename_ref( + refs->lower, add_namespace(&osb, refs, oldref), + add_namespace(&nsb, refs, newref), logmsg); + + strbuf_release(&osb); + strbuf_release(&nsb); + + return ret; +} + +extern struct ref_iterator_vtable namespaced_ref_iterator_vtable; + +struct namespaced_ref_iterator { + struct ref_iterator base; + struct ref_iterator *lower; + const char *prefix; +}; + +static struct namespaced_ref_iterator *nsiter_downcast( + struct ref_iterator *ref_iterator, const char *caller) +{ + struct namespaced_ref_iterator *iter; + + if (ref_iterator->vtable != &namespaced_ref_iterator_vtable) + die("BUG: ref_iterator is not \"namespaced\" in %s", + caller); + + iter = (struct namespaced_ref_iterator *)ref_iterator; + + return iter; +} +static int namespaced_ref_iterator_advance(struct ref_iterator *ref_iterator) +{ + struct namespaced_ref_iterator *iter = nsiter_downcast(ref_iterator, + "advance"); + int ret; + + while ((ret = iter->lower->vtable->advance(iter->lower)) == ITER_OK) { + /* Pseudorefs are not namespaced */ + if (ref_type(iter->lower->refname) == REF_TYPE_PSEUDOREF) { + iter->base.oid = iter->lower->oid; + iter->base.flags = iter->lower->flags; + iter->base.refname = iter->lower->refname; + return ITER_OK; + } + + /* Standard ref iterator is pre-filtered, + but the same function is used for reflogs + which has no pre-filtering. */ + if (!starts_with(iter->lower->refname, iter->prefix)) + continue; + + iter->base.oid = iter->lower->oid; + iter->base.flags = iter->lower->flags; + assert(skip_prefix(iter->lower->refname, iter->prefix, + &iter->base.refname)); + return ITER_OK; + } + + iter->lower = NULL; + if (ref_iterator_abort(ref_iterator) != ITER_DONE) + ret = ITER_ERROR; + + return ret; +} +static int namespaced_ref_iterator_peel(struct ref_iterator *ref_iterator, + struct object_id *peeled) +{ + struct namespaced_ref_iterator *iter = nsiter_downcast(ref_iterator, + "peel"); + return iter->lower->vtable->peel(iter->lower, peeled); +} +static int namespaced_ref_iterator_abort(struct ref_iterator *ref_iterator) +{ + struct namespaced_ref_iterator *iter = nsiter_downcast(ref_iterator, + "abort"); + int ret = ITER_DONE; + if (iter->lower) + ret = iter->lower->vtable->abort(iter->lower); + + base_ref_iterator_free(ref_iterator); + return ret; +} +struct ref_iterator_vtable namespaced_ref_iterator_vtable = { + namespaced_ref_iterator_advance, + namespaced_ref_iterator_peel, + namespaced_ref_iterator_abort, +}; + +static struct ref_iterator *make_namespaced_iterator( + struct ref_iterator *lower, + const char *prefix) +{ + struct ref_iterator *ref_iterator; + struct namespaced_ref_iterator *iter; + + iter = xcalloc(1, sizeof(*iter)); + ref_iterator = &iter->base; + base_ref_iterator_init(ref_iterator, &namespaced_ref_iterator_vtable); + + iter->lower = lower; + iter->prefix = prefix; + + return ref_iterator; +} +static struct ref_iterator *namespaced_iterator_begin( + struct ref_store *ref_store, const char *prefix, + unsigned int flags) +{ + struct namespaced_ref_store *refs = namespaced_downcast( + ref_store, "iterator_begin"); + struct ref_iterator *lower, *ret; + struct strbuf sb = STRBUF_INIT; + + /* TODO: Pseudorefs aren't namespaced, but appear in the namespace. + Pseudorefs can only appear if there's no prefix or it's "" + at which point stripping the namespace off and filtering works, + so if we've got a prefix we prepend the namespace, + and if we don't we filter post-hoc. + */ + if (prefix && *prefix) + prefix = add_namespace(&sb, refs, prefix); + + lower = refs->lower->be->iterator_begin( + refs->lower, prefix, flags); + + ret = make_namespaced_iterator(lower, refs->prefix); + + strbuf_release(&sb); + + return ret; +} +static int namespaced_read_raw_ref(struct ref_store *ref_store, + const char *refname, unsigned char *sha1, + struct strbuf *referent, unsigned int *type) +{ + struct namespaced_ref_store *refs = namespaced_downcast( + ref_store, "read_raw_ref"); + struct strbuf sb = STRBUF_INIT; + int ret; + + ret = refs->lower->be->read_raw_ref( + refs->lower, add_namespace(&sb, refs, refname), sha1, + referent, type); + + if (ret == 0 && (*type & REF_ISSYMREF) == REF_ISSYMREF) { + const char *stripped_ref; + if (skip_prefix(referent->buf, refs->prefix, &stripped_ref)) { + struct strbuf stripped = STRBUF_INIT; + strbuf_addstr(&stripped, stripped_ref); + strbuf_swap(referent, &stripped); + strbuf_release(&stripped); + } + } + + strbuf_release(&sb); + + return ret; +} +static struct ref_iterator *namespaced_reflog_iterator_begin( + struct ref_store *ref_store) +{ + struct namespaced_ref_store *refs = namespaced_downcast( + ref_store, "reflog_iterator_begin"); + struct ref_iterator *lower; + + lower = refs->lower->be->reflog_iterator_begin( + refs->lower); + + return make_namespaced_iterator(lower, refs->prefix); +} + +static int namespaced_for_each_reflog_ent(struct ref_store *ref_store, + const char *refname, + each_reflog_ent_fn fn, + void *cb_data) +{ + struct namespaced_ref_store *refs = namespaced_downcast( + ref_store, "for_each_reflog_ent"); + struct strbuf sb = STRBUF_INIT; + int ret; + + ret = refs->lower->be->for_each_reflog_ent( + refs->lower, add_namespace(&sb, refs, refname), + fn, cb_data); + + strbuf_release(&sb); + + return ret; +} +static int namespaced_for_each_reflog_ent_reverse(struct ref_store *ref_store, + const char *refname, + each_reflog_ent_fn fn, + void *cb_data) +{ + struct namespaced_ref_store *refs = namespaced_downcast( + ref_store, "for_each_reflog_ent_reverse"); + struct strbuf sb = STRBUF_INIT; + int ret; + + ret = refs->lower->be->for_each_reflog_ent_reverse( + refs->lower, add_namespace(&sb, refs, refname), + fn, cb_data); + + strbuf_release(&sb); + + return ret; +} +static int namespaced_reflog_exists(struct ref_store *ref_store, + const char *refname) +{ + struct namespaced_ref_store *refs = namespaced_downcast( + ref_store, "reflog_exists"); + struct strbuf sb = STRBUF_INIT; + int ret; + + ret = refs->lower->be->reflog_exists( + refs->lower, add_namespace(&sb, refs, refname)); + + strbuf_release(&sb); + + return ret; +} +static int namespaced_create_reflog(struct ref_store *ref_store, + const char *refname, int force_create, + struct strbuf *err) +{ + struct namespaced_ref_store *refs = namespaced_downcast( + ref_store, "create_reflog"); + struct strbuf sb = STRBUF_INIT; + int ret; + + ret = refs->lower->be->create_reflog( + refs->lower, add_namespace(&sb, refs, refname), + force_create, err); + + strbuf_release(&sb); + + return ret; +} +static int namespaced_delete_reflog(struct ref_store *ref_store, + const char *refname) +{ + struct namespaced_ref_store *refs = namespaced_downcast( + ref_store, "delete_reflog"); + struct strbuf sb = STRBUF_INIT; + int ret; + + ret = refs->lower->be->delete_reflog( + refs->lower, add_namespace(&sb, refs, refname)); + + strbuf_release(&sb); + + return ret; +} + +struct wrap_expiry_cb_data { + char *prefix; + reflog_expiry_prepare_fn *prepare_fn; + reflog_expiry_should_prune_fn *should_prune_fn; + reflog_expiry_cleanup_fn *cleanup_fn; + void *cb_data; +}; +void wrap_expiry_prepare(const char *refname, const struct object_id *oid, + void *cb_data) +{ + struct wrap_expiry_cb_data *cbd = cb_data; + skip_prefix(refname, cbd->prefix, &refname); + cbd->prepare_fn(refname, oid, cbd->cb_data); +} +int wrap_expiry_should_prune(struct object_id *ooid, struct object_id *noid, + const char *email, timestamp_t timestamp, int tz, + const char *message, void *cb_data) +{ + struct wrap_expiry_cb_data *cbd = cb_data; + return cbd->should_prune_fn(ooid, noid, email, timestamp, tz, + message, cbd->cb_data); +} +void wrap_expiry_cleanup(void *cb_data) +{ + struct wrap_expiry_cb_data *cbd = cb_data; + cbd->cleanup_fn(cbd->cb_data); +} +static int namespaced_reflog_expire( + struct ref_store *ref_store, const char *refname, + const unsigned char *sha1, 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 namespaced_ref_store *refs = namespaced_downcast( + ref_store, "reflog_expire"); + struct strbuf sb = STRBUF_INIT; + struct wrap_expiry_cb_data cbd = { + refs->prefix, + prepare_fn, + should_prune_fn, + cleanup_fn, + policy_cb_data, + }; + int ret; + + ret = refs->lower->be->reflog_expire( + refs->lower, add_namespace(&sb, refs, refname), sha1, + flags, wrap_expiry_prepare, wrap_expiry_should_prune, + wrap_expiry_cleanup, &cbd); + + strbuf_release(&sb); + + return ret; +} + +struct ref_storage_be refs_be_namespaced = { + NULL, + "namespaced", + NULL, /* Initialised by namespaced_ref_store_create with different API */ + namespaced_init_db, + namespaced_transaction_prepare, + namespaced_transaction_finish, + namespaced_transaction_abort, + namespaced_initial_transaction_commit, + namespaced_pack_refs, + namespaced_peel_ref, + namespaced_create_symref, + namespaced_delete_refs, + namespaced_rename_ref, + namespaced_iterator_begin, + namespaced_read_raw_ref, + namespaced_reflog_iterator_begin, + namespaced_for_each_reflog_ent, + namespaced_for_each_reflog_ent_reverse, + namespaced_reflog_exists, + namespaced_create_reflog, + namespaced_delete_reflog, + namespaced_reflog_expire, +}; + + diff --git a/refs/refs-internal.h b/refs/refs-internal.h index 192f9f8..8391eda 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -655,6 +655,7 @@ struct ref_storage_be { }; extern struct ref_storage_be refs_be_files; +int namespaced_ref_store_create(const char *gitdir, struct ref_store **lower); /* * A representation of the reference store for the main repository or -- 2.9.0