diff --git a/builtin/clone.c b/builtin/clone.c index 5b25cca510..48ac379b1d 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -154,24 +154,26 @@ static char *get_repo_path(const char *repo, int *is_bundle) return canon; } -static int add_one_reference(struct string_list_item *item, void *cb_data) +struct add_one_alternate_data { + struct strvec *alternates; + int required; +}; + +static int add_one_alternate(struct string_list_item *item, void *cb_data) { + struct add_one_alternate_data *data = cb_data; struct strbuf err = STRBUF_INIT; - int *required = cb_data; char *ref_git = compute_alternate_path(item->string, &err); if (!ref_git) { - if (*required) + if (data->required) die("%s", err.buf); else fprintf(stderr, _("info: Could not add alternate for '%s': %s\n"), item->string, err.buf); } else { - struct strbuf sb = STRBUF_INIT; - strbuf_addf(&sb, "%s/objects", ref_git); - odb_add_to_alternates_file(the_repository->objects, sb.buf); - strbuf_release(&sb); + strvec_pushf(data->alternates, "%s/objects", ref_git); } strbuf_release(&err); @@ -179,17 +181,7 @@ static int add_one_reference(struct string_list_item *item, void *cb_data) return 0; } -static void setup_reference(void) -{ - int required = 1; - for_each_string_list(&option_required_reference, - add_one_reference, &required); - required = 0; - for_each_string_list(&option_optional_reference, - add_one_reference, &required); -} - -static void copy_alternates(struct strbuf *src, const char *src_repo) +static void read_alternates(struct strvec *alternates, const char *src_repo) { /* * Read from the source objects/info/alternates file @@ -203,29 +195,70 @@ static void copy_alternates(struct strbuf *src, const char *src_repo) * to turn entries with paths relative to the original * absolute, so that they can be used in the new repository. */ - FILE *in = xfopen(src->buf, "r"); + FILE *in; + struct strbuf path = STRBUF_INIT; struct strbuf line = STRBUF_INIT; + strbuf_addf(&path, "%s/objects/info/alternates", src_repo); + + in = fopen(path.buf, "r"); + if (!in) { + if (errno == ENOENT) + goto out; + die_errno("could not read alternates file '%s'", path.buf); + } + while (strbuf_getline(&line, in) != EOF) { char *abs_path; if (!line.len || line.buf[0] == '#') continue; if (is_absolute_path(line.buf)) { - odb_add_to_alternates_file(the_repository->objects, - line.buf); + strvec_push(alternates, line.buf); continue; } abs_path = mkpathdup("%s/objects/%s", src_repo, line.buf); if (!normalize_path_copy(abs_path, abs_path)) - odb_add_to_alternates_file(the_repository->objects, - abs_path); + strvec_push(alternates, abs_path); else warning("skipping invalid relative alternate: %s/%s", src_repo, line.buf); free(abs_path); } + +out: + strbuf_release(&path); strbuf_release(&line); - fclose(in); + if (in) + fclose(in); +} + +static void collect_alternates(struct strvec *alternates, + const char *src_repo, bool is_local) +{ + if (option_required_reference.nr || option_optional_reference.nr) { + struct add_one_alternate_data data = { + .alternates = alternates, + .required = 1, + }; + + for_each_string_list(&option_required_reference, + add_one_alternate, &data); + data.required = 0; + for_each_string_list(&option_optional_reference, + add_one_alternate, &data); + } + + if (is_local) { + struct strbuf commondir = STRBUF_INIT; + + get_common_dir(&commondir, src_repo); + if (option_shared) + strvec_pushf(alternates, "%s/objects", commondir.buf); + else + read_alternates(alternates, commondir.buf); + + strbuf_release(&commondir); + } } static void mkdir_if_missing(const char *pathname, mode_t mode) @@ -301,11 +334,9 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, continue; } - /* Files that cannot be copied bit-for-bit... */ - if (!fspathcmp(iter->relative_path, "info/alternates")) { - copy_alternates(src, src_repo); + /* Alternates were already handled earlier. */ + if (!fspathcmp(iter->relative_path, "info/alternates")) continue; - } if (unlink(dest->buf) && errno != ENOENT) die_errno(_("failed to unlink '%s'"), dest->buf); @@ -349,13 +380,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, static void clone_local(const char *src_repo, const char *dest_repo) { - if (option_shared) { - struct strbuf alt = STRBUF_INIT; - get_common_dir(&alt, src_repo); - strbuf_addstr(&alt, "/objects"); - odb_add_to_alternates_file(the_repository->objects, alt.buf); - strbuf_release(&alt); - } else { + if (!option_shared) { struct strbuf src = STRBUF_INIT; struct strbuf dest = STRBUF_INIT; get_common_dir(&src, src_repo); @@ -999,6 +1024,7 @@ int cmd_clone(int argc, N_("uri"), N_("a URI for downloading bundles before fetching from origin remote")), OPT_END() }; + struct strvec alternates = STRVEC_INIT; const char * const builtin_clone_usage[] = { N_("git clone [] [--] []"), @@ -1184,11 +1210,14 @@ int cmd_clone(int argc, * 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. + * + * Furthermore, we skip initializing the object database so that we can + * first resolve potential alternates before creating it. */ init_db(the_repository, git_dir, real_git_dir, work_tree, option_template, GIT_HASH_UNKNOWN, ref_storage_format, NULL, do_not_override_repo_unix_permissions, - INIT_DB_QUIET | INIT_DB_SKIP_REFDB); + INIT_DB_QUIET | INIT_DB_SKIP_REFDB | INIT_DB_SKIP_ODB); if (real_git_dir) { free((char *)git_dir); @@ -1311,9 +1340,6 @@ int cmd_clone(int argc, strbuf_reset(&key); } - if (option_required_reference.nr || option_optional_reference.nr) - setup_reference(); - remote = remote_get_early(remote_name); if (!option_rev) @@ -1342,6 +1368,9 @@ int cmd_clone(int argc, if (option_local > 0 && !is_local) warning(_("--local is ignored")); + collect_alternates(&alternates, path, is_local); + create_object_database(the_repository, &alternates); + transport = transport_get(remote, path ? path : remote->url.v[0]); transport_set_verbosity(transport, option_verbosity, option_progress); transport->family = family; @@ -1637,6 +1666,7 @@ int cmd_clone(int argc, string_list_clear(&option_not, 0); string_list_clear(&option_config, 0); string_list_clear(&server_options, 0); + strvec_clear(&alternates); free(remote_name); strbuf_release(&reflog_msg); diff --git a/odb.c b/odb.c index 6d5943e5ea..40b0e13539 100644 --- a/odb.c +++ b/odb.c @@ -238,15 +238,6 @@ static struct odb_source *odb_add_alternate_recursively(struct object_database * return alternate; } -void odb_add_to_alternates_file(struct object_database *odb, - const char *dir) -{ - int ret = odb_source_write_alternate(odb->sources, dir); - if (ret < 0) - die(NULL); - odb_add_alternate_recursively(odb, dir, 0); -} - struct odb_source *odb_add_to_alternates_memory(struct object_database *odb, const char *dir) { diff --git a/odb.h b/odb.h index 248ee9cdfa..24b45238a0 100644 --- a/odb.h +++ b/odb.h @@ -265,13 +265,6 @@ int odb_mkstemp(struct object_database *odb, */ int odb_has_alternates(struct object_database *odb); -/* - * Add the directory to the on-disk alternates file; the new entry will also - * take effect in the current process. - */ -void odb_add_to_alternates_file(struct object_database *odb, - const char *dir); - /* * Add the directory to the in-memory list of alternate sources (along with any * recursive alternates it points to), but do not modify the on-disk alternates diff --git a/odb/source-files.c b/odb/source-files.c index 4d510e79db..28747b580d 100644 --- a/odb/source-files.c +++ b/odb/source-files.c @@ -19,6 +19,7 @@ #include "run-command.h" #include "strbuf.h" #include "string-list.h" +#include "strmap.h" #include "strvec.h" #include "tree.h" #include "write-or-die.h" @@ -51,9 +52,14 @@ static void odb_source_files_close(struct odb_source *source) odb_source_close(&files->packed->base); } -static int odb_source_files_create_on_disk(struct odb_source *source) +static int odb_source_files_create_on_disk(struct odb_source *source, + const struct odb_create_on_disk_options *opts) { + struct lock_file alternates_lock = LOCK_INIT; struct strbuf path = STRBUF_INIT; + struct strset seen = STRSET_INIT; + struct strbuf line = STRBUF_INIT; + int ret; safe_create_dir(source->odb->repo, source->path, 1); @@ -64,8 +70,74 @@ static int odb_source_files_create_on_disk(struct odb_source *source) strbuf_addf(&path, "%s/info", source->path); safe_create_dir(source->odb->repo, path.buf, 1); + if (opts->alternates && opts->alternates->nr) { + FILE *alternates, *orig; + + strbuf_reset(&path); + strbuf_addf(&path, "%s/info/alternates", source->path); + + repo_hold_lock_file_for_update(source->odb->repo, &alternates_lock, + path.buf, LOCK_DIE_ON_ERROR); + + alternates = fdopen_lock_file(&alternates_lock, "w"); + if (!alternates) { + ret = error_errno(_("unable to fdopen alternates lockfile")); + goto out; + } + + /* + * The alternates file may already exist, e.g. when it has been + * seeded from a template directory. Read any preexisting + * entries so that we don't end up writing duplicates. + */ + orig = fopen(path.buf, "r"); + if (orig) { + while (strbuf_getline(&line, orig) != EOF) { + strset_add(&seen, line.buf); + fprintf(alternates, "%s\n", line.buf); + } + + if (ferror(orig)) { + ret = error_errno(_("unable to read alternates file")); + fclose(orig); + goto out; + } + + fclose(orig); + } else if (errno != ENOENT) { + ret = error_errno(_("unable to read alternates file")); + goto out; + } + + for (size_t i = 0; i < opts->alternates->nr; i++) { + const char *alternate = opts->alternates->v[i]; + if (!strset_add(&seen, alternate)) + continue; + fprintf(alternates, "%s\n", alternate); + } + + if (ferror(alternates)) { + ret = error_errno(_("unable to write alternates file")); + goto out; + } + + if (commit_lock_file(&alternates_lock)) { + ret = error_errno(_("unable to commit alternates file")); + goto out; + } + } + + /* Reprepare the object database to activate alternates. */ + odb_reprepare(source->odb); + + ret = 0; + +out: + rollback_lock_file(&alternates_lock); + strbuf_release(&line); strbuf_release(&path); - return 0; + strset_clear(&seen); + return ret; } static void odb_source_files_prepare(struct odb_source *source, @@ -251,59 +323,6 @@ static int odb_source_files_read_alternates(struct odb_source *source, return 0; } -static int odb_source_files_write_alternate(struct odb_source *source, - const char *alternate) -{ - struct lock_file lock = LOCK_INIT; - char *path = xstrfmt("%s/%s", source->path, "info/alternates"); - FILE *in, *out; - int found = 0; - int ret; - - repo_hold_lock_file_for_update(source->odb->repo, &lock, path, - LOCK_DIE_ON_ERROR); - out = fdopen_lock_file(&lock, "w"); - if (!out) { - ret = error_errno(_("unable to fdopen alternates lockfile")); - goto out; - } - - in = fopen(path, "r"); - if (in) { - struct strbuf line = STRBUF_INIT; - - while (strbuf_getline(&line, in) != EOF) { - if (!strcmp(alternate, line.buf)) { - found = 1; - break; - } - fprintf_or_die(out, "%s\n", line.buf); - } - - strbuf_release(&line); - fclose(in); - } else if (errno != ENOENT) { - ret = error_errno(_("unable to read alternates file")); - goto out; - } - - if (found) { - rollback_lock_file(&lock); - } else { - fprintf_or_die(out, "%s\n", alternate); - if (commit_lock_file(&lock)) { - ret = error_errno(_("unable to move new alternates file into place")); - goto out; - } - } - - ret = 0; - -out: - free(path); - return ret; -} - static int too_many_loose_objects(struct odb_source_files *files, int limit) { unsigned long loose_count; @@ -934,7 +953,6 @@ struct odb_source_files *odb_source_files_new(struct object_database *odb, files->base.write_object_stream = odb_source_files_write_object_stream; files->base.begin_transaction = odb_source_files_begin_transaction; files->base.read_alternates = odb_source_files_read_alternates; - files->base.write_alternate = odb_source_files_write_alternate; files->base.optimize = odb_source_files_optimize; files->base.optimize_required = odb_source_files_optimize_required; files->base.generate_pack = odb_source_files_generate_pack; diff --git a/odb/source-inmemory.c b/odb/source-inmemory.c index 4905e71c7e..8aff27c2f9 100644 --- a/odb/source-inmemory.c +++ b/odb/source-inmemory.c @@ -327,12 +327,6 @@ static int odb_source_inmemory_read_alternates(struct odb_source *source UNUSED, return 0; } -static int odb_source_inmemory_write_alternate(struct odb_source *source UNUSED, - const char *alternate UNUSED) -{ - return error("in-memory source does not support alternates"); -} - static void odb_source_inmemory_close(struct odb_source *source UNUSED) { } @@ -389,7 +383,6 @@ struct odb_source_inmemory *odb_source_inmemory_new(struct object_database *odb) source->base.freshen_object = odb_source_inmemory_freshen_object; source->base.begin_transaction = odb_source_inmemory_begin_transaction; source->base.read_alternates = odb_source_inmemory_read_alternates; - source->base.write_alternate = odb_source_inmemory_write_alternate; return source; } diff --git a/odb/source-loose.c b/odb/source-loose.c index ec25700479..57ad423523 100644 --- a/odb/source-loose.c +++ b/odb/source-loose.c @@ -990,12 +990,6 @@ static int odb_source_loose_read_alternates(struct odb_source *source UNUSED, return 0; } -static int odb_source_loose_write_alternate(struct odb_source *source UNUSED, - const char *alternate UNUSED) -{ - return error("loose source does not support alternates"); -} - static void odb_source_loose_clear_cache(struct odb_source_loose *loose) { oidtree_clear(loose->cache); @@ -1060,7 +1054,6 @@ struct odb_source_loose *odb_source_loose_new(struct object_database *odb, loose->base.write_object_stream = odb_source_loose_write_object_stream; loose->base.begin_transaction = odb_source_loose_begin_transaction; loose->base.read_alternates = odb_source_loose_read_alternates; - loose->base.write_alternate = odb_source_loose_write_alternate; if (!is_absolute_path(loose->base.path)) chdir_notify_register(odb_source_loose_reparent, loose); diff --git a/odb/source-packed.c b/odb/source-packed.c index 1babccde78..9bea049b20 100644 --- a/odb/source-packed.c +++ b/odb/source-packed.c @@ -658,12 +658,6 @@ static int odb_source_packed_read_alternates(struct odb_source *source UNUSED, return 0; } -static int odb_source_packed_write_alternate(struct odb_source *source UNUSED, - const char *alternate UNUSED) -{ - return error("packed backend cannot write alternates"); -} - void (*report_garbage)(unsigned seen_bits, const char *path); static void report_helper(const struct string_list *list, @@ -878,7 +872,6 @@ struct odb_source_packed *odb_source_packed_new(struct object_database *odb, packed->base.write_object_stream = odb_source_packed_write_object_stream; packed->base.begin_transaction = odb_source_packed_begin_transaction; packed->base.read_alternates = odb_source_packed_read_alternates; - packed->base.write_alternate = odb_source_packed_write_alternate; if (!is_absolute_path(path)) chdir_notify_register(odb_source_packed_reparent, packed); diff --git a/odb/source.h b/odb/source.h index 515360e563..16f6662350 100644 --- a/odb/source.h +++ b/odb/source.h @@ -37,6 +37,15 @@ struct odb_stream; struct strbuf; struct strvec; +struct odb_create_on_disk_options { + /* + * Alternates that shall be written into the newly created object + * database. Whether or not this option can be handled is specific to + * the backend. + */ + const struct strvec *alternates; +}; + /* * The source is the part of the object database that stores the actual * objects. It thus encapsulates the logic to read and write the specific @@ -107,7 +116,8 @@ struct odb_source { * This callback may be NULL in case the source does not need any * on-disk setup. */ - int (*create_on_disk)(struct odb_source *source); + int (*create_on_disk)(struct odb_source *source, + const struct odb_create_on_disk_options *opts); /* * This callback is expected to prepare the source so that it becomes @@ -281,19 +291,6 @@ struct odb_source { int (*read_alternates)(struct odb_source *source, struct strvec *out); - /* - * This callback is expected to persist the singular alternate passed - * to it into its list of alternates. Any pre-existing alternates are - * expected to remain active. Subsequent calls to `read_alternates` are - * thus expected to yield the pre-existing list of alternates plus the - * newly added alternate appended to its end. - * - * The callback is expected to return 0 on success, a negative error - * code otherwise. - */ - int (*write_alternate)(struct odb_source *source, - const char *alternate); - /* * This callback is expected to optimize the object database source. * Returns 0 on success, a negative error code otherwise. @@ -378,11 +375,12 @@ static inline void odb_source_close(struct odb_source *source) * Create on-disk data structures that are required for this source to operate * correctly. Returns 0 on success, a negative error code otherwise. */ -static inline int odb_source_create_on_disk(struct odb_source *source) +static inline int odb_source_create_on_disk(struct odb_source *source, + const struct odb_create_on_disk_options *opts) { if (!source->create_on_disk) return 0; - return source->create_on_disk(source); + return source->create_on_disk(source, opts); } /* @@ -534,19 +532,6 @@ static inline int odb_source_read_alternates(struct odb_source *source, return source->read_alternates(source, out); } -/* - * Write and persist a new alternate object database source for the given - * source. Any preexisting alternates are expected to stay valid, and the new - * alternate shall be appended to the end of the list. - * - * Returns 0 on success, a negative error code otherwise. - */ -static inline int odb_source_write_alternate(struct odb_source *source, - const char *alternate) -{ - return source->write_alternate(source, alternate); -} - /* * Create a new transaction that can be used to write objects into a temporary * staging area. The objects will only be persisted when the transaction is diff --git a/setup.c b/setup.c index 0d157ac254..c6b7a51f2b 100644 --- a/setup.c +++ b/setup.c @@ -2663,8 +2663,13 @@ static int create_default_files(struct repository *repo, return reinit; } -static void create_object_database(struct repository *repo) +void create_object_database(struct repository *repo, + const struct strvec *alternates) { + struct odb_create_on_disk_options opts = { + .alternates = alternates, + }; + /* * Create the "objects" directory in the common directory. This is done * so that the repository can be discovered regardless of the backend @@ -2684,7 +2689,7 @@ static void create_object_database(struct repository *repo) repo->objects = odb_new(repo, ODB_NEW_HONOR_ENV); - if (odb_source_create_on_disk(repo->objects->sources) < 0) + if (odb_source_create_on_disk(repo->objects->sources, &opts) < 0) die(_("failed creating object database")); } @@ -2845,7 +2850,8 @@ int init_db(struct repository *repo, const char *template_dir, int hash, enum ref_storage_format ref_storage_format, const char *initial_branch, - int init_shared_repository, unsigned int flags) + int init_shared_repository, + enum init_db_flags flags) { int reinit; int exist_ok = flags & INIT_DB_EXIST_OK; @@ -2919,7 +2925,8 @@ int init_db(struct repository *repo, if (!(flags & INIT_DB_SKIP_REFDB)) create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET); - create_object_database(repo); + if (!(flags & INIT_DB_SKIP_ODB)) + create_object_database(repo, NULL); startup_info->have_repository = 1; diff --git a/setup.h b/setup.h index 7394473e95..e261dd4b98 100644 --- a/setup.h +++ b/setup.h @@ -257,9 +257,12 @@ int apply_repository_format(struct repository *repo, const char *get_template_dir(const char *option_template); -#define INIT_DB_QUIET (1 << 0) -#define INIT_DB_EXIST_OK (1 << 1) -#define INIT_DB_SKIP_REFDB (1 << 2) +enum init_db_flags { + INIT_DB_QUIET = (1 << 0), + INIT_DB_EXIST_OK = (1 << 1), + INIT_DB_SKIP_REFDB = (1 << 2), + INIT_DB_SKIP_ODB = (1 << 3), +}; int init_db(struct repository *repo, const char *git_dir, @@ -267,13 +270,15 @@ int init_db(struct repository *repo, const char *worktree, const char *template_dir, int hash_algo, enum ref_storage_format ref_storage_format, - const char *initial_branch, int init_shared_repository, - unsigned int flags); + const char *initial_branch, + int init_shared_repository, + enum init_db_flags flags); void initialize_repository_version(struct repository *repo, int hash_algo, enum ref_storage_format ref_storage_format, int reinit); void create_reference_database(struct repository *repo, const char *initial_branch, int quiet); +void create_object_database(struct repository *repo, const struct strvec *alternates); /* * NOTE NOTE NOTE!! diff --git a/t/t5604-clone-reference.sh b/t/t5604-clone-reference.sh index 39a0c318df..9e4b98fdb8 100755 --- a/t/t5604-clone-reference.sh +++ b/t/t5604-clone-reference.sh @@ -383,4 +383,29 @@ test_expect_success 'dissociate from repo with commit graph' ' git clone --no-local --reference graph.git --dissociate orig clone ' +test_expect_success 'local clone from linked worktree carries over alternates' ' + rm -fr base derived derived-wt dst expect && + git init base && + test_commit -C base one && + git clone --shared base derived && + git -C derived worktree add ../derived-wt && + git clone derived-wt dst && + echo "$(pwd)/base/.git/objects" >expect && + test_cmp expect dst/.git/objects/info/alternates && + git -C dst fsck +' + +test_expect_success 'local clone from linked worktree resolves relative alternates' ' + rm -fr base derived derived-wt dst expect && + git init base && + test_commit -C base one && + git clone --shared base derived && + echo "../../../base/.git/objects" >derived/.git/objects/info/alternates && + git -C derived worktree add ../derived-wt && + git clone derived-wt dst && + echo "$(pwd)/base/.git/objects" >expect && + test_cmp expect dst/.git/objects/info/alternates && + git -C dst fsck +' + test_done