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).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
diff --git a/include/git2/clone.h b/include/git2/clone.h
index 31bb52c..b2c944a 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 c02ca04..5aaa94f 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 7b273b2..289c50a 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);
+}