Commit d19870d947eef17008ae0b4b7ebc9e9d0038a770

Carlos Martín Nieto 2013-09-16T05:10:55

clone: implement git_clone_into This allows you to set up the repository and remote as you which to have them before performing the clone operation.

diff --git a/include/git2/clone.h b/include/git2/clone.h
index 38c759f..c3936f6 100644
--- a/include/git2/clone.h
+++ b/include/git2/clone.h
@@ -99,6 +99,22 @@ GIT_EXTERN(int) git_clone(
 		const char *local_path,
 		const git_clone_options *options);
 
+/**
+ * Clone into a repository
+ *
+ * After creating the repository and remote and configuring them for
+ * paths and callbacks respectively, you can call this function to
+ * perform the clone operation and optionally checkout files.
+ *
+ * @param repo the repository to use
+ * @param remote the remote repository to clone from
+ * @param co_opts options to use during checkout
+ * @param branch the branch to checkout after the clone, pass NULL for the remote's
+ * default branch
+ * @return 0 on success or an error code
+ */
+GIT_EXTERN(int) git_clone_into(git_repository *repo, git_remote *remote, git_checkout_opts *co_opts, const char *branch);
+
 /** @} */
 GIT_END_DECL
 #endif
diff --git a/src/clone.c b/src/clone.c
index 8385fb2..436fdff 100644
--- a/src/clone.c
+++ b/src/clone.c
@@ -270,23 +270,23 @@ cleanup:
 
 static int update_head_to_branch(
 		git_repository *repo,
-		const git_clone_options *options)
+		const char *remote_name,
+		const char *branch)
 {
 	int retcode;
 	git_buf remote_branch_name = GIT_BUF_INIT;
 	git_reference* remote_ref = NULL;
 
-	assert(options->checkout_branch);
+	assert(remote_name && branch);
 
 	if ((retcode = git_buf_printf(&remote_branch_name, GIT_REFS_REMOTES_DIR "%s/%s",
-		options->remote_name, options->checkout_branch)) < 0 )
+		remote_name, branch)) < 0 )
 		goto cleanup;
 
 	if ((retcode = git_reference_lookup(&remote_ref, repo, git_buf_cstr(&remote_branch_name))) < 0)
 		goto cleanup;
 
-	retcode = update_head_to_new_branch(repo, git_reference_target(remote_ref),
-		options->checkout_branch);
+	retcode = update_head_to_new_branch(repo, git_reference_target(remote_ref), branch);
 
 cleanup:
 	git_reference_free(remote_ref);
@@ -350,6 +350,23 @@ on_error:
 	return error;
 }
 
+static int do_fetch(git_remote *origin)
+{
+	int retcode;
+
+	/* Connect and download everything */
+	if ((retcode = git_remote_connect(origin, GIT_DIRECTION_FETCH)) < 0)
+		return retcode;
+
+	if ((retcode = git_remote_download(origin)) < 0)
+		return retcode;
+
+	/* Create "origin/foo" branches for all remote branches */
+	if ((retcode = git_remote_update_tips(origin)) < 0)
+		return retcode;
+
+	return 0;
+}
 
 static int setup_remotes_and_fetch(
 		git_repository *repo,
@@ -374,20 +391,12 @@ static int setup_remotes_and_fetch(
 		((retcode = git_remote_add_fetch(origin, "refs/tags/*:refs/tags/*")) < 0))
 		goto on_error;
 
-	/* Connect and download everything */
-	if ((retcode = git_remote_connect(origin, GIT_DIRECTION_FETCH)) < 0)
-		goto on_error;
-
-	if ((retcode = git_remote_download(origin)) < 0)
-		goto on_error;
-
-	/* Create "origin/foo" branches for all remote branches */
-	if ((retcode = git_remote_update_tips(origin)) < 0)
+	if ((retcode = do_fetch(origin)) < 0)
 		goto on_error;
 
 	/* Point HEAD to the requested branch */
 	if (options->checkout_branch)
-		retcode = update_head_to_branch(repo, options);
+		retcode = update_head_to_branch(repo, options->remote_name, options->checkout_branch);
 	/* Point HEAD to the same ref as the remote's head */
 	else
 		retcode = update_head_to_remote(repo, origin);
@@ -432,6 +441,45 @@ static void normalize_options(git_clone_options *dst, const git_clone_options *s
 	}
 }
 
+int git_clone_into(git_repository *repo, git_remote *remote, git_checkout_opts *co_opts, const char *branch)
+{
+	int error = 0, old_fetchhead;
+	size_t nspecs;
+
+	assert(repo && remote);
+
+	if (!git_repository_is_empty(repo)) {
+		giterr_set(GITERR_INVALID, "the repository is not empty");
+		return -1;
+	}
+
+	if ((error = git_remote_add_fetch(remote, "refs/tags/*:refs/tags/*")) < 0)
+		return error;
+
+	old_fetchhead = git_remote_update_fetchhead(remote);
+	git_remote_set_update_fetchhead(remote, 0);
+
+	if ((error = do_fetch(remote)) < 0)
+		goto cleanup;
+
+	if (branch)
+		error = update_head_to_branch(repo, git_remote_name(remote), branch);
+	/* Point HEAD to the same ref as the remote's head */
+	else
+		error = update_head_to_remote(repo, remote);
+
+	if (!error && should_checkout(repo, git_repository_is_bare(repo), co_opts))
+		error = git_checkout_head(repo, co_opts);
+
+cleanup:
+	git_remote_set_update_fetchhead(remote, old_fetchhead);
+	/* Remove the tags refspec */
+	nspecs = git_remote_refspec_count(remote);
+	git_remote_remove_refspec(remote, nspecs);
+
+	return error;
+}
+
 int git_clone(
 	git_repository **out,
 	const char *url,
diff --git a/tests-clar/online/clone.c b/tests-clar/online/clone.c
index b82cbcd..9a64ba1 100644
--- a/tests-clar/online/clone.c
+++ b/tests-clar/online/clone.c
@@ -126,6 +126,45 @@ void test_online_clone__can_checkout_a_cloned_repo(void)
 	git_buf_free(&path);
 }
 
+void test_online_clone__clone_into(void)
+{
+	git_buf path = GIT_BUF_INIT;
+	git_remote *remote;
+	git_reference *head;
+	git_checkout_opts checkout_opts = GIT_CHECKOUT_OPTS_INIT;
+	git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
+
+	bool checkout_progress_cb_was_called = false,
+		  fetch_progress_cb_was_called = false;
+
+	checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+	checkout_opts.progress_cb = &checkout_progress;
+	checkout_opts.progress_payload = &checkout_progress_cb_was_called;
+
+	cl_git_pass(git_repository_init(&g_repo, "./foo", false));
+	cl_git_pass(git_remote_create(&remote, g_repo, "origin", LIVE_REPO_URL));
+
+	callbacks.transfer_progress = &fetch_progress;
+	callbacks.payload = &fetch_progress_cb_was_called;
+	git_remote_set_callbacks(remote, &callbacks);
+
+	cl_git_pass(git_clone_into(g_repo, remote, &checkout_opts, NULL));
+
+	cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "master.txt"));
+	cl_assert_equal_i(true, git_path_isfile(git_buf_cstr(&path)));
+
+	cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD"));
+	cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head));
+	cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head));
+
+	cl_assert_equal_i(true, checkout_progress_cb_was_called);
+	cl_assert_equal_i(true, fetch_progress_cb_was_called);
+
+	git_remote_free(remote);
+	git_reference_free(head);
+	git_buf_free(&path);
+}
+
 static int update_tips(const char *refname, const git_oid *a, const git_oid *b, void *payload)
 {
 	int *callcount = (int*)payload;