Web lists-archives.com

[WIP 2/2] submodule: read-only super-backed ref backend




Note that a few major parts are still missing:
 - special handling of the current branch of the superproject
 - writing (whether "refs/..." to the superproject as an index change or
   a commit, or non-"refs/..." directly to the subproject like usual)

Signed-off-by: Jonathan Tan <jonathantanmy@xxxxxxxxxx>
---
 Makefile                       |   1 +
 refs.c                         |  11 +-
 refs/refs-internal.h           |   1 +
 refs/sp-backend.c              | 261 +++++++++++++++++++++++++++++++++++++++++
 submodule.c                    |  43 +++++--
 submodule.h                    |   2 +
 t/t1406-submodule-ref-store.sh |  26 ++++
 7 files changed, 331 insertions(+), 14 deletions(-)
 create mode 100644 refs/sp-backend.c

diff --git a/Makefile b/Makefile
index e53750ca0..74120b5d7 100644
--- a/Makefile
+++ b/Makefile
@@ -858,6 +858,7 @@ LIB_OBJS += refs/files-backend.o
 LIB_OBJS += refs/iterator.o
 LIB_OBJS += refs/packed-backend.o
 LIB_OBJS += refs/ref-cache.o
+LIB_OBJS += refs/sp-backend.o
 LIB_OBJS += ref-filter.o
 LIB_OBJS += remote.o
 LIB_OBJS += replace_object.o
diff --git a/refs.c b/refs.c
index 339d4318e..1f7922733 100644
--- a/refs.c
+++ b/refs.c
@@ -1575,12 +1575,17 @@ static struct ref_store *lookup_ref_store_map(struct hashmap *map,
 static struct ref_store *ref_store_init(const char *gitdir,
 					unsigned int flags)
 {
-	const char *be_name = "files";
-	struct ref_storage_be *be = find_ref_storage_backend(be_name);
+	struct ref_storage_be *be;
 	struct ref_store *refs;
 
+	if (getenv("USE_SP")) {
+		be = &refs_be_sp;
+	} else {
+		be = &refs_be_files;
+	}
+
 	if (!be)
-		die("BUG: reference backend %s is unknown", be_name);
+		die("BUG: reference backend %s is unknown", "files");
 
 	refs = be->init(gitdir, flags);
 	return refs;
diff --git a/refs/refs-internal.h b/refs/refs-internal.h
index dd834314b..a8ec03d90 100644
--- a/refs/refs-internal.h
+++ b/refs/refs-internal.h
@@ -651,6 +651,7 @@ struct ref_storage_be {
 
 extern struct ref_storage_be refs_be_files;
 extern struct ref_storage_be refs_be_packed;
+extern struct ref_storage_be refs_be_sp;
 
 /*
  * A representation of the reference store for the main repository or
diff --git a/refs/sp-backend.c b/refs/sp-backend.c
new file mode 100644
index 000000000..31e8cec4b
--- /dev/null
+++ b/refs/sp-backend.c
@@ -0,0 +1,261 @@
+#include "../cache.h"
+#include "../config.h"
+#include "../refs.h"
+#include "refs-internal.h"
+#include "ref-cache.h"
+#include "packed-backend.h"
+#include "../iterator.h"
+#include "../dir-iterator.h"
+#include "../lockfile.h"
+#include "../object.h"
+#include "../dir.h"
+#include "../submodule.h"
+
+/*
+ * Future: need to be in "struct repository"
+ * when doing a full libification.
+ */
+struct sp_ref_store {
+	struct ref_store base;
+	unsigned int store_flags;
+
+	/*
+	 * Ref store of this repository (the submodule), used only for the
+	 * reflog.
+	 */
+	struct ref_store *files;
+
+	/*
+	 * Ref store of the superproject, for refs.
+	 */
+	struct ref_store *files_superproject;
+};
+
+/*
+ * Create a new submodule ref cache and add it to the internal
+ * set of caches.
+ */
+static struct ref_store *sp_init(const char *gitdir, unsigned int flags)
+{
+	struct sp_ref_store *refs = xcalloc(1, sizeof(*refs));
+	struct ref_store *ref_store = (struct ref_store *)refs;
+
+	base_ref_store_init(ref_store, &refs_be_sp);
+	refs->store_flags = flags;
+	refs->files = refs_be_files.init(gitdir, flags);
+
+	return ref_store;
+}
+
+/*
+ * Downcast ref_store to sp_ref_store. Die if ref_store is not a
+ * sp_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 sp_ref_store *sp_downcast(struct ref_store *ref_store,
+					      unsigned int required_flags,
+					      const char *caller)
+{
+	struct sp_ref_store *refs;
+
+	if (ref_store->be != &refs_be_sp)
+		die("BUG: ref_store is type \"%s\" not \"sp\" in %s",
+		    ref_store->be->name, caller);
+
+	refs = (struct sp_ref_store *)ref_store;
+
+	if ((refs->store_flags & required_flags) != required_flags)
+		die("BUG: operation %s requires abilities 0x%x, but only have 0x%x",
+		    caller, required_flags, refs->store_flags);
+
+	return refs;
+}
+
+static int sp_read_raw_ref(struct ref_store *ref_store,
+			      const char *refname, struct object_id *oid,
+			      struct strbuf *referent, unsigned int *type)
+{
+	struct sp_ref_store *refs;
+
+	refs = sp_downcast(ref_store, REF_STORE_READ, "read_raw_ref");
+
+	if (!starts_with(refname, "refs/")) {
+		return refs_read_raw_ref(refs->files, refname, oid, referent, type);
+	}
+
+	/* read from the superproject instead */
+	return get_superproject_gitlink_oid(refname, oid);
+}
+
+static struct ref_iterator *sp_ref_iterator_begin(
+		struct ref_store *ref_store,
+		const char *prefix, unsigned int flags)
+{
+	return empty_ref_iterator_begin();
+}
+
+static int sp_pack_refs(struct ref_store *ref_store, unsigned int flags)
+{
+	/* no op */
+	return 0;
+}
+
+static int sp_delete_refs(struct ref_store *ref_store, const char *msg,
+			     struct string_list *refnames, unsigned int flags)
+{
+	/* unsupported */
+	return -1;
+}
+
+static int sp_rename_ref(struct ref_store *ref_store,
+			    const char *oldrefname, const char *newrefname,
+			    const char *logmsg)
+{
+	/* unsupported */
+	return -1;
+}
+
+static int sp_copy_ref(struct ref_store *ref_store,
+			    const char *oldrefname, const char *newrefname,
+			    const char *logmsg)
+{
+	/* unsupported */
+	return -1;
+}
+
+static int sp_create_reflog(struct ref_store *ref_store,
+			       const char *refname, int force_create,
+			       struct strbuf *err)
+{
+	struct sp_ref_store *refs =
+		sp_downcast(ref_store, REF_STORE_WRITE, "create_reflog");
+	return refs_create_reflog(refs->files, refname, force_create, err);
+}
+
+static int sp_create_symref(struct ref_store *ref_store,
+			       const char *refname, const char *target,
+			       const char *logmsg)
+{
+	/* unsupported */
+	return -1;
+}
+
+static int sp_reflog_exists(struct ref_store *ref_store,
+			       const char *refname)
+{
+	struct sp_ref_store *refs =
+		sp_downcast(ref_store, REF_STORE_READ, "reflog_exists");
+	return refs_reflog_exists(refs->files, refname);
+}
+
+static int sp_delete_reflog(struct ref_store *ref_store,
+			       const char *refname)
+{
+	struct sp_ref_store *refs =
+		sp_downcast(ref_store, REF_STORE_WRITE, "delete_reflog");
+	return refs_delete_reflog(refs->files, refname);
+}
+
+static int sp_for_each_reflog_ent_reverse(struct ref_store *ref_store,
+					     const char *refname,
+					     each_reflog_ent_fn fn,
+					     void *cb_data)
+{
+	struct sp_ref_store *refs =
+		sp_downcast(ref_store, REF_STORE_READ,
+			       "for_each_reflog_ent_reverse");
+	return refs_for_each_reflog_ent_reverse(refs->files, refname, fn, cb_data);
+}
+
+static int sp_for_each_reflog_ent(struct ref_store *ref_store,
+				     const char *refname,
+				     each_reflog_ent_fn fn, void *cb_data)
+{
+	struct sp_ref_store *refs =
+		sp_downcast(ref_store, REF_STORE_READ,
+			       "for_each_reflog_ent");
+	return refs_for_each_reflog_ent(refs->files, refname, fn, cb_data);
+}
+
+static struct ref_iterator *sp_reflog_iterator_begin(struct ref_store *ref_store)
+{
+	struct sp_ref_store *refs =
+		sp_downcast(ref_store, REF_STORE_READ,
+			       "reflog_iterator_begin");
+	return refs->files->be->reflog_iterator_begin(refs->files);
+}
+
+static int sp_transaction_prepare(struct ref_store *ref_store,
+				     struct ref_transaction *transaction,
+				     struct strbuf *err)
+{
+	return -1;
+}
+
+static int sp_transaction_finish(struct ref_store *ref_store,
+				    struct ref_transaction *transaction,
+				    struct strbuf *err)
+{
+	return -1;
+}
+
+static int sp_transaction_abort(struct ref_store *ref_store,
+				   struct ref_transaction *transaction,
+				   struct strbuf *err)
+{
+	return -1;
+}
+
+static int sp_initial_transaction_commit(struct ref_store *ref_store,
+					    struct ref_transaction *transaction,
+					    struct strbuf *err)
+{
+	return -1;
+}
+
+static int sp_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)
+{
+	struct sp_ref_store *refs =
+		sp_downcast(ref_store, REF_STORE_WRITE, "reflog_expire");
+	return refs_reflog_expire(refs->files, refname, oid, flags, prepare_fn, should_prune_fn, cleanup_fn, policy_cb_data);
+}
+
+static int sp_init_db(struct ref_store *ref_store, struct strbuf *err)
+{
+	return 0;
+}
+
+struct ref_storage_be refs_be_sp = {
+	NULL,
+	"sp",
+	sp_init,
+	sp_init_db,
+	sp_transaction_prepare,
+	sp_transaction_finish,
+	sp_transaction_abort,
+	sp_initial_transaction_commit,
+
+	sp_pack_refs,
+	sp_create_symref,
+	sp_delete_refs,
+	sp_rename_ref,
+	sp_copy_ref,
+
+	sp_ref_iterator_begin,
+	sp_read_raw_ref,
+
+	sp_reflog_iterator_begin,
+	sp_for_each_reflog_ent,
+	sp_for_each_reflog_ent_reverse,
+	sp_reflog_exists,
+	sp_create_reflog,
+	sp_delete_reflog,
+	sp_reflog_expire
+};
diff --git a/submodule.c b/submodule.c
index ce511180e..1ffaeec82 100644
--- a/submodule.c
+++ b/submodule.c
@@ -471,6 +471,7 @@ static void prepare_submodule_repo_env_no_git_dir(struct argv_array *out)
 void prepare_submodule_repo_env(struct argv_array *out)
 {
 	prepare_submodule_repo_env_no_git_dir(out);
+	argv_array_pushf(out, "USE_SP");
 	argv_array_pushf(out, "%s=%s", GIT_DIR_ENVIRONMENT,
 			 DEFAULT_GIT_DIR_ENVIRONMENT);
 }
@@ -1986,7 +1987,7 @@ void absorb_git_dir_into_superproject(const char *prefix,
  * Return 0 if successful, 1 if not (for example, if the parent
  * directory is not a repo or an unrelated one).
  */
-int get_superproject_entry(const char **full_name)
+static int get_superproject_entry(const char *ref, const char **full_name, struct object_id *oid)
 {
 	static struct strbuf sb = STRBUF_INIT;
 
@@ -2016,9 +2017,11 @@ int get_superproject_entry(const char **full_name)
 	prepare_submodule_repo_env(&cp.env_array);
 	argv_array_pop(&cp.env_array);
 
-	argv_array_pushl(&cp.args, "--literal-pathspecs", "-C", "..",
-			"ls-files", "-z", "--stage", "--full-name", "--",
-			subpath, NULL);
+	argv_array_pushl(&cp.args, "--literal-pathspecs", "-C", "..", NULL);
+	if (ref)
+		argv_array_pushl(&cp.args, "ls-tree", "-z", "--full-name", "-r", ref, subpath, NULL);
+	else
+		argv_array_pushl(&cp.args, "ls-files", "-z", "--full-name", "--stage", "--", subpath, NULL);
 	strbuf_reset(&sb);
 
 	cp.no_stdin = 1;
@@ -2037,13 +2040,24 @@ int get_superproject_entry(const char **full_name)
 	if (starts_with(sb.buf, "160000")) {
 		/*
 		 * There is a superproject having this repo as a submodule.
-		 * The format is <mode> SP <hash> SP <stage> TAB <full name> \0,
-		 * We're only interested in the name after the tab.
+		 * The format is:
+		 * [ls-tree] <mode> SP <type> SP <hash> TAB <full name> \0
+		 * [ls-files] <mode> SP <hash> SP <stage> TAB <full name> \0
 		 */
-		char *tab = strchr(sb.buf, '\t');
-		if (!tab)
-			die("BUG: ls-files returned line with no tab");
-		*full_name = tab + 1;
+		if (full_name) {
+			char *tab = strchr(sb.buf, '\t');
+			if (!tab)
+				die("BUG: ls-files returned line with no tab");
+			*full_name = tab + 1;
+		}
+		if (oid) {
+			char *space = strchr(sb.buf, ' ');
+			const char *p;
+			if (ref)
+				space = strchr(space + 1, ' ');
+			if (parse_oid_hex(space + 1, oid, &p))
+				die("BUG: Could not read OID: %s", space + 1);
+		}
 		return 0;
 	}
 
@@ -2063,7 +2077,7 @@ const char *get_superproject_working_tree(void)
 	size_t len;
 	const char *ret;
 
-	if (get_superproject_entry(&full_name))
+	if (get_superproject_entry(NULL, &full_name, NULL))
 		return NULL;
 
 	super_wt = xstrdup(xgetcwd());
@@ -2075,6 +2089,13 @@ const char *get_superproject_working_tree(void)
 	return ret;
 }
 
+int get_superproject_gitlink_oid(const char *ref, struct object_id *oid)
+{
+	if (get_superproject_entry(ref, NULL, oid))
+		return 1;
+	return 0;
+}
+
 /*
  * Put the gitdir for a submodule (given relative to the main
  * repository worktree) into `buf`, or return -1 on error.
diff --git a/submodule.h b/submodule.h
index 29ab302cc..3a836beab 100644
--- a/submodule.h
+++ b/submodule.h
@@ -140,4 +140,6 @@ extern void absorb_git_dir_into_superproject(const char *prefix,
  */
 extern const char *get_superproject_working_tree(void);
 
+extern int get_superproject_gitlink_oid(const char *ref, struct object_id *oid);
+
 #endif
diff --git a/t/t1406-submodule-ref-store.sh b/t/t1406-submodule-ref-store.sh
index c32d4cc46..46ba7a272 100755
--- a/t/t1406-submodule-ref-store.sh
+++ b/t/t1406-submodule-ref-store.sh
@@ -98,4 +98,30 @@ test_expect_success 'create-reflog() not allowed' '
 	test_must_fail $RUN create-reflog HEAD 1
 '
 
+test_expect_success 'ref backend based on superproject data' '
+	rm -rf sub super &&
+
+	git init sub &&
+	test_commit -C sub first &&
+	test_commit -C sub second &&
+
+	git init super &&
+
+	# master branch in superproject, submodule at second
+	git -C super submodule add ../sub sub &&
+	git -C super commit -m x &&
+
+	# anotherbranch in superproject, submodule at first
+	git -C super checkout -b anotherbranch &&
+	git -C super/sub checkout first &&
+	git -C super commit -a -m x &&
+
+	# Notice that rev-parse can parse "anotherbranch" and see that it
+	# points to first, even though a branch with the name "anotherbranch"
+	# is only defined in the superproject
+	git -C sub rev-parse first >expect &&
+	USE_SP=1 git -C super/sub rev-parse anotherbranch >actual &&
+	test_cmp expect actual
+'
+
 test_done
-- 
2.15.0.531.g2ccb3012c9-goog