git@vger.kernel.org mailing list mirror (one of many)
 help / color / mirror / code / Atom feed
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


  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).