From 20e38cba3926a5b42ae1a83c7d4776285bc5c841 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 10 Sep 2026 17:09:11 +0200 Subject: [PATCH 1/9] setup: split up concerns of `init_db()` The function `init_db()` is responsible for creating the on-disk directory structure required for a Git repository. It is used by both git-init(1) and git-clone(1), and because their expected behaviour is different we support a couple of flags: - The `QUIET` flag controls whether the command is quiet or not. For git-init(1) this is user-controllable, whereas for git-clone(1) we're always quiet. - The `EXIST_OK` flag controls whether a preexisting repository is okay or not. For git-init(1) it is, for git-clone(1) it's not. - The `SKIP_REFDB` flag controls whether the reference database should already be created or not. For git-init(1) we do, but for git-clone(1) we don't because it does not yet know about the default branch and about the remote object hash. Furthermore, we're about to add another divergence in behaviour, where we have to also skip creation of the object database in git-clone(1). This is becoming quite cumbersome though. Instead of introducing another flag, start to split up concerns of the function so that we never create the reference or object database. This becomes the responsibility of the caller, which is thus free to defer their creation to a later point in time. This lets us get rid of most of the divergent behaviour: - We don't need the `SKIP_REFDB` and a potential `SKIP_ODB` flags anymore. - We don't need the `QUIET` flag anymore, as nothing prints output except for the final status message that tells the user that the repository has been (re)initialized. But as this message is specific to git-init(1), we can easily move it there. The only piece of information we still have to convey is whether or not reinitialization of a preexisting repository is okay. This is handled by a new `reinit_ok` pointer that, if non-`NULL`, indicates that it is okay to reinitialize the repository. Furthermore, the pointer will be written to to indicate whether the repository was reinitialized or not, which we need in git-init(1) to print the correct initialization message. With these refactorings, `init_db()` is named quite misleadingly though, as we don't create any of the reference or object databases anymore. Rename it to `create_repository()`. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/clone.c | 9 ++++---- builtin/init-db.c | 32 +++++++++++++++++++++------- setup.c | 54 +++++++++++++++-------------------------------- setup.h | 43 +++++++++++++++++++++++++++---------- 4 files changed, 78 insertions(+), 60 deletions(-) diff --git a/builtin/clone.c b/builtin/clone.c index 5b25cca510..904d2d859f 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -1185,10 +1185,10 @@ int cmd_clone(int argc, * repository, and reference backends may persist that information into * their on-disk data structures. */ - 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); + create_repository(the_repository, git_dir, real_git_dir, work_tree, + option_template, GIT_HASH_UNKNOWN, ref_storage_format, + do_not_override_repo_unix_permissions, NULL); + create_object_database(the_repository); if (real_git_dir) { free((char *)git_dir); @@ -1445,6 +1445,7 @@ int cmd_clone(int argc, initialize_repository_version(the_repository, hash_algo, the_repository->ref_storage_format, 1); repo_set_hash_algo(the_repository, hash_algo); create_reference_database(the_repository, NULL, 1); + startup_info->have_repository = 1; /* * Before fetching from the remote, download and install bundle diff --git a/builtin/init-db.c b/builtin/init-db.c index e96b1283b7..f2c7e3be6d 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -80,7 +80,7 @@ int cmd_init_db(int argc, char *work_tree = NULL; const char *template_dir = NULL; char *template_dir_to_free = NULL; - unsigned int flags = 0; + int quiet = 0; int bare = startup_info->force_bare_repository ? 1 : -1; const char *object_format = NULL; const char *ref_format = NULL; @@ -102,7 +102,7 @@ int cmd_init_db(int argc, .flags = PARSE_OPT_OPTARG | PARSE_OPT_NONEG, .callback = shared_callback }, - OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET), + OPT_BOOL('q', "quiet", &quiet, N_("be quiet")), OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"), N_("separate git dir from working tree")), OPT_STRING('b', "initial-branch", &initial_branch, N_("name"), @@ -113,7 +113,7 @@ int cmd_init_db(int argc, N_("specify the reference format to use")), OPT_END() }; - int ret; + int reinit; argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0); @@ -247,14 +247,30 @@ int cmd_init_db(int argc, die(_("--separate-git-dir incompatible with bare repository")); } - flags |= INIT_DB_EXIST_OK; - ret = init_db(the_repository, git_dir, real_git_dir, work_tree, - template_dir, hash_algo, ref_storage_format, initial_branch, - init_shared_repository, flags); + create_repository(the_repository, git_dir, real_git_dir, work_tree, + template_dir, hash_algo, ref_storage_format, + init_shared_repository, &reinit); + create_reference_database(the_repository, initial_branch, quiet); + create_object_database(the_repository); + + if (!quiet) { + int len = strlen(git_dir); + + if (reinit) + printf(repo_settings_get_shared_repository(the_repository) + ? _("Reinitialized existing shared Git repository in %s%s\n") + : _("Reinitialized existing Git repository in %s%s\n"), + git_dir, len && git_dir[len-1] != '/' ? "/" : ""); + else + printf(repo_settings_get_shared_repository(the_repository) + ? _("Initialized empty shared Git repository in %s%s\n") + : _("Initialized empty Git repository in %s%s\n"), + git_dir, len && git_dir[len-1] != '/' ? "/" : ""); + } free(template_dir_to_free); free(real_git_dir_to_free); free(work_tree); free(git_dir); - return ret; + return 0; } diff --git a/setup.c b/setup.c index d90654f584..8c7b97f92e 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 @@ -2822,17 +2822,17 @@ static void repository_format_configure(struct repository_format *repo_fmt, } } -int init_db(struct repository *repo, - const char *git_dir, - const char *real_git_dir, - const char *worktree, - const char *template_dir, int hash, - enum ref_storage_format ref_storage_format, - const char *initial_branch, - int init_shared_repository, unsigned int flags) +void create_repository(struct repository *repo, + const char *git_dir, + const char *real_git_dir, + const char *worktree, + const char *template_dir, + int hash, + enum ref_storage_format ref_storage_format, + int init_shared_repository, + int *reinit_ok) { - int reinit; - int exist_ok = flags & INIT_DB_EXIST_OK; + int reinit_ignored; char *original_git_dir = real_pathdup(git_dir, 1); struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; struct strbuf err = STRBUF_INIT; @@ -2840,10 +2840,10 @@ int init_db(struct repository *repo, if (real_git_dir) { struct stat st; - if (!exist_ok && !stat(git_dir, &st)) + if (!reinit_ok && !stat(git_dir, &st)) die(_("%s already exists"), git_dir); - if (!exist_ok && !stat(real_git_dir, &st)) + if (!reinit_ok && !stat(real_git_dir, &st)) die(_("%s already exists"), real_git_dir); apply_and_export_relative_gitdir(repo, real_git_dir, 1); @@ -2877,8 +2877,10 @@ int init_db(struct repository *repo, safe_create_dir(repo, git_dir, 0); - reinit = create_default_files(repo, template_dir, original_git_dir, - &repo_fmt, init_shared_repository); + if (!reinit_ok) + reinit_ok = &reinit_ignored; + *reinit_ok = create_default_files(repo, template_dir, original_git_dir, + &repo_fmt, init_shared_repository); if (repo_settings_get_shared_repository(repo)) { char buf[10]; @@ -2901,29 +2903,7 @@ int init_db(struct repository *repo, repo_config_set(repo, "receive.denyNonFastforwards", "true"); } - if (!(flags & INIT_DB_SKIP_REFDB)) - create_reference_database(repo, initial_branch, flags & INIT_DB_QUIET); - create_object_database(repo); - - startup_info->have_repository = 1; - - if (!(flags & INIT_DB_QUIET)) { - int len = strlen(git_dir); - - if (reinit) - printf(repo_settings_get_shared_repository(repo) - ? _("Reinitialized existing shared Git repository in %s%s\n") - : _("Reinitialized existing Git repository in %s%s\n"), - git_dir, len && git_dir[len-1] != '/' ? "/" : ""); - else - printf(repo_settings_get_shared_repository(repo) - ? _("Initialized empty shared Git repository in %s%s\n") - : _("Initialized empty Git repository in %s%s\n"), - git_dir, len && git_dir[len-1] != '/' ? "/" : ""); - } - clear_repository_format(&repo_fmt); strbuf_release(&err); free(original_git_dir); - return 0; } diff --git a/setup.h b/setup.h index 763fd384e8..f1c1ed65fb 100644 --- a/setup.h +++ b/setup.h @@ -256,24 +256,45 @@ 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) +/* + * Create the repository by creating the necessary directory structures, + * setting up the configuration and configuring the repository's format. If + * `template_dir` is set, copy over templates from that directory. Furthermore, + * if and only if `reinit_ok` is a non-NULL pointer, then the function may + * reinitialize a preexisting repository. In that case, the pointer will be set + * to `1` in case the repo was reinitialized and `0` if it didn't exist yet. + * + * Note that this function does not create the reference and object databases. + */ +void create_repository(struct repository *repo, + const char *git_dir, + const char *real_git_dir, + const char *worktree, + const char *template_dir, + int hash_algo, + enum ref_storage_format ref_storage_format, + int init_shared_repository, + int *reinit_ok); -int init_db(struct repository *repo, - const char *git_dir, - const char *real_git_dir, - 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); void initialize_repository_version(struct repository *repo, int hash_algo, enum ref_storage_format ref_storage_format, int reinit); + +/* + * Create the reference database for the repository. The repository and its ref + * storage format must have already been configured properly before calling + * this function. When set, `initial_branch` overrides the default branch that + * HEAD will point to. + */ void create_reference_database(struct repository *repo, const char *initial_branch, int quiet); +/* + * Create the object database for the repository. The repository must have + * already been configured properly before calling this function. + */ +void create_object_database(struct repository *repo); + /* * NOTE NOTE NOTE!! * From dc33728d9d6249892c68060e2c21a211560e960c Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 10 Sep 2026 17:09:12 +0200 Subject: [PATCH 2/9] 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 "--reference{,-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. Defer creation of the object database until after we have resolved the URI. Note that this also requires us to defer the call to `setup_reference()` until after we have created the object database. While you might think that this function has something to do with references ("refs/*"), it is in fact responsible for setting up alternates. Consequently, we can only call it after we have created the object database already. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- builtin/clone.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/builtin/clone.c b/builtin/clone.c index 904d2d859f..bdcbd7aa1b 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -1188,7 +1188,6 @@ int cmd_clone(int argc, create_repository(the_repository, git_dir, real_git_dir, work_tree, option_template, GIT_HASH_UNKNOWN, ref_storage_format, do_not_override_repo_unix_permissions, NULL); - create_object_database(the_repository); if (real_git_dir) { free((char *)git_dir); @@ -1311,9 +1310,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 +1338,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; From f7aeeae6682881263adabc3d176ef1ce83205e79 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 10 Sep 2026 17:09:13 +0200 Subject: [PATCH 3/9] 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 bdcbd7aa1b..ac5843d7b9 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 501548160c6bd13a622e78ad393baf49f9a6eed8 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 10 Sep 2026 17:09:14 +0200 Subject: [PATCH 4/9] 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 alternates 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, as it does not set up the references itself anymore. 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 ac5843d7b9..08d913d306 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 collect_alternates_data { + struct strvec *alternates; + bool required; +}; + +static int collect_one_alternate(struct string_list_item *item, void *cb_data) { + struct collect_alternates_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 collect_alternates_data data = { + .alternates = alternates, + .required = true, + }; + + for_each_string_list(&option_required_reference, + collect_one_alternate, &data); + data.required = false; + for_each_string_list(&option_optional_reference, + collect_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 [] [--] []"), @@ -1339,8 +1348,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); @@ -1638,6 +1649,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 87e10a91e2396507451387d3cf9f351d2cf7d7e5 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 10 Sep 2026 17:09:15 +0200 Subject: [PATCH 5/9] 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, but means that at this step, we may compute `commondir` without it being used. It will become used in the next step though. 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 08d913d306..d397fd36b2 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 *local_source_repo) { if (option_required_reference.nr || option_optional_reference.nr) { struct collect_alternates_data data = { @@ -234,6 +235,16 @@ static void collect_alternates(struct strvec *alternates) for_each_string_list(&option_optional_reference, collect_one_alternate, &data); } + + if (local_source_repo) { + struct strbuf commondir = STRBUF_INIT; + + get_common_dir(&commondir, local_source_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); @@ -1348,7 +1353,7 @@ int cmd_clone(int argc, warning(_("--local is ignored")); create_object_database(the_repository); - collect_alternates(&alternates); + collect_alternates(&alternates, is_local ? path : NULL); for (size_t i = 0; i < alternates.nr; i++) odb_add_to_alternates_file(the_repository->objects, alternates.v[i]); From 002a7cfc731b9a4b77b580e82cba9aeea5b692cf Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 10 Sep 2026 17:09:16 +0200 Subject: [PATCH 6/9] 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 test 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 d397fd36b2..17353a8e1f 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -181,7 +181,7 @@ static int collect_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, local_source_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 d13e56771dd8ff8002c9ec31f17789df00d9199b Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 10 Sep 2026 17:09:17 +0200 Subject: [PATCH 7/9] 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 8c7b97f92e..37a8e6f124 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 a9c1a15debaf5dfc6e84bf86d24cc18a1333dfb1 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 10 Sep 2026 17:09:18 +0200 Subject: [PATCH 8/9] 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 +---- builtin/init-db.c | 2 +- setup.c | 7 +++++-- setup.h | 6 ++++-- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/builtin/clone.c b/builtin/clone.c index 17353a8e1f..b14264c33a 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -1364,11 +1364,8 @@ int cmd_clone(int argc, if (option_local > 0 && !is_local) warning(_("--local is ignored")); - create_object_database(the_repository); collect_alternates(&alternates, is_local ? path : NULL); - - 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/builtin/init-db.c b/builtin/init-db.c index f2c7e3be6d..5c22eae2f3 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -251,7 +251,7 @@ int cmd_init_db(int argc, template_dir, hash_algo, ref_storage_format, init_shared_repository, &reinit); create_reference_database(the_repository, initial_branch, quiet); - create_object_database(the_repository); + create_object_database(the_repository, NULL); if (!quiet) { int len = strlen(git_dir); diff --git a/setup.c b/setup.c index 37a8e6f124..17d0d25973 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 diff --git a/setup.h b/setup.h index f1c1ed65fb..27b2492373 100644 --- a/setup.h +++ b/setup.h @@ -291,9 +291,11 @@ void create_reference_database(struct repository *repo, const char *initial_bran /* * Create the object database for the repository. The repository must have - * already been configured properly before calling this function. + * already been configured properly before calling this function. When set, + * `alternates` is the list of alternates that should be written into the + * object database. */ -void create_object_database(struct repository *repo); +void create_object_database(struct repository *repo, const struct strvec *alternates); /* * NOTE NOTE NOTE!! From d1019ac8941cd0d50e71c6b03e171faf80843c8d Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Thu, 10 Sep 2026 17:09:19 +0200 Subject: [PATCH 9/9] 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