From d19870d947eef17008ae0b4b7ebc9e9d0038a770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Mon, 16 Sep 2013 05:10:55 +0200 Subject: [PATCH] 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. --- include/git2/clone.h | 16 ++++++++ src/clone.c | 78 +++++++++++++++++++++++++++++++-------- tests-clar/online/clone.c | 39 ++++++++++++++++++++ 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;