From: Richard Maw <richard.maw@gmail.com>
To: git@vger.kernel.org
Subject: [PATCH 6/7] Add namespaced ref backend
Date: Sun, 13 Aug 2017 20:36:10 +0100 [thread overview]
Message-ID: <20170813193611.4233-7-richard.maw@gmail.com> (raw)
In-Reply-To: <20170813193611.4233-1-richard.maw@gmail.com>
---
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
next prev parent reply other threads:[~2017-08-13 19:36 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2017-08-13 19:36 [RFC PATCH 0/7] Implement ref namespaces as a ref storage backend Richard Maw
2017-08-13 19:36 ` [PATCH 1/7] Expose expand_namespace API Richard Maw
2017-08-13 19:36 ` [PATCH 2/7] Add git_configset_add_standard Richard Maw
2017-08-13 19:36 ` [PATCH 3/7] Add helper for skipping namespace prefixes Richard Maw
2017-08-13 19:36 ` [PATCH 4/7] Autocreate reflogs for namespaced refs Richard Maw
2017-08-13 19:36 ` [PATCH 5/7] Treat namespaced HEAD and refs/bisect as per-worktree Richard Maw
2017-08-13 19:36 ` Richard Maw [this message]
2017-08-13 19:36 ` [PATCH 7/7] Plumb in namespaced ref store Richard Maw
2017-08-14 17:00 ` [RFC PATCH 0/7] Implement ref namespaces as a ref storage backend Stefan Beller
2017-08-15 17:13 ` Junio C Hamano
2017-08-24 10:20 ` Richard Maw
2017-08-24 16:17 ` Michael Haggerty
2017-08-30 13:17 ` Richard Maw
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: http://vger.kernel.org/majordomo-info.html
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20170813193611.4233-7-richard.maw@gmail.com \
--to=richard.maw@gmail.com \
--cc=git@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://80x24.org/mirrors/git.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).