summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlos Martín Nieto <cmn@dwim.me>2013-12-21 17:18:21 +0000
committerCarlos Martín Nieto <cmn@dwim.me>2014-05-28 15:40:20 +0200
commit4386d80be108102548d4ff52c875aedfa94e7412 (patch)
tree776b4eeb7aeffd59c87606031b2a2e78661cea13
parent433ba614a2ef948008510a1b1189702d515d2fc4 (diff)
downloadlibgit2-4386d80be108102548d4ff52c875aedfa94e7412.tar.gz
clone: perform a "local clone" when given a local path
When git is given such a path, it will perform a "local clone", bypassing the git-aware protocol and simply copying over all objects that exist in the source. Copy this behaviour when given a local path.
-rw-r--r--include/git2/clone.h25
-rw-r--r--src/clone.c114
2 files changed, 128 insertions, 11 deletions
diff --git a/include/git2/clone.h b/include/git2/clone.h
index 985c04bf6..ceb1a53bb 100644
--- a/include/git2/clone.h
+++ b/include/git2/clone.h
@@ -123,6 +123,31 @@ GIT_EXTERN(int) git_clone_into(
const char *branch,
const git_signature *signature);
+/**
+ * Perform a local clone into a repository
+ *
+ * A "local clone" bypasses any git-aware protocols and simply copies
+ * over the object database from the source repository. It is often
+ * faster than a git-aware clone, but no verification of the data is
+ * performed, and can copy over too much data.
+ *
+ * @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
+ * @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
+ * `giterr_last` for a detailed error message)
+ */
+GIT_EXTERN(int) git_clone_local_into(
+ git_repository *repo,
+ git_remote *remote,
+ const git_checkout_options *co_opts,
+ const char *branch,
+ const git_signature *signature);
+
/** @} */
GIT_END_DECL
#endif
diff --git a/src/clone.c b/src/clone.c
index 8381ec63c..b66ba6b4c 100644
--- a/src/clone.c
+++ b/src/clone.c
@@ -22,6 +22,7 @@
#include "refs.h"
#include "path.h"
#include "repository.h"
+#include "odb.h"
static int create_branch(
git_reference **branch,
@@ -280,6 +281,23 @@ static bool should_checkout(
return !git_repository_head_unborn(repo);
}
+static int checkout_branch(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature, const char *reflog_message)
+{
+ int error;
+
+ if (branch)
+ error = update_head_to_branch(repo, git_remote_name(remote), branch,
+ signature, reflog_message);
+ /* Point HEAD to the same ref as the remote's head */
+ else
+ error = update_head_to_remote(repo, remote, signature, reflog_message);
+
+ if (!error && should_checkout(repo, git_repository_is_bare(repo), co_opts))
+ error = git_checkout_head(repo, co_opts);
+
+ return error;
+}
+
int git_clone_into(git_repository *repo, git_remote *_remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature)
{
int error;
@@ -311,15 +329,7 @@ int git_clone_into(git_repository *repo, git_remote *_remote, const git_checkout
if ((error = git_remote_fetch(remote, signature, git_buf_cstr(&reflog_message))) != 0)
goto cleanup;
- if (branch)
- error = update_head_to_branch(repo, git_remote_name(remote), branch,
- signature, git_buf_cstr(&reflog_message));
- /* Point HEAD to the same ref as the remote's head */
- else
- error = update_head_to_remote(repo, remote, signature, git_buf_cstr(&reflog_message));
-
- if (!error && should_checkout(repo, git_repository_is_bare(repo), co_opts))
- error = git_checkout_head(repo, co_opts);
+ error = checkout_branch(repo, remote, co_opts, branch, signature, git_buf_cstr(&reflog_message));
cleanup:
git_remote_free(remote);
@@ -362,8 +372,15 @@ int git_clone(
return error;
if (!(error = create_and_configure_origin(&origin, repo, url, &options))) {
- error = git_clone_into(
- repo, origin, &options.checkout_opts, options.checkout_branch, options.signature);
+ if (git__prefixcmp(url, "file://")) {
+ error = git_clone_local_into(
+ repo, origin, &options.checkout_opts,
+ options.checkout_branch, options.signature);
+ } else {
+ error = git_clone_into(
+ repo, origin, &options.checkout_opts,
+ options.checkout_branch, options.signature);
+ }
git_remote_free(origin);
}
@@ -390,3 +407,78 @@ int git_clone_init_options(git_clone_options *opts, unsigned int version)
opts, version, git_clone_options, GIT_CLONE_OPTIONS_INIT);
return 0;
}
+
+static const char *repository_base(git_repository *repo)
+{
+ if (git_repository_is_bare(repo))
+ return git_repository_path(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)
+{
+ int error, root;
+ 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);
+
+ if (!git_repository_is_empty(repo)) {
+ giterr_set(GITERR_INVALID, "the repository is not empty");
+ return -1;
+ }
+
+ /*
+ * Let's figure out what path we should use for the source
+ * repo, if it's not rooted, the path should be relative to
+ * the repository's worktree/gitdir.
+ */
+ url = git_remote_url(remote);
+ if (!git__prefixcmp(url, "file://"))
+ root = strlen("file://");
+ else
+ root = git_path_root(url);
+
+ if (root >= 0)
+ git_buf_puts(&src_path, url + root);
+ else
+ git_buf_joinpath(&src_path, repository_base(repo), url);
+
+ if (git_buf_oom(&src_path))
+ return -1;
+
+ /* Copy .git/objects/ from the source to the target */
+ if ((error = git_repository_open(&src, git_buf_cstr(&src_path))) < 0) {
+ git_buf_free(&src_path);
+ return error;
+ }
+
+ git_buf_joinpath(&src_odb, git_repository_path(src), GIT_OBJECTS_DIR);
+ git_buf_joinpath(&dst_odb, git_repository_path(repo), GIT_OBJECTS_DIR);
+ if (git_buf_oom(&src_odb) || git_buf_oom(&dst_odb)) {
+ error = -1;
+ goto cleanup;
+ }
+
+ if ((error = git_futils_cp_r(git_buf_cstr(&src_odb), git_buf_cstr(&dst_odb),
+ 0, GIT_OBJECT_DIR_MODE)) < 0)
+ goto cleanup;
+
+ git_buf_printf(&reflog_message, "clone: from %s", git_remote_url(remote));
+
+ if ((error = git_remote_fetch(remote, signature, git_buf_cstr(&reflog_message))) != 0)
+ goto cleanup;
+
+ error = checkout_branch(repo, remote, co_opts, branch, signature, git_buf_cstr(&reflog_message));
+
+cleanup:
+ git_buf_free(&reflog_message);
+ git_buf_free(&src_path);
+ git_buf_free(&src_odb);
+ git_buf_free(&dst_odb);
+ git_repository_free(src);
+ return error;
+}