Commit 3c5d78bd7ed1ece63409a6f1f7e6d5f216fcfc7b

Etienne Samson 2019-05-01T16:16:26

submodule: provide a wrapper for simple submodule clone steps

diff --git a/include/git2/submodule.h b/include/git2/submodule.h
index 62f250b..bedd76d 100644
--- a/include/git2/submodule.h
+++ b/include/git2/submodule.h
@@ -263,7 +263,8 @@ GIT_EXTERN(int) git_submodule_foreach(
  * from the working directory to the new repo.
  *
  * To fully emulate "git submodule add" call this function, then open the
- * submodule repo and perform the clone step as needed.  Lastly, call
+ * submodule repo and perform the clone step as needed (if you don't need
+ * anything custom see `git_submodule_add_clone()`). Lastly, call
  * `git_submodule_add_finalize()` to wrap up adding the new submodule and
  * .gitmodules to the index to be ready to commit.
  *
@@ -286,6 +287,22 @@ GIT_EXTERN(int) git_submodule_add_setup(
 	int use_gitlink);
 
 /**
+ * Perform the clone step for a newly created submodule.
+ *
+ * This performs the necessary `git_clone` to setup a newly-created submodule.
+ *
+ * @param out The newly created repository object. Optional.
+ * @param submodule The submodule currently waiting for its clone.
+ * @param opts The options to use.
+ *
+ * @return 0 on success, -1 on other errors (see git_clone).
+ */
+GIT_EXTERN(int) git_submodule_clone(
+	git_repository **out,
+	git_submodule *submodule,
+	const git_submodule_update_options *opts);
+
+/**
  * Resolve the setup of a new git submodule.
  *
  * This should be called on a submodule once you have called add setup
diff --git a/src/clone.c b/src/clone.c
index e0b43f1..e8972b4 100644
--- a/src/clone.c
+++ b/src/clone.c
@@ -382,11 +382,12 @@ done:
 	return is_local;
 }
 
-int git_clone(
+static int git__clone(
 	git_repository **out,
 	const char *url,
 	const char *local_path,
-	const git_clone_options *_options)
+	const git_clone_options *_options,
+	int use_existing)
 {
 	int error = 0;
 	git_repository *repo = NULL;
@@ -403,7 +404,7 @@ int git_clone(
 	GIT_ERROR_CHECK_VERSION(&options, GIT_CLONE_OPTIONS_VERSION, "git_clone_options");
 
 	/* Only clone to a new directory or an empty directory */
-	if (git_path_exists(local_path) && !git_path_is_empty_dir(local_path)) {
+	if (git_path_exists(local_path) && !use_existing && !git_path_is_empty_dir(local_path)) {
 		git_error_set(GIT_ERROR_INVALID,
 			"'%s' exists and is not an empty directory", local_path);
 		return GIT_EEXISTS;
@@ -455,6 +456,24 @@ int git_clone(
 	return error;
 }
 
+int git_clone(
+	git_repository **out,
+	const char *url,
+	const char *local_path,
+	const git_clone_options *_options)
+{
+	return git__clone(out, url, local_path, _options, 0);
+}
+
+int git_clone__submodule(
+	git_repository **out,
+	const char *url,
+	const char *local_path,
+	const git_clone_options *_options)
+{
+	return git__clone(out, url, local_path, _options, 1);
+}
+
 int git_clone_options_init(git_clone_options *opts, unsigned int version)
 {
 	GIT_INIT_STRUCTURE_FROM_TEMPLATE(
diff --git a/src/clone.h b/src/clone.h
index 864b590..7d73cab 100644
--- a/src/clone.h
+++ b/src/clone.h
@@ -11,6 +11,10 @@
 
 #include "git2/clone.h"
 
+extern int git_clone__submodule(git_repository **out,
+	const char *url, const char *local_path,
+	const git_clone_options *_options);
+
 extern int git_clone__should_clone_local(const char *url, git_clone_local_t local);
 
 #endif
diff --git a/src/submodule.c b/src/submodule.c
index cf89fc9..d12dbcf 100644
--- a/src/submodule.c
+++ b/src/submodule.c
@@ -23,6 +23,7 @@
 #include "path.h"
 #include "index.h"
 #include "worktree.h"
+#include "clone.h"
 
 #define GIT_MODULES_FILE ".gitmodules"
 
@@ -815,6 +816,64 @@ done:
 	return error;
 }
 
+static int clone_return_origin(git_remote **out, git_repository *repo, const char *name, const char *url, void *payload)
+{
+	GIT_UNUSED(url);
+	GIT_UNUSED(payload);
+	return git_remote_lookup(out, repo, name);
+}
+
+static int clone_return_repo(git_repository **out, const char *path, int bare, void *payload)
+{
+	git_submodule *sm = payload;
+
+	GIT_UNUSED(path);
+	GIT_UNUSED(bare);
+	return git_submodule_open(out, sm);
+}
+
+int git_submodule_clone(git_repository **out, git_submodule *submodule, const git_submodule_update_options *given_opts)
+{
+	int error;
+	git_repository *clone;
+	git_buf rel_path = GIT_BUF_INIT;
+	git_submodule_update_options sub_opts = GIT_SUBMODULE_UPDATE_OPTIONS_INIT;
+	git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
+
+	assert(submodule);
+
+	if (given_opts)
+		memcpy(&sub_opts, given_opts, sizeof(sub_opts));
+
+	GIT_ERROR_CHECK_VERSION(&sub_opts, GIT_SUBMODULE_UPDATE_OPTIONS_VERSION, "git_submodule_update_options");
+
+	memcpy(&opts.checkout_opts, &sub_opts.checkout_opts, sizeof(sub_opts.checkout_opts));
+	memcpy(&opts.fetch_opts, &sub_opts.fetch_opts, sizeof(sub_opts.fetch_opts));
+	opts.repository_cb = clone_return_repo;
+	opts.repository_cb_payload = submodule;
+	opts.remote_cb = clone_return_origin;
+	opts.remote_cb_payload = submodule;
+
+	git_buf_puts(&rel_path, git_repository_workdir(git_submodule_owner(submodule)));
+	git_buf_joinpath(&rel_path, git_buf_cstr(&rel_path), git_submodule_path(submodule));
+
+	GIT_ERROR_CHECK_ALLOC_BUF(&rel_path);
+
+	error = git_clone__submodule(&clone, git_submodule_url(submodule), git_buf_cstr(&rel_path), &opts);
+	if (error < 0)
+		goto cleanup;
+
+	if (!out)
+		git_repository_free(clone);
+	else
+		*out = clone;
+
+cleanup:
+	git_buf_dispose(&rel_path);
+
+	return error;
+}
+
 int git_submodule_add_finalize(git_submodule *sm)
 {
 	int error;
diff --git a/tests/clone/nonetwork.c b/tests/clone/nonetwork.c
index 2b8081f..7ca4908 100644
--- a/tests/clone/nonetwork.c
+++ b/tests/clone/nonetwork.c
@@ -1,7 +1,6 @@
 #include "clar_libgit2.h"
 
 #include "git2/clone.h"
-#include "git2/sys/commit.h"
 #include "../submodule/submodule_helpers.h"
 #include "remote.h"
 #include "futils.h"
@@ -352,56 +351,3 @@ void test_clone_nonetwork__clone_from_empty_sets_upstream(void)
 	git_repository_free(repo);
 	cl_fixture_cleanup("./repowithunborn");
 }
-
-static int just_return_origin(git_remote **out, git_repository *repo, const char *name, const char *url, void *payload)
-{
-	GIT_UNUSED(url); GIT_UNUSED(payload);
-
-	return git_remote_lookup(out, repo, name);
-}
-
-static int just_return_repo(git_repository **out, const char *path, int bare, void *payload)
-{
-	git_submodule *sm = payload;
-
-	GIT_UNUSED(path); GIT_UNUSED(bare);
-
-	return git_submodule_open(out, sm);
-}
-
-void test_clone_nonetwork__clone_submodule(void)
-{
-	git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT;
-	git_index *index;
-	git_oid tree_id, commit_id;
-	git_submodule *sm;
-	git_signature *sig;
-	git_repository *sm_repo;
-
-	cl_git_pass(git_repository_init(&g_repo, "willaddsubmodule", false));
-
-
-	/* Create the submodule structure, clone into it and finalize */
-	cl_git_pass(git_submodule_add_setup(&sm, g_repo, cl_fixture("testrepo.git"), "testrepo", true));
-
-	clone_opts.repository_cb = just_return_repo;
-	clone_opts.repository_cb_payload = sm;
-	clone_opts.remote_cb = just_return_origin;
-	clone_opts.remote_cb_payload = sm;
-	cl_git_pass(git_clone(&sm_repo, cl_fixture("testrepo.git"), "testrepo", &clone_opts));
-	cl_git_pass(git_submodule_add_finalize(sm));
-	git_repository_free(sm_repo);
-	git_submodule_free(sm);
-
-	cl_git_pass(git_repository_index(&index, g_repo));
-	cl_git_pass(git_index_write_tree(&tree_id, index));
-	git_index_free(index);
-
-	cl_git_pass(git_signature_now(&sig, "Submoduler", "submoduler@local"));
-	cl_git_pass(git_commit_create_from_ids(&commit_id, g_repo, "HEAD", sig, sig, NULL, "A submodule\n",
-					       &tree_id, 0, NULL));
-
-	git_signature_free(sig);
-
-	assert_submodule_exists(g_repo, "testrepo");
-}
diff --git a/tests/submodule/add.c b/tests/submodule/add.c
index b251b33..d3100da 100644
--- a/tests/submodule/add.c
+++ b/tests/submodule/add.c
@@ -5,6 +5,7 @@
 #include "config/config_helpers.h"
 #include "futils.h"
 #include "repository.h"
+#include "git2/sys/commit.h"
 
 static git_repository *g_repo = NULL;
 static const char *valid_blob_id = "fa49b077972391ad58037050f2a75f74e3671e92";
@@ -183,3 +184,83 @@ void test_submodule_add__file_exists_in_index(void)
 	git_submodule_free(sm);
 	git_buf_dispose(&name);
 }
+
+static int just_return_origin(git_remote **out, git_repository *repo, const char *name, const char *url, void *payload)
+{
+	GIT_UNUSED(url); GIT_UNUSED(payload);
+
+	return git_remote_lookup(out, repo, name);
+}
+
+static int just_return_repo(git_repository **out, const char *path, int bare, void *payload)
+{
+	git_submodule *sm = payload;
+
+	GIT_UNUSED(path); GIT_UNUSED(bare);
+
+	return git_submodule_open(out, sm);
+}
+
+void test_submodule_add__homemade_clone(void)
+{
+	git_clone_options clone_opts = GIT_CLONE_OPTIONS_INIT;
+	git_index *index;
+	git_oid tree_id, commit_id;
+	git_submodule *sm;
+	git_signature *sig;
+	git_repository *sm_repo;
+
+	cl_git_pass(git_repository_init(&g_repo, "willaddsubmodule", false));
+
+	/* Create the submodule structure, clone into it and finalize */
+	cl_git_pass(git_submodule_add_setup(&sm, g_repo, cl_fixture("testrepo.git"), "testrepo", true));
+
+	clone_opts.repository_cb = just_return_repo;
+	clone_opts.repository_cb_payload = sm;
+	clone_opts.remote_cb = just_return_origin;
+	clone_opts.remote_cb_payload = sm;
+	cl_git_pass(git_clone(&sm_repo, cl_fixture("testrepo.git"), "testrepo", &clone_opts));
+	cl_git_pass(git_submodule_add_finalize(sm));
+	git_repository_free(sm_repo);
+	git_submodule_free(sm);
+
+	cl_git_pass(git_repository_index(&index, g_repo));
+	cl_git_pass(git_index_write_tree(&tree_id, index));
+	git_index_free(index);
+
+	cl_git_pass(git_signature_now(&sig, "Submoduler", "submoduler@local"));
+	cl_git_pass(git_commit_create_from_ids(&commit_id, g_repo, "HEAD", sig, sig, NULL, "A submodule\n",
+										   &tree_id, 0, NULL));
+
+	git_signature_free(sig);
+
+	assert_submodule_exists(g_repo, "testrepo");
+}
+
+void test_submodule_add__submodule_clone(void)
+{
+	git_index *index;
+	git_oid tree_id, commit_id;
+	git_submodule *sm;
+	git_signature *sig;
+
+	cl_git_pass(git_repository_init(&g_repo, "willaddsubmodule-add", false));
+
+	/* Create the submodule structure, clone into it and finalize */
+	cl_git_pass(git_submodule_add_setup(&sm, g_repo, cl_fixture("testrepo.git"), "testrepo-add", true));
+	cl_git_pass(git_submodule_clone(NULL, sm, NULL));
+	cl_git_pass(git_submodule_add_finalize(sm));
+	git_submodule_free(sm);
+
+	cl_git_pass(git_repository_index(&index, g_repo));
+	cl_git_pass(git_index_write_tree(&tree_id, index));
+	git_index_free(index);
+
+	cl_git_pass(git_signature_now(&sig, "Submoduler", "submoduler@local"));
+	cl_git_pass(git_commit_create_from_ids(&commit_id, g_repo, "HEAD", sig, sig, NULL, "A submodule\n",
+										   &tree_id, 0, NULL));
+
+	git_signature_free(sig);
+
+	assert_submodule_exists(g_repo, "testrepo-add");
+}