Commit d19870d9 by Carlos Martín Nieto

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.
parent e3c131c5
......@@ -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
......@@ -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,
......
......@@ -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;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment