diff --git a/include/git2.h b/include/git2.h index f260cfacd..cab517d99 100644 --- a/include/git2.h +++ b/include/git2.h @@ -37,6 +37,7 @@ #include "git2/index.h" #include "git2/config.h" #include "git2/remote.h" +#include "git2/clone.h" #include "git2/refspec.h" #include "git2/net.h" diff --git a/include/git2/clone.h b/include/git2/clone.h new file mode 100644 index 000000000..7936282fa --- /dev/null +++ b/include/git2/clone.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_git_clone_h__ +#define INCLUDE_git_clone_h__ + +#include "common.h" +#include "types.h" + + +/** + * @file git2/clone.h + * @brief Git cloning routines + * @defgroup git_clone Git cloning routines + * @ingroup Git + * @{ + */ +GIT_BEGIN_DECL + +/** + * TODO + * + * @param out pointer that will receive the resulting repository object + * @param origin_url repository to clone from + * @param dest_path local directory to clone to + * @return 0 on success, GIT_ERROR otherwise (use git_error_last for information about the error) + */ +GIT_EXTERN(int) git_clone(git_repository **out, const char *origin_url, const char *dest_path); + +/** + * TODO + * + * @param out pointer that will receive the resulting repository object + * @param origin_url repository to clone from + * @param dest_path local directory to clone to + * @return 0 on success, GIT_ERROR otherwise (use git_error_last for information about the error) + */ +GIT_EXTERN(int) git_clone_bare(git_repository **out, const char *origin_url, const char *dest_path); + +/** @} */ +GIT_END_DECL +#endif diff --git a/src/clone.c b/src/clone.c new file mode 100644 index 000000000..bdb5d9cd6 --- /dev/null +++ b/src/clone.c @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2009-2012 the libgit2 contributors + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include + +#include "git2/clone.h" +#include "git2/remote.h" + +#include "common.h" +#include "remote.h" +#include "fileops.h" +// TODO #include "checkout.h" + +GIT_BEGIN_DECL + +/* + * submodules? + * filemodes? + */ + +static int setup_remotes_and_fetch(git_repository *repo, const char *origin_url) +{ + int retcode = GIT_ERROR; + git_remote *origin = NULL; + git_off_t bytes = 0; + git_indexer_stats stats = {0}; + + if (!git_remote_new(&origin, repo, "origin", origin_url, NULL)) { + if (!git_remote_save(origin)) { + if (!git_remote_connect(origin, GIT_DIR_FETCH)) { + if (!git_remote_download(origin, &bytes, &stats)) { + if (!git_remote_update_tips(origin, NULL)) { + // TODO + // if (!git_checkout(...)) { + retcode = 0; + // } + } + } + git_remote_disconnect(origin); + } + } + git_remote_free(origin); + } + + return retcode; +} + +int git_clone(git_repository **out, const char *origin_url, const char *dest_path) +{ + int retcode = GIT_ERROR; + git_repository *repo = NULL; + char fullpath[512] = {0}; + + p_realpath(dest_path, fullpath); + if (git_path_exists(fullpath)) { + giterr_set(GITERR_INVALID, "Destination already exists: %s", fullpath); + return GIT_ERROR; + } + + /* Initialize the dest/.git directory */ + if (!(retcode = git_repository_init(&repo, fullpath, 0))) { + if ((retcode = setup_remotes_and_fetch(repo, origin_url)) < 0) { + /* Failed to fetch; clean up */ + git_repository_free(repo); + git_futils_rmdir_r(fullpath, GIT_DIRREMOVAL_FILES_AND_DIRS); + } else { + /* Fetched successfully, do a checkout */ + /* if (!(retcode = git_checkout(...))) {} */ + *out = repo; + retcode = 0; + } + } + + return retcode; +} + + +int git_clone_bare(git_repository **out, const char *origin_url, const char *dest_path) +{ + int retcode = GIT_ERROR; + git_repository *repo = NULL; + char fullpath[512] = {0}; + + p_realpath(dest_path, fullpath); + if (git_path_exists(fullpath)) { + giterr_set(GITERR_INVALID, "Destination already exists: %s", fullpath); + return GIT_ERROR; + } + + if (!(retcode = git_repository_init(&repo, fullpath, 1))) { + if ((retcode = setup_remotes_and_fetch(repo, origin_url)) < 0) { + /* Failed to fetch; clean up */ + git_repository_free(repo); + git_futils_rmdir_r(fullpath, GIT_DIRREMOVAL_FILES_AND_DIRS); + } else { + /* Fetched successfully, do a checkout */ + /* if (!(retcode = git_checkout(...))) {} */ + *out = repo; + retcode = 0; + } + } + + return retcode; +} + + + +GIT_END_DECL diff --git a/tests-clar/clone/clone.c b/tests-clar/clone/clone.c new file mode 100644 index 000000000..f60ffb5a1 --- /dev/null +++ b/tests-clar/clone/clone.c @@ -0,0 +1,101 @@ +#include "clar_libgit2.h" + +#include "git2/clone.h" +#include "repository.h" + +static git_repository *g_repo; + +void test_clone_clone__initialize(void) +{ + g_repo = NULL; +} + +void test_clone_clone__cleanup(void) +{ + if (g_repo) { + git_repository_free(g_repo); + g_repo = NULL; + } +} + +// TODO: This is copy/pasted from network/remotelocal.c. +static void build_local_file_url(git_buf *out, const char *fixture) +{ + const char *in_buf; + + git_buf path_buf = GIT_BUF_INIT; + + cl_git_pass(git_path_prettify_dir(&path_buf, fixture, NULL)); + cl_git_pass(git_buf_puts(out, "file://")); + +#ifdef _MSC_VER + /* + * A FILE uri matches the following format: file://[host]/path + * where "host" can be empty and "path" is an absolute path to the resource. + * + * In this test, no hostname is used, but we have to ensure the leading triple slashes: + * + * *nix: file:///usr/home/... + * Windows: file:///C:/Users/... + */ + cl_git_pass(git_buf_putc(out, '/')); +#endif + + in_buf = git_buf_cstr(&path_buf); + + /* + * A very hacky Url encoding that only takes care of escaping the spaces + */ + while (*in_buf) { + if (*in_buf == ' ') + cl_git_pass(git_buf_puts(out, "%20")); + else + cl_git_pass(git_buf_putc(out, *in_buf)); + + in_buf++; + } + + git_buf_free(&path_buf); +} + + +void test_clone_clone__bad_url(void) +{ + /* Clone should clean up the mess if the URL isn't a git repository */ + cl_git_fail(git_clone(&g_repo, "not_a_repo", "./foo")); + cl_assert(!git_path_exists("./foo")); + cl_git_fail(git_clone_bare(&g_repo, "not_a_repo", "./foo.git")); + cl_assert(!git_path_exists("./foo")); +} + + +void test_clone_clone__local(void) +{ + git_buf src = GIT_BUF_INIT; + build_local_file_url(&src, cl_fixture("testrepo.git")); + + cl_git_pass(git_clone(&g_repo, git_buf_cstr(&src), "./local")); + git_repository_free(g_repo); + git_futils_rmdir_r("./local", GIT_DIRREMOVAL_FILES_AND_DIRS); + cl_git_pass(git_clone_bare(&g_repo, git_buf_cstr(&src), "./local.git")); + git_futils_rmdir_r("./local.git", GIT_DIRREMOVAL_FILES_AND_DIRS); +} + + +void test_clone_clone__network(void) +{ + cl_git_pass(git_clone(&g_repo, + "https://github.com/libgit2/libgit2.git", + "./libgit2.git")); + git_futils_rmdir_r("./libgit2.git", GIT_DIRREMOVAL_FILES_AND_DIRS); +} + + +void test_clone_clone__already_exists(void) +{ + mkdir("./foo", GIT_DIR_MODE); + cl_git_fail(git_clone(&g_repo, + "https://github.com/libgit2/libgit2.git", + "./foo")); + git_futils_rmdir_r("./foo", GIT_DIRREMOVAL_FILES_AND_DIRS); +}