diff options
Diffstat (limited to 'src/clone.c')
| -rw-r--r-- | src/clone.c | 311 |
1 files changed, 194 insertions, 117 deletions
diff --git a/src/clone.c b/src/clone.c index c6be00f0e..6c4fb6727 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, @@ -105,54 +106,6 @@ static int create_tracking_branch( git_reference_name(*branch)); } -struct head_info { - git_repository *repo; - git_oid remote_head_oid; - git_buf branchname; - const git_refspec *refspec; - bool found; -}; - -static int reference_matches_remote_head( - const char *reference_name, - void *payload) -{ - struct head_info *head_info = (struct head_info *)payload; - git_oid oid; - int error; - - /* TODO: Should we guard against references - * which name doesn't start with refs/heads/ ? - */ - - error = git_reference_name_to_id(&oid, head_info->repo, reference_name); - if (error == GIT_ENOTFOUND) { - /* If the reference doesn't exists, it obviously cannot match the - * expected oid. */ - giterr_clear(); - return 0; - } - - if (!error && !git_oid__cmp(&head_info->remote_head_oid, &oid)) { - /* Determine the local reference name from the remote tracking one */ - error = git_refspec_rtransform( - &head_info->branchname, head_info->refspec, reference_name); - - if (!error && - git_buf_len(&head_info->branchname) > 0 && - !(error = git_buf_sets( - &head_info->branchname, - git_buf_cstr(&head_info->branchname) + - strlen(GIT_REFS_HEADS_DIR)))) - { - head_info->found = true; - error = GIT_ITEROVER; - } - } - - return error; -} - static int update_head_to_new_branch( git_repository *repo, const git_oid *target, @@ -161,7 +114,12 @@ static int update_head_to_new_branch( const char *reflog_message) { git_reference *tracking_branch = NULL; - int error = create_tracking_branch(&tracking_branch, repo, target, name, + int error; + + if (!git__prefixcmp(name, GIT_REFS_HEADS_DIR)) + name += strlen(GIT_REFS_HEADS_DIR); + + error = create_tracking_branch(&tracking_branch, repo, target, name, signature, reflog_message); if (!error) @@ -171,6 +129,10 @@ static int update_head_to_new_branch( git_reference_free(tracking_branch); + /* if it already existed, then the user's refspec created it for us, ignore it' */ + if (error == GIT_EEXISTS) + error = 0; + return error; } @@ -180,12 +142,13 @@ static int update_head_to_remote( const git_signature *signature, const char *reflog_message) { - int error = 0; + int error = 0, found_branch = 0; size_t refs_len; - git_refspec dummy_spec; + git_refspec dummy_spec, *refspec; const git_remote_head *remote_head, **refs; - struct head_info head_info; + const git_oid *remote_head_id; git_buf remote_master_name = GIT_BUF_INIT; + git_buf branch = GIT_BUF_INIT; if ((error = git_remote_ls(&refs, &refs_len, remote)) < 0) return error; @@ -195,63 +158,45 @@ static int update_head_to_remote( return setup_tracking_config( repo, "master", GIT_REMOTE_ORIGIN, GIT_REFS_HEADS_MASTER_FILE); + error = git_remote_default_branch(&branch, remote); + if (error == GIT_ENOTFOUND) { + git_buf_puts(&branch, GIT_REFS_HEADS_MASTER_FILE); + } else { + found_branch = 1; + } + /* Get the remote's HEAD. This is always the first ref in the list. */ remote_head = refs[0]; assert(remote_head); - memset(&head_info, 0, sizeof(head_info)); - git_oid_cpy(&head_info.remote_head_oid, &remote_head->oid); - head_info.repo = repo; - head_info.refspec = - git_remote__matching_refspec(remote, GIT_REFS_HEADS_MASTER_FILE); + remote_head_id = &remote_head->oid; + refspec = git_remote__matching_refspec(remote, git_buf_cstr(&branch)); - if (head_info.refspec == NULL) { + if (refspec == NULL) { memset(&dummy_spec, 0, sizeof(git_refspec)); - head_info.refspec = &dummy_spec; + refspec = &dummy_spec; } /* Determine the remote tracking reference name from the local master */ if ((error = git_refspec_transform( &remote_master_name, - head_info.refspec, - GIT_REFS_HEADS_MASTER_FILE)) < 0) + refspec, + git_buf_cstr(&branch))) < 0) return error; - /* Check to see if the remote HEAD points to the remote master */ - error = reference_matches_remote_head( - git_buf_cstr(&remote_master_name), &head_info); - if (error < 0 && error != GIT_ITEROVER) - goto cleanup; - - if (head_info.found) { - error = update_head_to_new_branch( - repo, - &head_info.remote_head_oid, - git_buf_cstr(&head_info.branchname), - signature, reflog_message); - goto cleanup; - } - - /* Not master. Check all the other refs. */ - error = git_reference_foreach_name( - repo, reference_matches_remote_head, &head_info); - if (error < 0 && error != GIT_ITEROVER) - goto cleanup; - - if (head_info.found) { + if (found_branch) { error = update_head_to_new_branch( repo, - &head_info.remote_head_oid, - git_buf_cstr(&head_info.branchname), + remote_head_id, + git_buf_cstr(&branch), signature, reflog_message); } else { error = git_repository_set_head_detached( - repo, &head_info.remote_head_oid, signature, reflog_message); + repo, remote_head_id, signature, reflog_message); } -cleanup: git_buf_free(&remote_master_name); - git_buf_free(&head_info.branchname); + git_buf_free(&branch); return error; } @@ -297,6 +242,15 @@ static int create_and_configure_origin( int error; git_remote *origin = NULL; const char *name; + char buf[GIT_PATH_MAX]; + + /* If the path exists and is a dir, the url should be the absolute path */ + if (git_path_root(url) < 0 && git_path_exists(url) && git_path_isdir(url)) { + if (p_realpath(url, buf) == NULL) + return -1; + + url = buf; + } name = options->remote_name ? options->remote_name : "origin"; if ((error = git_remote_create(&origin, repo, name, url)) < 0) @@ -336,59 +290,86 @@ static bool should_checkout( return !git_repository_head_unborn(repo); } -int git_clone_into(git_repository *repo, git_remote *remote, const git_checkout_options *co_opts, const char *branch, const git_signature *signature) +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 = 0, old_fetchhead; - git_strarray refspecs; + int error; git_buf reflog_message = GIT_BUF_INIT; + git_remote *remote; + const git_remote_callbacks *callbacks; - assert(repo && remote); + assert(repo && _remote); if (!git_repository_is_empty(repo)) { giterr_set(GITERR_INVALID, "the repository is not empty"); return -1; } - - if ((error = git_remote_get_fetch_refspecs(&refspecs, remote)) < 0) + if ((error = git_remote_dup(&remote, _remote)) < 0) return error; + callbacks = git_remote_get_callbacks(_remote); + if (!giterr__check_version(callbacks, 1, "git_remote_callbacks") && + (error = git_remote_set_callbacks(remote, callbacks)) < 0) + goto cleanup; + if ((error = git_remote_add_fetch(remote, "refs/tags/*:refs/tags/*")) < 0) - return error; + goto cleanup; - old_fetchhead = git_remote_update_fetchhead(remote); git_remote_set_update_fetchhead(remote, 0); 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; - 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_set_update_fetchhead(remote, old_fetchhead); - - /* Go back to the original refspecs */ - { - int error_alt = git_remote_set_fetch_refspecs(remote, &refspecs); - if (!error) - error = error_alt; - } - - git_strarray_free(&refspecs); + git_remote_free(remote); git_buf_free(&reflog_message); return error; } +int git_clone__should_clone_local(const char *url, git_clone_local_t local) +{ + const char *path; + int is_url; + + if (local == GIT_CLONE_NO_LOCAL) + return false; + + is_url = !git__prefixcmp(url, "file://"); + + if (is_url && local != GIT_CLONE_LOCAL && local != GIT_CLONE_LOCAL_NO_LINKS ) + return false; + + path = url; + if (is_url) + path = url + strlen("file://"); + + if ((git_path_exists(path) && git_path_isdir(path)) && local != GIT_CLONE_NO_LOCAL) + return true; + + return false; +} + int git_clone( git_repository **out, const char *url, @@ -423,8 +404,16 @@ 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_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, link, options.signature); + } else { + error = git_clone_into( + repo, origin, &options.checkout_opts, + options.checkout_branch, options.signature); + } git_remote_free(origin); } @@ -451,3 +440,91 @@ 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); +} + +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, 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; + + assert(repo && remote); + + 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. + */ + if ((error = git_path_from_url_or_path(&src_path, git_remote_url(remote))) < 0) + return error; + + /* 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; + } + + 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), + flags, 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; +} |
