From ef8fb7fd96f548a1f2a05463d7102cab2798cc82 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 31 Aug 2026 12:02:05 +0200 Subject: [PATCH 1/8] builtin/clone: defer setup of the object database When cloning a repository we defer initialization of the reference database. This is because we don't yet know all details required for us to initialize the refdb in the first place. Most importantly, what we are missing is information about the object hash. We don't do the same thing for the object database yet, but here we essentially have the same problem. While the "files" database does not need any information about the object format at creation time, alternate backends are likely to require that information so that they can properly set up their data structures. Besides this forward-looking future proofing though, we also have a second use case for deferring initialization of the object database, namely alternates. When initializing the object database we do not yet know whether we'll need alternates or not because this depends on the repository we're about to clone from. If it is a local repository and the user has passed "--refernce{,-if-able}", then we will end up writing alternates into the object database. The ugly part though is that we cannot determine where the repository is getting cloned from before it has been initialized. While we of course already have access to the user-provided URI, that URI can be very well rewritten via "url..insteadOf". We can of course read the global- and system-level configuration to resolve it. But we explicitly resolve the URI a second time after we have initialized the repository because it can happen that we copy a ".git/config" over from our templates, and that file may cause us to rewrite the path. In a subsequent commit though we'll start to write alternates as part of the repository initialization, so we'll need to have the URI properly resolved before we can initialize the object database. This is ugly, but as mentioned above it makes sense for us to defer its initialization anyway so that we also know about the object hash already. Introduce a new flag that makes `init_db()` skip initializing the object database. Expose `create_object_database()` and make use of it after we have resolved the URI. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/clone.c | 12 ++++++++---- setup.c | 8 +++++--- setup.h | 15 ++++++++++----- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/builtin/clone.c b/builtin/clone.c index 5b25cca510..0a67492ebd 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -1184,11 +1184,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 +1314,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 +1342,10 @@ int cmd_clone(int argc, if (option_local > 0 && !is_local) warning(_("--local is ignored")); + create_object_database(the_repository); + if (option_required_reference.nr || option_optional_reference.nr) + setup_reference(); + transport = transport_get(remote, path ? path : remote->url.v[0]); transport_set_verbosity(transport, option_verbosity, option_progress); transport->family = family; diff --git a/setup.c b/setup.c index d90654f584..e654e27d05 100644 --- a/setup.c +++ b/setup.c @@ -2647,7 +2647,7 @@ 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) { /* * Create the "objects" directory in the common directory. This is done @@ -2829,7 +2829,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; @@ -2903,7 +2904,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); startup_info->have_repository = 1; diff --git a/setup.h b/setup.h index 763fd384e8..570ebcd150 100644 --- a/setup.h +++ b/setup.h @@ -256,9 +256,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, @@ -266,13 +269,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); /* * NOTE NOTE NOTE!! From 1cd2aaa24bd3ce714a8706cdb102c91a07f1683c Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 31 Aug 2026 12:02:06 +0200 Subject: [PATCH 2/8] builtin/clone: move around `setup_reference()` In a subsequent commit, `setup_reference()` will start to call `copy_alternates()`. Prepare for this by moving the function further down so that we can avoid adding a declaration. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/clone.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/builtin/clone.c b/builtin/clone.c index 0a67492ebd..8c990ce0cc 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -179,16 +179,6 @@ 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) { /* @@ -228,6 +218,16 @@ static void copy_alternates(struct strbuf *src, const char *src_repo) fclose(in); } +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 mkdir_if_missing(const char *pathname, mode_t mode) { struct stat st; From 2ab05bcd00679a88985937163800d4e89c334f11 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 31 Aug 2026 12:02:07 +0200 Subject: [PATCH 3/8] builtin/clone: refactor handling of "--reference{,-if-able}" Users can pass "--reference{,-if-able}" to git-clone(1) to instruct it to set up alternates for the newly created repository. This allows it to reuse objects from the source repository so that in the best case we don't have to clone all objects over. Those options are handled by the confusingly named `setup_reference()` function -- without the above context, one might rightfully believe that it was about refs, not about alternates. The function itself is rather simple: we loop through all provided alternate paths and then, if such an alternate is valid, we write it to the object database. In subsequent commits we're about to consolidate the complete setup of alternates into this function, and furthermore we'll refactor the setup of the object database to handle doing this for us instead of writing the alterantes into it one by one. Prepare for this refactoring by collecting the alternates into a strvec. Rename the function to `collect_alternates()` to clarify its scope. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/clone.c | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/builtin/clone.c b/builtin/clone.c index 8c990ce0cc..8eae3ac7d9 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); @@ -218,14 +220,20 @@ static void copy_alternates(struct strbuf *src, const char *src_repo) fclose(in); } -static void setup_reference(void) +static void collect_alternates(struct strvec *alternates) { - 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); + 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); + } } static void mkdir_if_missing(const char *pathname, mode_t mode) @@ -999,6 +1007,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 [] [--] []"), @@ -1343,8 +1352,10 @@ int cmd_clone(int argc, warning(_("--local is ignored")); create_object_database(the_repository); - if (option_required_reference.nr || option_optional_reference.nr) - setup_reference(); + collect_alternates(&alternates); + + for (size_t i = 0; i < alternates.nr; i++) + odb_add_to_alternates_file(the_repository->objects, alternates.v[i]); transport = transport_get(remote, path ? path : remote->url.v[0]); transport_set_verbosity(transport, option_verbosity, option_progress); @@ -1641,6 +1652,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); From ec2df7848f526c183f9e6678dd21e9610f1c10ce Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 31 Aug 2026 12:02:08 +0200 Subject: [PATCH 4/8] builtin/clone: move setup of alternates for shared local clones When cloning a local repository with "--shared" we add that repository to the new repository's alternates. This is done in `clone_local()`, which is responsible for performing local clones. Move the logic into `collect_alternates()` to unify our setup of alternates. Furthermore, this will allow us to set up alternates right at creation time of the object database. Note that the logic for cloning a local repository with "--no-shared" is not yet part of `collect_alternates()`. This will be handled in the next commit. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/clone.c | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/builtin/clone.c b/builtin/clone.c index 8eae3ac7d9..08c8f5a94f 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -220,7 +220,8 @@ static void copy_alternates(struct strbuf *src, const char *src_repo) fclose(in); } -static void collect_alternates(struct strvec *alternates) +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 = { @@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates) 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); + + strbuf_release(&commondir); + } } static void mkdir_if_missing(const char *pathname, mode_t mode) @@ -357,13 +368,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); @@ -1352,7 +1357,7 @@ int cmd_clone(int argc, warning(_("--local is ignored")); create_object_database(the_repository); - collect_alternates(&alternates); + collect_alternates(&alternates, path, is_local); for (size_t i = 0; i < alternates.nr; i++) odb_add_to_alternates_file(the_repository->objects, alternates.v[i]); From 2e26876b48a9048ce5658168cf39aac56a27a405 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 31 Aug 2026 12:02:09 +0200 Subject: [PATCH 5/8] builtin/clone: move setup of alternates for non-shared local clones Similar as in the preceding commit, move the setup of alternates for local clones with "--no-shared" into `collect_alternates()`. With this step, the complete setup of alternates is now handled by that function. Note that besides moving stuff around, it also fixes a bug: previously, we did not know to resolve the referenced repository's common directory. Consequently, when referencing a worktree we failed to resolve alternates. But as `collect_alternates()` already knows to resolve the commondir for "--local" we can simply reuse this resolved path for our purpose. Add two tests, the first one of which exercises this bug to avoid future regressions. The second patch ensures that we properly handle relative alternates for a referenced worktree. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/clone.c | 34 +++++++++++++++++++++++----------- t/t5604-clone-reference.sh | 25 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 11 deletions(-) diff --git a/builtin/clone.c b/builtin/clone.c index 08c8f5a94f..2e3473fddf 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -181,7 +181,7 @@ static int add_one_alternate(struct string_list_item *item, void *cb_data) return 0; } -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 @@ -195,29 +195,41 @@ 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, @@ -242,6 +254,8 @@ static void collect_alternates(struct strvec *alternates, 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); } @@ -320,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); 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 From d540449b157e1a78ef5d1af664646ff6a90e3758 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 31 Aug 2026 12:02:10 +0200 Subject: [PATCH 6/8] odb/source: support writing alternates when creating the database Add the ability to write alternates when creating the object database. This change allows us to remove the `write_alternates()` callback in a subsequent patch. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- odb/source-files.c | 76 ++++++++++++++++++++++++++++++++++++++++++++-- odb/source.h | 17 +++++++++-- setup.c | 4 ++- 3 files changed, 91 insertions(+), 6 deletions(-) diff --git a/odb/source-files.c b/odb/source-files.c index b7b3a297bb..8fe65d91f8 100644 --- a/odb/source-files.c +++ b/odb/source-files.c @@ -18,6 +18,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, diff --git a/odb/source.h b/odb/source.h index ea8675247e..63f1c0c531 100644 --- a/odb/source.h +++ b/odb/source.h @@ -36,6 +36,15 @@ struct object_id; struct odb_stream; 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 @@ -106,7 +115,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 @@ -356,11 +366,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); } /* diff --git a/setup.c b/setup.c index e654e27d05..426cc7dff8 100644 --- a/setup.c +++ b/setup.c @@ -2649,6 +2649,8 @@ static int create_default_files(struct repository *repo, void create_object_database(struct repository *repo) { + struct odb_create_on_disk_options opts = { 0 }; + /* * Create the "objects" directory in the common directory. This is done * so that the repository can be discovered regardless of the backend @@ -2668,7 +2670,7 @@ 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")); } From 1b65c850a5a898d15bd52e2d9d5b53d14fb56fda Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 31 Aug 2026 12:02:11 +0200 Subject: [PATCH 7/8] builtin/clone: write alternates via `odb_create_on_disk()` When creating a repository with alternates we first initialize the object database and then write alternates to it in a separate step. This is unfortunate due to a couple of reasons: - It requires us to have a `write_alternates()` callback, which is unfortunate as we never even write alternates to an object database after it has been created. - We're about to make alternates an implementation detail of the object database's backend in a future patch series, so having this callback is suboptimal there. - The backend has more flexibility with how exactly alternates are configured when it itself is in full control over their setup at the time where it creates the object database itself. We have thus introduced the ability to write alternates right at creation time in the preceding commits, and we have unified setup of alternates into a single location. All that's left to do for us now is to wire up alternates as an option for the database creation. Do so. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/clone.c | 5 +---- setup.c | 9 ++++++--- setup.h | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/builtin/clone.c b/builtin/clone.c index 2e3473fddf..48ac379b1d 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -1368,11 +1368,8 @@ int cmd_clone(int argc, if (option_local > 0 && !is_local) warning(_("--local is ignored")); - create_object_database(the_repository); collect_alternates(&alternates, path, is_local); - - for (size_t i = 0; i < alternates.nr; i++) - odb_add_to_alternates_file(the_repository->objects, alternates.v[i]); + create_object_database(the_repository, &alternates); transport = transport_get(remote, path ? path : remote->url.v[0]); transport_set_verbosity(transport, option_verbosity, option_progress); diff --git a/setup.c b/setup.c index 426cc7dff8..cfa286ff59 100644 --- a/setup.c +++ b/setup.c @@ -2647,9 +2647,12 @@ static int create_default_files(struct repository *repo, return reinit; } -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 = { 0 }; + struct odb_create_on_disk_options opts = { + .alternates = alternates, + }; /* * Create the "objects" directory in the common directory. This is done @@ -2907,7 +2910,7 @@ int init_db(struct repository *repo, if (!(flags & INIT_DB_SKIP_REFDB)) create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET); if (!(flags & INIT_DB_SKIP_ODB)) - create_object_database(repo); + create_object_database(repo, NULL); startup_info->have_repository = 1; diff --git a/setup.h b/setup.h index 570ebcd150..34e86dad37 100644 --- a/setup.h +++ b/setup.h @@ -277,7 +277,7 @@ void initialize_repository_version(struct repository *repo, 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); +void create_object_database(struct repository *repo, const struct strvec *alternates); /* * NOTE NOTE NOTE!! From 9e665c13b7f8a99048982cd9317548109609c991 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 31 Aug 2026 12:02:12 +0200 Subject: [PATCH 8/8] odb/source: remove the ability to write alternates There are no users of `odb_source_write_alternates()` in our tree anymore. Remove that function and its supporting infrastructure. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- odb.c | 9 -------- odb.h | 7 ------ odb/source-files.c | 54 ------------------------------------------- odb/source-inmemory.c | 7 ------ odb/source-loose.c | 7 ------ odb/source-packed.c | 7 ------ odb/source.h | 26 --------------------- 7 files changed, 117 deletions(-) diff --git a/odb.c b/odb.c index 67d98d64fc..b531cf8fb3 100644 --- a/odb.c +++ b/odb.c @@ -239,15 +239,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 b9e0db56ec..2d002461f8 100644 --- a/odb.h +++ b/odb.h @@ -270,13 +270,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 8fe65d91f8..b3f340dff8 100644 --- a/odb/source-files.c +++ b/odb/source-files.c @@ -306,59 +306,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; @@ -842,7 +789,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; diff --git a/odb/source-inmemory.c b/odb/source-inmemory.c index 795672adf2..b00248dfb2 100644 --- a/odb/source-inmemory.c +++ b/odb/source-inmemory.c @@ -326,12 +326,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) { } @@ -388,7 +382,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 bb3455dfbd..0f9b30bac1 100644 --- a/odb/source-loose.c +++ b/odb/source-loose.c @@ -982,12 +982,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); @@ -1053,7 +1047,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(NULL, odb_source_loose_reparent, loose); diff --git a/odb/source-packed.c b/odb/source-packed.c index 630d955585..c2d253759c 100644 --- a/odb/source-packed.c +++ b/odb/source-packed.c @@ -628,12 +628,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, @@ -849,7 +843,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(NULL, odb_source_packed_reparent, packed); diff --git a/odb/source.h b/odb/source.h index 63f1c0c531..693a9fc604 100644 --- a/odb/source.h +++ b/odb/source.h @@ -286,19 +286,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. @@ -518,19 +505,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