Commit 4386d80b by Carlos Martín Nieto

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.
parent 433ba614
......@@ -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
......@@ -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))) {
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);
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;
}
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