summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlos Martín Nieto <cmn@dwim.me>2013-09-16 05:10:55 +0200
committerCarlos Martín Nieto <cmn@dwim.me>2013-10-02 06:41:42 +0200
commitd19870d947eef17008ae0b4b7ebc9e9d0038a770 (patch)
tree48566589f6270d15d92507842e8f9eda32e19c03
parente3c131c544bc79573ebefab4931b5ca89836ace1 (diff)
downloadlibgit2-d19870d947eef17008ae0b4b7ebc9e9d0038a770.tar.gz
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.
-rw-r--r--include/git2/clone.h16
-rw-r--r--src/clone.c78
-rw-r--r--tests-clar/online/clone.c39
3 files changed, 118 insertions, 15 deletions
diff --git a/include/git2/clone.h b/include/git2/clone.h
index 38c759f6e..c3936f6b0 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 8385fb264..436fdff43 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 b82cbcd46..9a64ba166 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;