diff options
Diffstat (limited to 'builtin/clone.c')
-rw-r--r-- | builtin/clone.c | 533 |
1 files changed, 363 insertions, 170 deletions
diff --git a/builtin/clone.c b/builtin/clone.c index f579794d9a..920ef7f8c9 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -37,20 +37,31 @@ static const char * const builtin_clone_usage[] = { NULL }; -static int option_no_checkout, option_bare, option_mirror; +static int option_no_checkout, option_bare, option_mirror, option_single_branch = -1; static int option_local, option_no_hardlinks, option_shared, option_recursive; -static char *option_template, *option_reference, *option_depth; +static char *option_template, *option_depth; static char *option_origin = NULL; static char *option_branch = NULL; static const char *real_git_dir; static char *option_upload_pack = "git-upload-pack"; static int option_verbosity; -static int option_progress; +static int option_progress = -1; +static struct string_list option_config; +static struct string_list option_reference; + +static int opt_parse_reference(const struct option *opt, const char *arg, int unset) +{ + struct string_list *option_reference = opt->value; + if (!arg) + return -1; + string_list_append(option_reference, arg); + return 0; +} static struct option builtin_clone_options[] = { OPT__VERBOSITY(&option_verbosity), - OPT_BOOLEAN(0, "progress", &option_progress, - "force progress reporting"), + OPT_BOOL(0, "progress", &option_progress, + "force progress reporting"), OPT_BOOLEAN('n', "no-checkout", &option_no_checkout, "don't create a checkout"), OPT_BOOLEAN(0, "bare", &option_bare, "create a bare repository"), @@ -71,19 +82,22 @@ static struct option builtin_clone_options[] = { "initialize submodules in the clone"), OPT_STRING(0, "template", &option_template, "template-directory", "directory from which templates will be used"), - OPT_STRING(0, "reference", &option_reference, "repo", - "reference repository"), - OPT_STRING('o', "origin", &option_origin, "branch", - "use <branch> instead of 'origin' to track upstream"), + OPT_CALLBACK(0 , "reference", &option_reference, "repo", + "reference repository", &opt_parse_reference), + OPT_STRING('o', "origin", &option_origin, "name", + "use <name> instead of 'origin' to track upstream"), OPT_STRING('b', "branch", &option_branch, "branch", "checkout <branch> instead of the remote's HEAD"), OPT_STRING('u', "upload-pack", &option_upload_pack, "path", "path to git-upload-pack on the remote"), OPT_STRING(0, "depth", &option_depth, "depth", "create a shallow clone of that depth"), + OPT_BOOL(0, "single-branch", &option_single_branch, + "clone only one branch, HEAD or --branch"), OPT_STRING(0, "separate-git-dir", &real_git_dir, "gitdir", "separate git dir from working tree"), - + OPT_STRING_LIST('c', "config", &option_config, "key=value", + "set config inside the new repository"), OPT_END() }; @@ -93,7 +107,7 @@ static const char *argv_submodule[] = { static char *get_repo_path(const char *repo, int *is_bundle) { - static char *suffix[] = { "/.git", ".git", "" }; + static char *suffix[] = { "/.git", "", ".git/.git", ".git" }; static char *bundle_suffix[] = { ".bundle", "" }; struct stat st; int i; @@ -101,9 +115,26 @@ static char *get_repo_path(const char *repo, int *is_bundle) for (i = 0; i < ARRAY_SIZE(suffix); i++) { const char *path; path = mkpath("%s%s", repo, suffix[i]); - if (is_directory(path)) { + if (stat(path, &st)) + continue; + if (S_ISDIR(st.st_mode) && is_git_directory(path)) { *is_bundle = 0; return xstrdup(absolute_path(path)); + } else if (S_ISREG(st.st_mode) && st.st_size > 8) { + /* Is it a "gitfile"? */ + char signature[8]; + int len, fd = open(path, O_RDONLY); + if (fd < 0) + continue; + len = read_in_full(fd, signature, 8); + close(fd); + if (len != 8 || strncmp(signature, "gitdir: ", 8)) + continue; + path = read_gitfile(path); + if (path) { + *is_bundle = 0; + return xstrdup(absolute_path(path)); + } } } @@ -197,39 +228,69 @@ static void strip_trailing_slashes(char *dir) *end = '\0'; } -static void setup_reference(const char *repo) +static int add_one_reference(struct string_list_item *item, void *cb_data) { - const char *ref_git; - char *ref_git_copy; - - struct remote *remote; - struct transport *transport; - const struct ref *extra; - - ref_git = real_path(option_reference); - - if (is_directory(mkpath("%s/.git/objects", ref_git))) - ref_git = mkpath("%s/.git", ref_git); - else if (!is_directory(mkpath("%s/objects", ref_git))) + char *ref_git; + struct strbuf alternate = STRBUF_INIT; + + /* Beware: real_path() and mkpath() return static buffer */ + ref_git = xstrdup(real_path(item->string)); + if (is_directory(mkpath("%s/.git/objects", ref_git))) { + char *ref_git_git = xstrdup(mkpath("%s/.git", ref_git)); + free(ref_git); + ref_git = ref_git_git; + } else if (!is_directory(mkpath("%s/objects", ref_git))) die(_("reference repository '%s' is not a local directory."), - option_reference); + item->string); - ref_git_copy = xstrdup(ref_git); - - add_to_alternates_file(ref_git_copy); + strbuf_addf(&alternate, "%s/objects", ref_git); + add_to_alternates_file(alternate.buf); + strbuf_release(&alternate); + free(ref_git); + return 0; +} - remote = remote_get(ref_git_copy); - transport = transport_get(remote, ref_git_copy); - for (extra = transport_get_remote_refs(transport); extra; - extra = extra->next) - add_extra_ref(extra->name, extra->old_sha1, 0); +static void setup_reference(void) +{ + for_each_string_list(&option_reference, add_one_reference, NULL); +} - transport_disconnect(transport); +static void copy_alternates(struct strbuf *src, struct strbuf *dst, + const char *src_repo) +{ + /* + * Read from the source objects/info/alternates file + * and copy the entries to corresponding file in the + * destination repository with add_to_alternates_file(). + * Both src and dst have "$path/objects/info/alternates". + * + * Instead of copying bit-for-bit from the original, + * we need to append to existing one so that the already + * created entry via "clone -s" is not lost, and also + * to turn entries with paths relative to the original + * absolute, so that they can be used in the new repository. + */ + FILE *in = fopen(src->buf, "r"); + struct strbuf line = STRBUF_INIT; - free(ref_git_copy); + while (strbuf_getline(&line, in, '\n') != EOF) { + char *abs_path, abs_buf[PATH_MAX]; + if (!line.len || line.buf[0] == '#') + continue; + if (is_absolute_path(line.buf)) { + add_to_alternates_file(line.buf); + continue; + } + abs_path = mkpath("%s/objects/%s", src_repo, line.buf); + normalize_path_copy(abs_buf, abs_path); + add_to_alternates_file(abs_buf); + } + strbuf_release(&line); + fclose(in); } -static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) +static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, + const char *src_repo, int src_baselen) { struct dirent *de; struct stat buf; @@ -265,7 +326,14 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) } if (S_ISDIR(buf.st_mode)) { if (de->d_name[0] != '.') - copy_or_link_directory(src, dest); + copy_or_link_directory(src, dest, + src_repo, src_baselen); + continue; + } + + /* Files that cannot be copied bit-for-bit... */ + if (!strcmp(src->buf + src_baselen, "/info/alternates")) { + copy_alternates(src, dest, src_repo); continue; } @@ -284,32 +352,25 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest) closedir(dir); } -static const struct ref *clone_local(const char *src_repo, - const char *dest_repo) +static void clone_local(const char *src_repo, const char *dest_repo) { - const struct ref *ret; - struct strbuf src = STRBUF_INIT; - struct strbuf dest = STRBUF_INIT; - struct remote *remote; - struct transport *transport; - - if (option_shared) - add_to_alternates_file(src_repo); - else { + if (option_shared) { + struct strbuf alt = STRBUF_INIT; + strbuf_addf(&alt, "%s/objects", src_repo); + add_to_alternates_file(alt.buf); + strbuf_release(&alt); + } else { + struct strbuf src = STRBUF_INIT; + struct strbuf dest = STRBUF_INIT; strbuf_addf(&src, "%s/objects", src_repo); strbuf_addf(&dest, "%s/objects", dest_repo); - copy_or_link_directory(&src, &dest); + copy_or_link_directory(&src, &dest, src_repo, src.len); strbuf_release(&src); strbuf_release(&dest); } - remote = remote_get(src_repo); - transport = transport_get(remote, src_repo); - ret = transport_get_remote_refs(transport); - transport_disconnect(transport); if (0 <= option_verbosity) printf(_("done.\n")); - return ret; } static const char *junk_work_tree; @@ -340,14 +401,57 @@ static void remove_junk_on_signal(int signo) raise(signo); } +static struct ref *find_remote_branch(const struct ref *refs, const char *branch) +{ + struct ref *ref; + struct strbuf head = STRBUF_INIT; + strbuf_addstr(&head, "refs/heads/"); + strbuf_addstr(&head, branch); + ref = find_ref_by_name(refs, head.buf); + strbuf_release(&head); + + if (ref) + return ref; + + strbuf_addstr(&head, "refs/tags/"); + strbuf_addstr(&head, branch); + ref = find_ref_by_name(refs, head.buf); + strbuf_release(&head); + + return ref; +} + static struct ref *wanted_peer_refs(const struct ref *refs, struct refspec *refspec) { - struct ref *local_refs = NULL; - struct ref **tail = &local_refs; + struct ref *head = copy_ref(find_ref_by_name(refs, "HEAD")); + struct ref *local_refs = head; + struct ref **tail = head ? &head->next : &local_refs; + + if (option_single_branch) { + struct ref *remote_head = NULL; + + if (!option_branch) + remote_head = guess_remote_head(head, refs, 0); + else { + local_refs = NULL; + tail = &local_refs; + remote_head = copy_ref(find_remote_branch(refs, option_branch)); + } + + if (!remote_head && option_branch) + warning(_("Could not find remote branch %s to clone."), + option_branch); + else { + get_fetch_map(remote_head, refspec, &tail, 0); + + /* if --branch=tag, pull the requested tag explicitly */ + get_fetch_map(remote_head, tag_refspec, &tail, 0); + } + } else + get_fetch_map(refs, refspec, &tail, 0); - get_fetch_map(refs, refspec, &tail, 0); - if (!option_mirror) + if (!option_mirror && !option_single_branch) get_fetch_map(refs, tag_refspec, &tail, 0); return local_refs; @@ -357,11 +461,153 @@ static void write_remote_refs(const struct ref *local_refs) { const struct ref *r; - for (r = local_refs; r; r = r->next) - add_extra_ref(r->peer_ref->name, r->old_sha1, 0); + for (r = local_refs; r; r = r->next) { + if (!r->peer_ref) + continue; + add_packed_ref(r->peer_ref->name, r->old_sha1); + } pack_refs(PACK_REFS_ALL); - clear_extra_refs(); +} + +static void write_followtags(const struct ref *refs, const char *msg) +{ + const struct ref *ref; + for (ref = refs; ref; ref = ref->next) { + if (prefixcmp(ref->name, "refs/tags/")) + continue; + if (!suffixcmp(ref->name, "^{}")) + continue; + if (!has_sha1_file(ref->old_sha1)) + continue; + update_ref(msg, ref->name, ref->old_sha1, + NULL, 0, DIE_ON_ERR); + } +} + +static void update_remote_refs(const struct ref *refs, + const struct ref *mapped_refs, + const struct ref *remote_head_points_at, + const char *branch_top, + const char *msg) +{ + if (refs) { + write_remote_refs(mapped_refs); + if (option_single_branch) + write_followtags(refs, msg); + } + + if (remote_head_points_at && !option_bare) { + struct strbuf head_ref = STRBUF_INIT; + strbuf_addstr(&head_ref, branch_top); + strbuf_addstr(&head_ref, "HEAD"); + create_symref(head_ref.buf, + remote_head_points_at->peer_ref->name, + msg); + } +} + +static void update_head(const struct ref *our, const struct ref *remote, + const char *msg) +{ + if (our && !prefixcmp(our->name, "refs/heads/")) { + /* Local default branch link */ + create_symref("HEAD", our->name, NULL); + if (!option_bare) { + const char *head = skip_prefix(our->name, "refs/heads/"); + update_ref(msg, "HEAD", our->old_sha1, NULL, 0, DIE_ON_ERR); + install_branch_config(0, head, option_origin, our->name); + } + } else if (our) { + struct commit *c = lookup_commit_reference(our->old_sha1); + /* --branch specifies a non-branch (i.e. tags), detach HEAD */ + update_ref(msg, "HEAD", c->object.sha1, + NULL, REF_NODEREF, DIE_ON_ERR); + } else if (remote) { + /* + * We know remote HEAD points to a non-branch, or + * HEAD points to a branch but we don't know which one. + * Detach HEAD in all these cases. + */ + update_ref(msg, "HEAD", remote->old_sha1, + NULL, REF_NODEREF, DIE_ON_ERR); + } +} + +static int checkout(void) +{ + unsigned char sha1[20]; + char *head; + struct lock_file *lock_file; + struct unpack_trees_options opts; + struct tree *tree; + struct tree_desc t; + int err = 0, fd; + + if (option_no_checkout) + return 0; + + head = resolve_refdup("HEAD", sha1, 1, NULL); + if (!head) { + warning(_("remote HEAD refers to nonexistent ref, " + "unable to checkout.\n")); + return 0; + } + if (!strcmp(head, "HEAD")) { + if (advice_detached_head) + detach_advice(sha1_to_hex(sha1)); + } else { + if (prefixcmp(head, "refs/heads/")) + die(_("HEAD not found below refs/heads!")); + } + free(head); + + /* We need to be in the new work tree for the checkout */ + setup_work_tree(); + + lock_file = xcalloc(1, sizeof(struct lock_file)); + fd = hold_locked_index(lock_file, 1); + + memset(&opts, 0, sizeof opts); + opts.update = 1; + opts.merge = 1; + opts.fn = oneway_merge; + opts.verbose_update = (option_verbosity >= 0); + opts.src_index = &the_index; + opts.dst_index = &the_index; + + tree = parse_tree_indirect(sha1); + parse_tree(tree); + init_tree_desc(&t, tree->buffer, tree->size); + unpack_trees(1, &t, &opts); + + if (write_cache(fd, active_cache, active_nr) || + commit_locked_index(lock_file)) + die(_("unable to write new index file")); + + err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1), + sha1_to_hex(sha1), "1", NULL); + + if (!err && option_recursive) + err = run_command_v_opt(argv_submodule, RUN_GIT_CMD); + + return err; +} + +static int write_one_config(const char *key, const char *value, void *data) +{ + return git_config_set_multivar(key, value ? value : "true", "^$", 0); +} + +static void write_config(struct string_list *config) +{ + int i; + + for (i = 0; i < config->nr; i++) { + if (git_config_parse_parameter(config->items[i].string, + write_one_config, NULL) < 0) + die("unable to write parameters to config file"); + } } int cmd_clone(int argc, const char **argv, const char *prefix) @@ -375,11 +621,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix) const struct ref *remote_head_points_at; const struct ref *our_head_points_at; struct ref *mapped_refs; + const struct ref *ref; struct strbuf key = STRBUF_INIT, value = STRBUF_INIT; struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT; struct transport *transport = NULL; - char *src_ref_prefix = "refs/heads/"; - int err = 0; + const char *src_ref_prefix = "refs/heads/"; + struct remote *remote; + int err = 0, complete_refs_before_fetch = 1; struct refspec *refspec; const char *fetch_pattern; @@ -398,6 +646,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) usage_msg_opt(_("You must specify a repository to clone."), builtin_clone_usage, builtin_clone_options); + if (option_single_branch == -1) + option_single_branch = option_depth ? 1 : 0; + if (option_mirror) option_bare = 1; @@ -457,7 +708,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (safe_create_leading_directories_const(work_tree) < 0) die_errno(_("could not create leading directories of '%s'"), work_tree); - if (!dest_exists && mkdir(work_tree, 0755)) + if (!dest_exists && mkdir(work_tree, 0777)) die_errno(_("could not create work tree dir '%s'."), work_tree); set_git_work_tree(work_tree); @@ -477,11 +728,12 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (0 <= option_verbosity) { if (option_bare) - printf(_("Cloning into bare repository %s...\n"), dir); + printf(_("Cloning into bare repository '%s'...\n"), dir); else - printf(_("Cloning into %s...\n"), dir); + printf(_("Cloning into '%s'...\n"), dir); } init_db(option_template, INIT_DB_QUIET); + write_config(&option_config); /* * At this point, the config exists, so we do not need the @@ -521,21 +773,18 @@ int cmd_clone(int argc, const char **argv, const char *prefix) git_config_set(key.buf, repo); strbuf_reset(&key); - if (option_reference) - setup_reference(git_dir); + if (option_reference.nr) + setup_reference(); fetch_pattern = value.buf; refspec = parse_fetch_refspec(1, &fetch_pattern); strbuf_reset(&value); - if (is_local) { - refs = clone_local(path, git_dir); - mapped_refs = wanted_peer_refs(refs, refspec); - } else { - struct remote *remote = remote_get(option_origin); - transport = transport_get(remote, remote->url[0]); + remote = remote_get(option_origin); + transport = transport_get(remote, remote->url[0]); + if (!is_local) { if (!transport->get_refs_list || !transport->fetch) die(_("Don't know how to clone %s"), transport->url); @@ -544,49 +793,57 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_depth) transport_set_option(transport, TRANS_OPT_DEPTH, option_depth); + if (option_single_branch) + transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1"); transport_set_verbosity(transport, option_verbosity, option_progress); if (option_upload_pack) transport_set_option(transport, TRANS_OPT_UPLOADPACK, option_upload_pack); - - refs = transport_get_remote_refs(transport); - if (refs) { - mapped_refs = wanted_peer_refs(refs, refspec); - transport_fetch_refs(transport, mapped_refs); - } } + refs = transport_get_remote_refs(transport); + if (refs) { - clear_extra_refs(); + mapped_refs = wanted_peer_refs(refs, refspec); + /* + * transport_get_remote_refs() may return refs with null sha-1 + * in mapped_refs (see struct transport->get_refs_list + * comment). In that case we need fetch it early because + * remote_head code below relies on it. + * + * for normal clones, transport_get_remote_refs() should + * return reliable ref set, we can delay cloning until after + * remote HEAD check. + */ + for (ref = refs; ref; ref = ref->next) + if (is_null_sha1(ref->old_sha1)) { + complete_refs_before_fetch = 0; + break; + } - write_remote_refs(mapped_refs); + if (!is_local && !complete_refs_before_fetch) + transport_fetch_refs(transport, mapped_refs); remote_head = find_ref_by_name(refs, "HEAD"); remote_head_points_at = guess_remote_head(remote_head, mapped_refs, 0); if (option_branch) { - struct strbuf head = STRBUF_INIT; - strbuf_addstr(&head, src_ref_prefix); - strbuf_addstr(&head, option_branch); our_head_points_at = - find_ref_by_name(mapped_refs, head.buf); - strbuf_release(&head); - - if (!our_head_points_at) { - warning(_("Remote branch %s not found in " - "upstream %s, using HEAD instead"), - option_branch, option_origin); - our_head_points_at = remote_head_points_at; - } + find_remote_branch(mapped_refs, option_branch); + + if (!our_head_points_at) + die(_("Remote branch %s not found in upstream %s"), + option_branch, option_origin); } else our_head_points_at = remote_head_points_at; } else { warning(_("You appear to have cloned an empty repository.")); + mapped_refs = NULL; our_head_points_at = NULL; remote_head_points_at = NULL; remote_head = NULL; @@ -596,84 +853,20 @@ int cmd_clone(int argc, const char **argv, const char *prefix) "refs/heads/master"); } - if (remote_head_points_at && !option_bare) { - struct strbuf head_ref = STRBUF_INIT; - strbuf_addstr(&head_ref, branch_top.buf); - strbuf_addstr(&head_ref, "HEAD"); - create_symref(head_ref.buf, - remote_head_points_at->peer_ref->name, - reflog_msg.buf); - } + if (is_local) + clone_local(path, git_dir); + else if (refs && complete_refs_before_fetch) + transport_fetch_refs(transport, mapped_refs); - if (our_head_points_at) { - /* Local default branch link */ - create_symref("HEAD", our_head_points_at->name, NULL); - if (!option_bare) { - const char *head = skip_prefix(our_head_points_at->name, - "refs/heads/"); - update_ref(reflog_msg.buf, "HEAD", - our_head_points_at->old_sha1, - NULL, 0, DIE_ON_ERR); - install_branch_config(0, head, option_origin, - our_head_points_at->name); - } - } else if (remote_head) { - /* Source had detached HEAD pointing somewhere. */ - if (!option_bare) { - update_ref(reflog_msg.buf, "HEAD", - remote_head->old_sha1, - NULL, REF_NODEREF, DIE_ON_ERR); - our_head_points_at = remote_head; - } - } else { - /* Nothing to checkout out */ - if (!option_no_checkout) - warning(_("remote HEAD refers to nonexistent ref, " - "unable to checkout.\n")); - option_no_checkout = 1; - } + update_remote_refs(refs, mapped_refs, remote_head_points_at, + branch_top.buf, reflog_msg.buf); - if (transport) { - transport_unlock_pack(transport); - transport_disconnect(transport); - } + update_head(our_head_points_at, remote_head, reflog_msg.buf); - if (!option_no_checkout) { - struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file)); - struct unpack_trees_options opts; - struct tree *tree; - struct tree_desc t; - int fd; - - /* We need to be in the new work tree for the checkout */ - setup_work_tree(); - - fd = hold_locked_index(lock_file, 1); - - memset(&opts, 0, sizeof opts); - opts.update = 1; - opts.merge = 1; - opts.fn = oneway_merge; - opts.verbose_update = (option_verbosity > 0); - opts.src_index = &the_index; - opts.dst_index = &the_index; - - tree = parse_tree_indirect(our_head_points_at->old_sha1); - parse_tree(tree); - init_tree_desc(&t, tree->buffer, tree->size); - unpack_trees(1, &t, &opts); - - if (write_cache(fd, active_cache, active_nr) || - commit_locked_index(lock_file)) - die(_("unable to write new index file")); - - err |= run_hook(NULL, "post-checkout", sha1_to_hex(null_sha1), - sha1_to_hex(our_head_points_at->old_sha1), "1", - NULL); - - if (!err && option_recursive) - err = run_command_v_opt(argv_submodule, RUN_GIT_CMD); - } + transport_unlock_pack(transport); + transport_disconnect(transport); + + err = checkout(); strbuf_release(&reflog_msg); strbuf_release(&branch_top); |