diff options
author | Carlos Martín Nieto <cmn@dwim.me> | 2014-05-28 11:28:57 +0200 |
---|---|---|
committer | Carlos Martín Nieto <cmn@dwim.me> | 2014-05-28 15:40:47 +0200 |
commit | 2614819cf3e2163fb831c12c6d793254c78adb3d (patch) | |
tree | 365e649c4468d5d1db64e0937e68be86a37f5089 | |
parent | 94f742bac60656f4f915711b953814477633b984 (diff) | |
download | libgit2-2614819cf3e2163fb831c12c6d793254c78adb3d.tar.gz |
clone: allow for linking in local clone
If requested, git_clone_local_into() will try to link the object files
instead of copying them.
This only works on non-Windows (since it doesn't have this) when both
are on the same filesystem (which are unix semantics).
-rw-r--r-- | include/git2/clone.h | 4 | ||||
-rw-r--r-- | src/clone.c | 36 | ||||
-rw-r--r-- | tests/clone/local.c | 59 |
3 files changed, 94 insertions, 5 deletions
diff --git a/include/git2/clone.h b/include/git2/clone.h index 31bb52ccd..b2c944a78 100644 --- a/include/git2/clone.h +++ b/include/git2/clone.h @@ -144,6 +144,9 @@ GIT_EXTERN(int) git_clone_into( * @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 + * @param link wether to use hardlinks instead of copying + * objects. This is only possible if both repositories are on the same + * filesystem. * @param signature the identity used when updating the reflog * @return 0 on success, any non-zero return value from a callback * function, or a negative value to indicate an error (use @@ -154,6 +157,7 @@ GIT_EXTERN(int) git_clone_local_into( git_remote *remote, const git_checkout_options *co_opts, const char *branch, + int link, const git_signature *signature); /** @} */ diff --git a/src/clone.c b/src/clone.c index c02ca04b4..5aaa94ff6 100644 --- a/src/clone.c +++ b/src/clone.c @@ -405,9 +405,10 @@ int git_clone( if (!(error = create_and_configure_origin(&origin, repo, url, &options))) { if (git_clone__should_clone_local(url, options.local)) { + int link = options.local != GIT_CLONE_LOCAL_NO_LINKS; error = git_clone_local_into( repo, origin, &options.checkout_opts, - options.checkout_branch, options.signature); + options.checkout_branch, link, options.signature); } else { error = git_clone_into( repo, origin, &options.checkout_opts, @@ -448,15 +449,36 @@ static const char *repository_base(git_repository *repo) return git_repository_workdir(repo); } -int git_clone_local_into(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature) +static bool can_link(const char *src, const char *dst, int link) +{ +#ifdef GIT_WIN32 + return false; +#else + + struct stat st_src, st_dst; + + if (!link) + return false; + + if (p_stat(src, &st_src) < 0) + return false; + + if (p_stat(dst, &st_dst) < 0) + return false; + + return st_src.st_dev == st_dst.st_dev; +#endif +} + +int git_clone_local_into(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, int link, const git_signature *signature) { - int error, root; + int error, root, flags; git_repository *src; git_buf src_odb = GIT_BUF_INIT, dst_odb = GIT_BUF_INIT, src_path = GIT_BUF_INIT; git_buf reflog_message = GIT_BUF_INIT; const char *url; - assert(repo && remote && co_opts); + assert(repo && remote); if (!git_repository_is_empty(repo)) { giterr_set(GITERR_INVALID, "the repository is not empty"); @@ -495,8 +517,12 @@ int git_clone_local_into(git_repository *repo, git_remote *remote, const git_che goto cleanup; } + flags = 0; + if (can_link(git_repository_path(src), git_repository_path(repo), link)) + flags |= GIT_CPDIR_LINK_FILES; + if ((error = git_futils_cp_r(git_buf_cstr(&src_odb), git_buf_cstr(&dst_odb), - 0, GIT_OBJECT_DIR_MODE)) < 0) + flags, GIT_OBJECT_DIR_MODE)) < 0) goto cleanup; git_buf_printf(&reflog_message, "clone: from %s", git_remote_url(remote)); diff --git a/tests/clone/local.c b/tests/clone/local.c index 7b273b23a..289c50aaf 100644 --- a/tests/clone/local.c +++ b/tests/clone/local.c @@ -3,6 +3,8 @@ #include "git2/clone.h" #include "clone.h" #include "buffer.h" +#include "path.h" +#include "posix.h" void assert_clone(const char *path, git_clone_local_t opt, int val) { @@ -29,3 +31,60 @@ void test_clone_local__should_clone_local(void) cl_assert_equal_i(true, git_clone__should_clone_local(path, GIT_CLONE_LOCAL_NO_LINKS)); cl_assert_equal_i(false, git_clone__should_clone_local(path, GIT_CLONE_NO_LOCAL)); } + +void test_clone_local__hardlinks(void) +{ + git_repository *repo; + git_remote *remote; + git_signature *sig; + git_buf buf = GIT_BUF_INIT; + struct stat st; + + cl_git_pass(git_repository_init(&repo, "./clone.git", true)); + cl_git_pass(git_remote_create(&remote, repo, "origin", cl_fixture("testrepo.git"))); + cl_git_pass(git_signature_now(&sig, "foo", "bar")); + cl_git_pass(git_clone_local_into(repo, remote, NULL, NULL, true, sig)); + + git_remote_free(remote); + git_repository_free(repo); + + /* + * We can't rely on the link option taking effect in the first + * clone, since the temp dir and fixtures dir may reside on + * different filesystems. We perform the second clone + * side-by-side to make sure this is the case. + */ + + cl_git_pass(git_repository_init(&repo, "./clone2.git", true)); + cl_git_pass(git_buf_puts(&buf, cl_git_path_url("clone.git"))); + cl_git_pass(git_remote_create(&remote, repo, "origin", buf.ptr)); + cl_git_pass(git_clone_local_into(repo, remote, NULL, NULL, true, sig)); + +#ifndef GIT_WIN32 + git_buf_clear(&buf); + cl_git_pass(git_buf_join_n(&buf, '/', 4, git_repository_path(repo), "objects", "08", "b041783f40edfe12bb406c9c9a8a040177c125")); + + cl_git_pass(p_stat(buf.ptr, &st)); + cl_assert(st.st_nlink > 1); +#endif + + git_remote_free(remote); + git_repository_free(repo); + git_buf_clear(&buf); + + cl_git_pass(git_repository_init(&repo, "./clone3.git", true)); + cl_git_pass(git_buf_puts(&buf, cl_git_path_url("clone.git"))); + cl_git_pass(git_remote_create(&remote, repo, "origin", buf.ptr)); + cl_git_pass(git_clone_local_into(repo, remote, NULL, NULL, false, sig)); + + git_buf_clear(&buf); + cl_git_pass(git_buf_join_n(&buf, '/', 4, git_repository_path(repo), "objects", "08", "b041783f40edfe12bb406c9c9a8a040177c125")); + + cl_git_pass(p_stat(buf.ptr, &st)); + cl_assert_equal_i(1, st.st_nlink); + + git_buf_free(&buf); + git_signature_free(sig); + git_remote_free(remote); + git_repository_free(repo); +} |