diff --git a/builtin/clone.c b/builtin/clone.c index d1801bbe1a188b..343f536cf8a113 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -1101,8 +1101,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } } + /* + * Initialize the repository, but skip initializing the reference + * database. We do not yet know about the object format of the + * repository, and reference backends may persist that information into + * their on-disk data structures. + */ init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, NULL, - do_not_override_repo_unix_permissions, INIT_DB_QUIET); + do_not_override_repo_unix_permissions, INIT_DB_QUIET | INIT_DB_SKIP_REFDB); if (real_git_dir) { free((char *)git_dir); @@ -1189,10 +1195,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_required_reference.nr || option_optional_reference.nr) setup_reference(); - if (option_sparse_checkout && git_sparse_checkout_init(dir)) - return 1; - - remote = remote_get(remote_name); + remote = remote_get_early(remote_name); refspec_appendf(&remote->fetch, "+%s*:%s*", src_ref_prefix, branch_top.buf); @@ -1270,6 +1273,27 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (transport->smart_options && !deepen && !filter_options.choice) transport->smart_options->check_self_contained_and_connected = 1; + strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD"); + refspec_ref_prefixes(&remote->fetch, + &transport_ls_refs_options.ref_prefixes); + if (option_branch) + expand_ref_prefix(&transport_ls_refs_options.ref_prefixes, + option_branch); + if (!option_no_tags) + strvec_push(&transport_ls_refs_options.ref_prefixes, + "refs/tags/"); + + refs = transport_get_remote_refs(transport, &transport_ls_refs_options); + + /* + * Now that we know what algorithm the remote side is using, let's set + * ours to the same thing. + */ + hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); + initialize_repository_version(hash_algo, 1); + repo_set_hash_algo(the_repository, hash_algo); + create_reference_database(NULL, 1); + /* * Before fetching from the remote, download and install bundle * data from the --bundle-uri option. @@ -1285,24 +1309,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) bundle_uri); else if (has_heuristic) git_config_set_gently("fetch.bundleuri", bundle_uri); - } - - strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD"); - refspec_ref_prefixes(&remote->fetch, - &transport_ls_refs_options.ref_prefixes); - if (option_branch) - expand_ref_prefix(&transport_ls_refs_options.ref_prefixes, - option_branch); - if (!option_no_tags) - strvec_push(&transport_ls_refs_options.ref_prefixes, - "refs/tags/"); - - refs = transport_get_remote_refs(transport, &transport_ls_refs_options); - - if (refs) - mapped_refs = wanted_peer_refs(refs, &remote->fetch); - - if (!bundle_uri) { + } else { /* * Populate transport->got_remote_bundle_uri and * transport->bundle_uri. We might get nothing. @@ -1323,13 +1330,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } } - /* - * Now that we know what algorithm the remote side is using, - * let's set ours to the same thing. - */ - hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); - initialize_repository_version(hash_algo, 1); - repo_set_hash_algo(the_repository, hash_algo); + if (refs) + mapped_refs = wanted_peer_refs(refs, &remote->fetch); if (mapped_refs) { /* @@ -1432,6 +1434,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) dissociate_from_references(); } + if (option_sparse_checkout && git_sparse_checkout_init(dir)) + return 1; + junk_mode = JUNK_LEAVE_REPO; err = checkout(submodule_progress, filter_submodules); diff --git a/remote-curl.c b/remote-curl.c index ef05752ca5738e..fc29757b654c93 100644 --- a/remote-curl.c +++ b/remote-curl.c @@ -1564,8 +1564,11 @@ int cmd_main(int argc, const char **argv) if (buf.len == 0) break; if (starts_with(buf.buf, "fetch ")) { - if (nongit) - die(_("remote-curl: fetch attempted without a local repo")); + if (nongit) { + setup_git_directory_gently(&nongit); + if (nongit) + die(_("remote-curl: fetch attempted without a local repo")); + } parse_fetch(&buf); } else if (!strcmp(buf.buf, "list") || starts_with(buf.buf, "list ")) { diff --git a/remote.c b/remote.c index abb24822beb2a1..051d0a64a0b962 100644 --- a/remote.c +++ b/remote.c @@ -509,7 +509,7 @@ static void alias_all_urls(struct remote_state *remote_state) } } -static void read_config(struct repository *repo) +static void read_config(struct repository *repo, int early) { int flag; @@ -518,7 +518,7 @@ static void read_config(struct repository *repo) repo->remote_state->initialized = 1; repo->remote_state->current_branch = NULL; - if (startup_info->have_repository) { + if (startup_info->have_repository && !early) { const char *head_ref = refs_resolve_ref_unsafe( get_main_ref_store(repo), "HEAD", 0, NULL, &flag); if (head_ref && (flag & REF_ISSYMREF) && @@ -561,7 +561,7 @@ static const char *remotes_remote_for_branch(struct remote_state *remote_state, const char *remote_for_branch(struct branch *branch, int *explicit) { - read_config(the_repository); + read_config(the_repository, 0); die_on_missing_branch(the_repository, branch); return remotes_remote_for_branch(the_repository->remote_state, branch, @@ -587,7 +587,7 @@ remotes_pushremote_for_branch(struct remote_state *remote_state, const char *pushremote_for_branch(struct branch *branch, int *explicit) { - read_config(the_repository); + read_config(the_repository, 0); die_on_missing_branch(the_repository, branch); return remotes_pushremote_for_branch(the_repository->remote_state, @@ -599,7 +599,7 @@ static struct remote *remotes_remote_get(struct remote_state *remote_state, const char *remote_ref_for_branch(struct branch *branch, int for_push) { - read_config(the_repository); + read_config(the_repository, 0); die_on_missing_branch(the_repository, branch); if (branch) { @@ -709,7 +709,13 @@ remotes_remote_get(struct remote_state *remote_state, const char *name) struct remote *remote_get(const char *name) { - read_config(the_repository); + read_config(the_repository, 0); + return remotes_remote_get(the_repository->remote_state, name); +} + +struct remote *remote_get_early(const char *name) +{ + read_config(the_repository, 1); return remotes_remote_get(the_repository->remote_state, name); } @@ -722,7 +728,7 @@ remotes_pushremote_get(struct remote_state *remote_state, const char *name) struct remote *pushremote_get(const char *name) { - read_config(the_repository); + read_config(the_repository, 0); return remotes_pushremote_get(the_repository->remote_state, name); } @@ -738,7 +744,7 @@ int remote_is_configured(struct remote *remote, int in_repo) int for_each_remote(each_remote_fn fn, void *priv) { int i, result = 0; - read_config(the_repository); + read_config(the_repository, 0); for (i = 0; i < the_repository->remote_state->remotes_nr && !result; i++) { struct remote *remote = @@ -1831,7 +1837,7 @@ struct branch *branch_get(const char *name) { struct branch *ret; - read_config(the_repository); + read_config(the_repository, 0); if (!name || !*name || !strcmp(name, "HEAD")) ret = the_repository->remote_state->current_branch; else @@ -1973,7 +1979,7 @@ static const char *branch_get_push_1(struct remote_state *remote_state, const char *branch_get_push(struct branch *branch, struct strbuf *err) { - read_config(the_repository); + read_config(the_repository, 0); die_on_missing_branch(the_repository, branch); if (!branch) diff --git a/remote.h b/remote.h index cdc8b1db42c4a8..79353ba2265768 100644 --- a/remote.h +++ b/remote.h @@ -118,6 +118,7 @@ struct remote { * and configuration. */ struct remote *remote_get(const char *name); +struct remote *remote_get_early(const char *name); struct remote *pushremote_get(const char *name); int remote_is_configured(struct remote *remote, int in_repo); diff --git a/setup.c b/setup.c index 50131be7cc6a37..bc90bbd03373f0 100644 --- a/setup.c +++ b/setup.c @@ -1887,21 +1887,79 @@ void initialize_repository_version(int hash_algo, int reinit) git_config_set_gently("extensions.objectformat", NULL); } +static int is_reinit(void) +{ + struct strbuf buf = STRBUF_INIT; + char junk[2]; + int ret; + + git_path_buf(&buf, "HEAD"); + ret = !access(buf.buf, R_OK) || readlink(buf.buf, junk, sizeof(junk) - 1) != -1; + strbuf_release(&buf); + return ret; +} + +void create_reference_database(const char *initial_branch, int quiet) +{ + struct strbuf err = STRBUF_INIT; + int reinit = is_reinit(); + + /* + * We need to create a "refs" dir in any case so that older versions of + * Git can tell that this is a repository. This serves two main purposes: + * + * - Clients will know to stop walking the parent-directory chain when + * detecting the Git repository. Otherwise they may end up detecting + * a Git repository in a parent directory instead. + * + * - Instead of failing to detect a repository with unknown reference + * format altogether, old clients will print an error saying that + * they do not understand the reference format extension. + */ + safe_create_dir(git_path("refs"), 1); + adjust_shared_perm(git_path("refs")); + + if (refs_init_db(&err)) + die("failed to set up refs db: %s", err.buf); + + /* + * Point the HEAD symref to the initial branch with if HEAD does + * not yet exist. + */ + if (!reinit) { + char *ref; + + if (!initial_branch) + initial_branch = git_default_branch_name(quiet); + + ref = xstrfmt("refs/heads/%s", initial_branch); + if (check_refname_format(ref, 0) < 0) + die(_("invalid initial branch name: '%s'"), + initial_branch); + + if (create_symref("HEAD", ref, NULL) < 0) + exit(1); + free(ref); + } + + if (reinit && initial_branch) + warning(_("re-init: ignored --initial-branch=%s"), + initial_branch); + + strbuf_release(&err); +} + static int create_default_files(const char *template_path, const char *original_git_dir, - const char *initial_branch, const struct repository_format *fmt, int prev_bare_repository, - int init_shared_repository, - int quiet) + int init_shared_repository) { struct stat st1; struct strbuf buf = STRBUF_INIT; char *path; - char junk[2]; int reinit; int filemode; - struct strbuf err = STRBUF_INIT; const char *init_template_dir = NULL; const char *work_tree = get_git_work_tree(); @@ -1921,6 +1979,8 @@ static int create_default_files(const char *template_path, reset_shared_repository(); git_config(git_default_config, NULL); + reinit = is_reinit(); + /* * We must make sure command-line options continue to override any * values we might have just re-read from the config. @@ -1964,39 +2024,6 @@ static int create_default_files(const char *template_path, adjust_shared_perm(get_git_dir()); } - /* - * We need to create a "refs" dir in any case so that older - * versions of git can tell that this is a repository. - */ - safe_create_dir(git_path("refs"), 1); - adjust_shared_perm(git_path("refs")); - - if (refs_init_db(&err)) - die("failed to set up refs db: %s", err.buf); - - /* - * Point the HEAD symref to the initial branch with if HEAD does - * not yet exist. - */ - path = git_path_buf(&buf, "HEAD"); - reinit = (!access(path, R_OK) - || readlink(path, junk, sizeof(junk)-1) != -1); - if (!reinit) { - char *ref; - - if (!initial_branch) - initial_branch = git_default_branch_name(quiet); - - ref = xstrfmt("refs/heads/%s", initial_branch); - if (check_refname_format(ref, 0) < 0) - die(_("invalid initial branch name: '%s'"), - initial_branch); - - if (create_symref("HEAD", ref, NULL) < 0) - exit(1); - free(ref); - } - initialize_repository_version(fmt->hash_algo, 0); /* Check filemode trustability */ @@ -2156,14 +2183,11 @@ int init_db(const char *git_dir, const char *real_git_dir, validate_hash_algorithm(&repo_fmt, hash); reinit = create_default_files(template_dir, original_git_dir, - initial_branch, &repo_fmt, - prev_bare_repository, - init_shared_repository, - flags & INIT_DB_QUIET); - if (reinit && initial_branch) - warning(_("re-init: ignored --initial-branch=%s"), - initial_branch); + &repo_fmt, prev_bare_repository, + init_shared_repository); + if (!(flags & INIT_DB_SKIP_REFDB)) + create_reference_database(initial_branch, flags & INIT_DB_QUIET); create_object_directory(); if (get_shared_repository()) { diff --git a/setup.h b/setup.h index b48cf1c43b5a55..3f0f17c351cbc5 100644 --- a/setup.h +++ b/setup.h @@ -169,14 +169,16 @@ int verify_repository_format(const struct repository_format *format, */ void check_repository_format(struct repository_format *fmt); -#define INIT_DB_QUIET 0x0001 -#define INIT_DB_EXIST_OK 0x0002 +#define INIT_DB_QUIET (1 << 0) +#define INIT_DB_EXIST_OK (1 << 1) +#define INIT_DB_SKIP_REFDB (1 << 2) int init_db(const char *git_dir, const char *real_git_dir, const char *template_dir, int hash_algo, const char *initial_branch, int init_shared_repository, unsigned int flags); void initialize_repository_version(int hash_algo, int reinit); +void create_reference_database(const char *initial_branch, int quiet); /* * NOTE NOTE NOTE!! diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh index e444b30bf61568..4c3b32785d580f 100755 --- a/t/t5550-http-fetch-dumb.sh +++ b/t/t5550-http-fetch-dumb.sh @@ -66,11 +66,11 @@ test_expect_success 'create empty remote repository' ' setup_post_update_server_info_hook "$HTTPD_DOCUMENT_ROOT_PATH/empty.git" ' -test_expect_success 'empty dumb HTTP repository has default hash algorithm' ' +test_expect_success 'empty dumb HTTP repository falls back to SHA1' ' test_when_finished "rm -fr clone-empty" && git clone $HTTPD_URL/dumb/empty.git clone-empty && git -C clone-empty rev-parse --show-object-format >empty-format && - test "$(cat empty-format)" = "$(test_oid algo)" + test "$(cat empty-format)" = sha1 ' setup_askpass_helper diff --git a/t/t5558-clone-bundle-uri.sh b/t/t5558-clone-bundle-uri.sh index 996a08e90c9c2c..1ca5f745e73bd3 100755 --- a/t/t5558-clone-bundle-uri.sh +++ b/t/t5558-clone-bundle-uri.sh @@ -33,6 +33,15 @@ test_expect_success 'clone with path bundle' ' test_cmp expect actual ' +test_expect_success 'clone with path bundle and non-default hash' ' + test_when_finished "rm -rf clone-path-non-default-hash" && + GIT_DEFAULT_HASH=sha256 git clone --bundle-uri="clone-from/B.bundle" \ + clone-from clone-path-non-default-hash && + git -C clone-path-non-default-hash rev-parse refs/bundles/topic >actual && + git -C clone-from rev-parse topic >expect && + test_cmp expect actual +' + test_expect_success 'clone with file:// bundle' ' git clone --bundle-uri="file://$(pwd)/clone-from/B.bundle" \ clone-from clone-file && @@ -284,6 +293,15 @@ test_expect_success 'clone HTTP bundle' ' test_config -C clone-http log.excludedecoration refs/bundle/ ' +test_expect_success 'clone HTTP bundle with non-default hash' ' + test_when_finished "rm -rf clone-http-non-default-hash" && + GIT_DEFAULT_HASH=sha256 git clone --bundle-uri="$HTTPD_URL/B.bundle" \ + "$HTTPD_URL/smart/fetch.git" clone-http-non-default-hash && + git -C clone-http-non-default-hash rev-parse refs/bundles/topic >actual && + git -C clone-from rev-parse topic >expect && + test_cmp expect actual +' + test_expect_success 'clone bundle list (HTTP, no heuristic)' ' test_when_finished rm -f trace*.txt &&